Разработка
08
Окт
0

Относительный Field Level Security

Еще в CRM 2011 появилась такая фича как Field Level Security, которая позволяет скрывать значения определенный полей записи и показывать (и/или разрешать их редактировать) его только определенным Пользователям/Рабочим группам. Одна проблема – эта настройка глобальная – т.е. нельзя сделать видимость поля только для Ответственного. Если Пользователю разрешено видеть секретное поле, то он будет видеть его на вcех доступных ему записях.

К счастью, помимо прочего, MS запилила в функционал Field Level Security возможность расшаривания прав на защищенные полей. Поэтому запилим небольшой «кастыль», который будет предоставлять доступ к защищенному полю только Ответственному за запись:

  • Создайте правило Field Level Security для какого-либо поля (в данном примере это Телефон (new_telephone) на Организации), в котором разрешите Создание (чтобы кто-угодно мог заполнить его при создании) и запретите Чтение и Обновлении. И добавьте всех юзверов в этот профиль;
  • Создайте сборку плагина с таким кодом:
    using System;
    using System.ServiceModel;
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Query;
    using Microsoft.Xrm.Sdk.Messages;
    
    namespace ShareSecureField
    {
        public class ShareSecureField : IPlugin
        {
            public void Execute(IServiceProvider serviceProvider)
            {
                try
                {
                    IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
                    IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                    IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
    
                    // Запрашиваем из метаданных "секретный" атрибут
                    RetrieveAttributeRequest attributeRequest = new RetrieveAttributeRequest
                    {
                        EntityLogicalName = "account",
                        LogicalName = "new_telephone",
                        RetrieveAsIfPublished = true
                    };
    
                    RetrieveAttributeResponse attribute = (RetrieveAttributeResponse)service.Execute(attributeRequest);
    
                    // Если имеется ПРЕ-снимок
                    if (context.PreEntityImages.Contains("Image") && context.PreEntityImages["Image"] is Entity)
                    {
                        // Получаем из него старого Ответственного
                        Entity preImage = (Entity)context.PreEntityImages["Image"];
                        EntityReference oldOwnerid = (EntityReference)preImage["ownerid"];
    
                        // Запрашиваем объект запись расшаривания на текущего владельца
                        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, attribute.AttributeMetadata.MetadataId));
                        queryPOAA.Criteria.Conditions.Add(new ConditionExpression("objectid", ConditionOperator.Equal, context.PrimaryEntityId));
                        queryPOAA.Criteria.Conditions.Add(new ConditionExpression("principalid", ConditionOperator.Equal, oldOwnerid.Id));
                        EntityCollection responsePOAA = service.RetrieveMultiple(queryPOAA);
    
                        // Удаляем записи расшаривание (на старого Ответственного)
                        foreach (var deletePOAA in responsePOAA.Entities)
                        {
                            service.Delete("principalobjectattributeaccess", deletePOAA.Id);
                        }
                    }
    
                    // Получаем из ПОСТ-снимка текущего Ответственного
                    Entity postImage = (Entity)context.PostEntityImages["Image"];
                    EntityReference newOwnerid = (EntityReference)postImage["ownerid"];
    
                    // Расшариваем атрибут посредством объекта POAA на текущего Ответственного
                    Entity createPOAA = new Entity("principalobjectattributeaccess");
                    createPOAA["attributeid"] = attribute.AttributeMetadata.MetadataId;
                    createPOAA["objectid"] = new EntityReference("account", context.PrimaryEntityId);
                    createPOAA["readaccess"] = true;
                    createPOAA["updateaccess"] = true;
                    if (newOwnerid.LogicalName == "team")
                        createPOAA["principalid"] = new EntityReference("team", newOwnerid.Id);
                    else
                        createPOAA["principalid"] = new EntityReference("systemuser", newOwnerid.Id);
                    service.Create(createPOAA);
                }
                catch (FaultException<OrganizationServiceFault> ex)
                {
                    throw new InvalidPluginExecutionException("Ошибка: ", ex);
                }
            }
        }
    }
    

    Что тут происходит:

    • Сначала возвращаем из метаданных атрибут (для расшаривания нужен его ID). В целях улучшения производительности можно этот ID захардкодить или передать во входящих параметрах плагина;
    • Если имеется Пре-снимок, т.е. запись была уже создана и переназначается на другого Пользователя, то удаляем расшаривание секретного поля с предыдущего ответственного (значение которого берется из PreImage);
    • Берем из Пост-снимка нового Ответственного и расшариваем на него наше поле.
  • Зарегистрируйте плагин с помощью Plug-in Registration Tool, после чего зарегистрируйте два сообщения:
    • Сообщение Assign:
      • Стадия Post, синхронно;
      • Создайте два снимка на Пре и Пост с именем Image и атрибутом ownerid;
      • Запускать под админом.
    • Сообщение Create:
      • Стадия Post, синхронно;
      • Создайте снимок на Пост с именем Image и атрибутом ownerid;
      • Запускать под админом.




Комментарии (0)

*

code