Относительный 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;
- Запускать под админом.
- Сообщение Assign: