Field Security Profile – Based on Owner

Santosh BhagatDyn365CE4 years ago22 Views

 Recently received requirement related to Field security
profile.

Expectation: –

1.     Set to users need access of secure attributes.

2.     Owner of record need access of secure attributes.

3.      Access of secure attributes need to change once ownership
of record changed.

4.     Share Access of secure attributes once record is
shared.

Solution: – 

As per out -of-box offer from Dynamics CRM only first requirement
is feasible but for 2,3 and 4 requirements need to go for customization.

Dynamics CRM provide option to share secure attribute permission
via code.

Create a custom workflow to share attribute and register it.

using Microsoft.Xrm.Sdk;

using Microsoft.Xrm.Sdk.Client;

using Microsoft.Xrm.Sdk.Messages;

using Microsoft.Xrm.Sdk.Query;

using Microsoft.Xrm.Sdk.Workflow;

using System;

using System.Activities;

 namespace CustomWorkflows

{

    public sealed class ShareSecuredField : CodeActivity

    {

        [RequiredArgument]

        [Input(“Attributes Name”)]

        public InArgument<String> AttributeNames { 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 (Exception 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 recordId = context.PrimaryEntityId;

            string[] multiArray = this.AttributeNames.Get(executionContext).ToString().Split(‘,’);

            foreach (string var in multiArray)

            {

                string attributeName = var.Trim();

                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 userId = userToShare.Id;

                    ShareSecuredFieldCore(service,
entityName, attributeName, recordId, userId, allowRead, allowUpdate,
false);

                }

                if (teamToShare != null)

                {

                    Guid teamID = teamToShare.Id;

                    ShareSecuredFieldCore(service,
entityName, attributeName, recordId, teamID, allowRead, allowUpdate);

                }

            }

        }

        private void
ShareSecuredFieldCore(
IOrganizationService service, string
entityName,
string
attributeName,
Guid
recordId,
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, recordId));

               
queryPOAA.Criteria.Conditions.Add(
new ConditionExpression(“principalid”, ConditionOperator.Equal, principalId));

 

                //
Execute the query.

                EntityCollection responsePOAA = service.RetrieveMultiple(queryPOAA);

                //throw
new InvalidPluginExecutionException(“del” +
attributeResponse.AttributeMetadata.MetadataId.ToString()+” “+

                //    recordId.ToString()+” ”
+principalId.ToString());

                if (responsePOAA.Entities.Count > 0)

                {

                    Entity poaa = responsePOAA.Entities[0];

 

                    if (allowRead || allowUpdate)

                    {

                        poaa[“readaccess”] = allowRead;

                        poaa[“updateaccess”] =
allowUpdate;

 

                        service.Update(poaa);

                        // throw new InvalidPluginExecutionException(“up”);

                    }

                    else

                    {

                        service.Delete(“principalobjectattributeaccess”, poaa.Id);

                        // throw new InvalidPluginExecutionException(“del”+
responsePOAA.Entities.Count.ToString());

                    }

                }

                else

                {

                    if (allowRead || allowUpdate)

                    {

                        // Create POAA entity for user

                        Entity poaa = new Entity(“principalobjectattributeaccess”);

                        poaa[“attributeid”] =
attributeResponse.AttributeMetadata.MetadataId;

                        poaa[“objectid”] = new EntityReference(entityName, recordId);

                        poaa[“readaccess”] = allowRead;

                        poaa[“updateaccess”] =
allowUpdate;

                        if (shareWithTeam)

                        {

                            poaa[“principalid”] = new EntityReference(“team”, principalId);

                        }

                        else

                        {

                            poaa[“principalid”] = new EntityReference(“systemuser”, principalId);

                        }

 

                        service.Create(poaa);

 

                    }

                }

            }

        }

    }

}

Create workflow on create and Assign of record.

 

Make sure workflow set Execute As “The owner of the workflow”.
And owner of workflow have permission to change secure field permission or
system admin.

Call custom workflow from newly created workflow.

Now Configure parameters of custom workflow.

For “Attributes Name” use logical name of secure fields.

Example: – emailaddress,phone,mobile.

 

This is completed our second requirement as half of third requirement.

We need to change secure field permission on record is
shared or sharing is changed.

For that Dynamics CRM provides three messages that we can
use to complete our requirement.

·        
GrantAccess

·        
RevokeAccess

·        
ModifyAccess

Below plugin need to register on these messages to create/update/delete
sharing permission.

 

using Microsoft.Xrm.Sdk;

using Microsoft.Xrm.Sdk.Client;

using Microsoft.Xrm.Sdk.Query;

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using Microsoft.Crm.Sdk.Messages;

using Microsoft.Xrm.Sdk.Messages;

 

namespace CustomWorkflow.Custom_Plugin

{

    public class
OnShareRecordShareFLS : IPlugin

    {

        public void
Execute(IServiceProvider serviceProvider)

        {

            // Obtain
the execution context from the service provider.

           
IPluginExecutionContext context =
(IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

           
IOrganizationServiceFactory serviceFactory =
(IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

           
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

           
Post_Message_GrantOrRevokeAccess(service, context);

        }

        //this one for
Unshare and Share Registration

        //pass the
context here from your common Execute function

        void
Post_Message_GrantOrRevokeAccess(IOrganizationService service,
IPluginExecutionContext context)

        {

           
EntityReference EntityRef =
(EntityReference)context.InputParameters[“Target”];

            string
entityName = EntityRef.LogicalName;

            Guid
entityId = context.PrimaryEntityId;

            if
(EntityRef.LogicalName == “contact”)

            {

                if
(context.MessageName == “GrantAccess”)

                {

                   
PrincipalAccess PrincipalAccess =
(PrincipalAccess)context.InputParameters[“PrincipalAccess”];

                   
bool allowRead = true;

                   
bool allowUpdate = false;

 

                    if
(PrincipalAccess.AccessMask.ToString().Contains(“WriteAccess”))

                       
allowUpdate = true;

                   
//throw new
InvalidPluginExecutionException(PrincipalAccess.AccessMask.ToString());

                   
EntityReference userOrTeam = PrincipalAccess.Principal;

                   
Guid objectId = userOrTeam.Id;

 

                   
OrganizationRequest req = new
OrganizationRequest(“asia_FLSAddRemove”);

                   
req[“Target”] = EntityRef;

                    if
(userOrTeam.LogicalName == “systemuser”)

                       
req[“UserToShare”] = userOrTeam;

                    if
(userOrTeam.LogicalName == “team”)

                       
req[“TeamToShare”] = userOrTeam;

                   
req[“AllowRead”] = allowRead;

                   
req[“AllowUpdate”] = allowUpdate;

                   
OrganizationResponse response = service.Execute(req);

 

 

                }

                else
if (context.MessageName == “RevokeAccess”)

                {

                   
EntityReference Revokee = (EntityReference)context.InputParameters[“Revokee”];

                   
bool allowRead = false;

                   
bool allowUpdate = false;

                   
OrganizationRequest req = new
OrganizationRequest(“asia_FLSAddRemove”);

 

                   
req[“Target”] = EntityRef;

                    if
(Revokee.LogicalName == “systemuser”)

                       
req[“UserToShare”] = Revokee;

                    if
(Revokee.LogicalName == “team”)

                       
req[“TeamToShare”] = Revokee;

                   
req[“AllowRead”] = allowRead;

                   
req[“AllowUpdate”] = allowUpdate;

                   
OrganizationResponse response = service.Execute(req);

                    //
throw new InvalidPluginExecutionException(Revokee.LogicalName);

                }

                else
if (context.MessageName == “ModifyAccess”)

                {

                   
PrincipalAccess PrincipalAccess =
(PrincipalAccess)context.InputParameters[“PrincipalAccess”];

                    bool allowRead = true;

                   
bool allowUpdate = false;

                    if
(PrincipalAccess.AccessMask.ToString().Contains(“WriteAccess”))

                       
allowUpdate = true;

                   
//throw new InvalidPluginExecutionException(PrincipalAccess.AccessMask.ToString());

                   
EntityReference userOrTeam = PrincipalAccess.Principal;

                   
Guid objectId = userOrTeam.Id;

 

                   
OrganizationRequest req = new OrganizationRequest(“asia_FLSAddRemove”);

                   
req[“Target”] = EntityRef;

                    if
(userOrTeam.LogicalName == “systemuser”)

                       
req[“UserToShare”] = userOrTeam;

                    if
(userOrTeam.LogicalName == “team”)

                        req[“TeamToShare”] =
userOrTeam;

                   
req[“AllowRead”] = allowRead;

                   
req[“AllowUpdate”] = allowUpdate;

                   
OrganizationResponse response = service.Execute(req);

 

 

                   
//Then got the User or Team and also Access Control that being Granted

 

                   
//***to Get User/Team that being Shared With

 

 

 

                }

                else if (context.MessageName ==
“Assign”)

                {

 

                    if
(context.PreEntityImages.Contains(“preImage”) &&
context.PreEntityImages[“preImage”] is Entity)

                    {

                       
Entity preMessageImage =
(Entity)context.PreEntityImages[“preImage”];

                       
// get topic field value before database update perform

                       
EntityReference previousOwner =
(EntityReference)preMessageImage.Attributes[“ownerid”];

                        bool allowRead = false;

                       
bool allowUpdate = false;

                       
OrganizationRequest reqInner = new
OrganizationRequest(“asia_FLSAddRemove”);

                       
reqInner[“Target”] = EntityRef;

                        if (previousOwner.LogicalName ==
“systemuser”)

                           
reqInner[“UserToShare”] = new
EntityReference(“systemuser”, previousOwner.Id);

                       
reqInner[“AllowRead”] = allowRead;

                       
reqInner[“AllowUpdate”] = allowUpdate;

                       
OrganizationResponse responseinner = service.Execute(reqInner);

                    }

                }

            }

        }

    }

}

 

In this plugin we have called a “Action”. So in future if we
need to add or remove any attributes than we can do from UI using “Action” and no need to change plugin code.

 

Action: –

This action has four Input parameters to use.

Call our custom workflow from Action.

Configure Custom workflow parameters.

Now time to register the plugins.

While registering plugin make sure you use any admin user or
System (Disabled) user.

If want to remove access of secure fields from previous
owner add register plugin on “Assign” message and use “preImage”.

Ribbon: –

It’s better to hide or disable “Share secure fields” button
for normal expect admin so it will not confuse users.

Field security Profile: – 

This is our last step
create a new field security profile and add users who need create permission of
secure fields. With help of this user able to create record with secure field.

Now we share record it also shares secure attributes and sync
with Read and Write of sharing record.

Once record is saved need to refresh form once to read secure field.

Thanks for reading. HAPPY CRM !!       Follow me to for new updates. 

Original Post https://dotnetdeveloper2012.blogspot.com/2020/09/field-security-profile-based-on-owner.html

0 Votes: 0 Upvotes, 0 Downvotes (0 Points)

Leave a reply

Join Us
  • X Network2.1K
  • LinkedIn3.8k
  • Bluesky0.5K
Support The Site
Events
March 2025
MTWTFSS
      1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31       
« Feb   Apr »
Follow
Sign In/Sign Up Sidebar Search
Popular Now
Loading

Signing-in 3 seconds...

Signing-up 3 seconds...