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

Управление правами с помощью плагинов

Предположим мы хотим сделать несколько ответственных за одну запись (т.е. предоставить им определенные права вне контекста Ролей безопасности). Но ответственным может быть только кто-то один. Как раз для этих целей в CRM 2011 появился функционал Рабочих групп, которые можно назначать ответственными за записи. Но ручное управление такими Рабочими группами несколько трудоемко. Поэтому разработаем более простой механизм, с помощью которого пользователи смогут добавлять Соответственных к записи.

Как это будет работать:

  • На форму объекта будет вынесен дополнительный лукап на пользователя – Соответственный;
  • При создании записи (в данном примере Организации) будет создаваться Рабочая группа с тем же именем и запись Организации будет расшариваться на эту рабочую группу;
  • Если при создании записи лукап совладельца будет заполнен, то он будет тут добавлен в служебную Рабочую группу;
  • При изменении Соответственного старый пользователь будет удаляться из служебной Рабочей группы, а новый добавляться;
  • При удалении записи соответствующая Рабочая группа будет удалятся.

Два момента:

  • Можно было бы назначать Рабочую группу ответственно за запись, но тогда пришлось бы заводить для нее и назначить ей Роль безопасности. Плюс имеются некоторые проблемы с работой плагина при таком сценарии – система не успеет отработать назначение Роли безопасности Рабочей группе и при ее подставлении в лукап Ответственного мы получим ошибку (есть воркэраунд, но он слишком громоздкий на мой взгляд);
  • Можно было бы просто расшарить Соответственного на текущую запись, но решение должно быть универсальным. Этих Соответственных может быть много. Но с этим есть проблема: каждый раз при расшаривании записи, создается новая строчка в таблице PrincipalObjectAccess. И каждый раз при расчете прав происходит чтение и анализ этой таблицы. Соответственно чем больше записей в ней, тем медленнее происходит процесс обработки прав. Чтобы нивелировать этот момент и предлагается расшаривать запись на Рабочую группу, а всех соответственных добавлять именно в нее.

Итак, приступим:

  • Добавьте и вынесите на форму Организации новый лукап new_coownerid, ссылающийся на пользователей;
  • Создайте новый проект плагина с таким кодом:
    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.ServiceModel;
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Query;
    using Microsoft.Xrm.Sdk.Messages;
    using Microsoft.Crm.Sdk.Messages;
    
    namespace sharePlgn
    {
        public class AccountPostCreate : 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);
    
                    Entity target = (Entity)context.InputParameters["Target"];
    
                    // Создаем команду
                    Entity team = new Entity("team");
                    team["name"] = target["name"];
                    team["description"] = "Команда для Организации: " + target["name"];
                    team["businessunitid"] = new EntityReference("businessunit", context.BusinessUnitId);
                    team["administratorid"] = new EntityReference("systemuser", context.UserId);
                    Guid teamId = service.Create(team);
    
                    // Расшариваем Организацю на созданную Рабочую группу
                    var grantAccessRequest = new GrantAccessRequest
                    {
                        PrincipalAccess = new PrincipalAccess
                        {
                            AccessMask = AccessRights.ReadAccess | AccessRights.WriteAccess,
                            Principal = new EntityReference(team.LogicalName, teamId)
                        },
                        Target = new EntityReference("account", target.Id)
                    };
                    service.Execute(grantAccessRequest);
    
                    // Если при создании Организации задан Совладелец
                    if (target.Attributes.Contains("new_coownerid"))
                    {
                        // Получаем Совладельца
                        EntityReference coownerid = (EntityReference)target["new_coownerid"];
                        Guid[] Members = new Guid[1];
                        Members[0] = coownerid.Id;
    
                        // Добавлям Совладельца в созданную Рабочую группу
                        AddMembersTeamRequest AddMembersReq = new AddMembersTeamRequest();
                        AddMembersReq.TeamId = teamId;
                        AddMembersReq.MemberIds = Members;
                        service.Execute(AddMembersReq);
                    }
                }
                catch (FaultException<OrganizationServiceFault> ex)
                {
                    throw new InvalidPluginExecutionException("Ошибка: ", ex);
                }
            }
        }
    
        public class AccountPreDelete : 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);
    
                    // Из пре "снимка" получаем название Организации
                    Entity preImage = (Entity)context.PreEntityImages["preImage"];
                    string name = preImage["name"].ToString();
    
                    // Возвращаем все Рабочие групыы (и Пользователей) на которые расшарена запись
                    RetrieveSharedPrincipalsAndAccessRequest retrieveSharedRequest = new RetrieveSharedPrincipalsAndAccessRequest()
                    {
                        Target = new EntityReference("account", context.PrimaryEntityId)
                    };
                    RetrieveSharedPrincipalsAndAccessResponse retrieveSharedResponse = (RetrieveSharedPrincipalsAndAccessResponse)service.Execute(retrieveSharedRequest);
                    PrincipalAccess[] sharedItems = retrieveSharedResponse.PrincipalAccesses;
    
                    // Просматриваем все возвращенные записи
                    foreach (PrincipalAccess item in sharedItems)
                    {
                        // Если имя Рабочей группы совпадает с названием Организации, то удаляем ее
                        Entity retrievedTeam = service.Retrieve("team", item.Principal.Id, new ColumnSet("name"));
                        if (retrievedTeam["name"].ToString() == name) service.Delete("team", item.Principal.Id);
                    }
                }
                catch (FaultException<OrganizationServiceFault> ex)
                {
                    throw new InvalidPluginExecutionException("Ошибка: ", ex);
                }
            }
        }
    
        public class AccountPostUpdate : 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);
    
                    // Ссылки на "снимки"
                    Entity preImage = (Entity)context.PreEntityImages["Image"];
                    Entity postImage = (Entity)context.PostEntityImages["Image"];
    
                    // Из пре "снимка" получаем название Организации
                    string name = preImage["name"].ToString();
    
                    // Возвращаем все Рабочие групыы (и Пользователей) на которые расшарена запись
                    RetrieveSharedPrincipalsAndAccessRequest retrieveSharedRequest = new RetrieveSharedPrincipalsAndAccessRequest()
                    {
                        Target = new EntityReference("account", context.PrimaryEntityId)
                    };
                    RetrieveSharedPrincipalsAndAccessResponse retrieveSharedResponse = (RetrieveSharedPrincipalsAndAccessResponse)service.Execute(retrieveSharedRequest);
                    PrincipalAccess[] sharedItems = retrieveSharedResponse.PrincipalAccesses;
    
                    Guid teamId = Guid.Empty;
    
                    // Просматриваем все возвращенные записи
                    foreach (PrincipalAccess item in sharedItems)
                    {
                        // Если имя Рабочей группы совпадает с названием Организации, то искомая Рабочая группа найдена
                        Entity retrievedTeam = service.Retrieve("team", item.Principal.Id, new ColumnSet("name"));
                        if (retrievedTeam["name"].ToString() == name)
                            teamId = item.Principal.Id;
                    }
    
                    EntityReference coownerid = null;
    
                    // Если на пре "снимке" есть Совладелец
                    if (preImage.Attributes.Contains("new_coownerid"))
                    {
                        // Получаем Совладельца из пре "снимка"
                        coownerid = (EntityReference)preImage["new_coownerid"];
                        Guid[] Members = new Guid[1];
                        Members[0] = coownerid.Id;
    
                        // Удаляем Совладельца из созданной Рабочей группы
                        RemoveMembersTeamRequest removeMembersReq = new RemoveMembersTeamRequest()
                        {
                            TeamId = teamId,
                            MemberIds = Members
                        };
                        service.Execute(removeMembersReq);
                    }
    
                    // Если на пост "снимке" есть Совладелец
                    if (postImage.Attributes.Contains("new_coownerid"))
                    {
                        // Получаем Совладельца из пост "снимка"
                        coownerid = (EntityReference)postImage["new_coownerid"];
                        Guid[] Members = new Guid[1];
                        Members[0] = coownerid.Id;
    
                        // Добавлям Совладельца в созданную Рабочую группу
                        AddMembersTeamRequest AddMembersReq = new AddMembersTeamRequest()
                        {
                            TeamId = teamId,
                            MemberIds = Members
                        };
                        service.Execute(AddMembersReq);
                    }
                }
                catch (FaultException<OrganizationServiceFault> ex)
                {
                    throw new InvalidPluginExecutionException("Ошибка: ", ex);
                }
            }
        }
    }
    

    Здесь не один, а три плагина:

    • AccountPostCreate:
      • При создании Организации создает Рабочую группу;
      • Расшаривает текущую запись на Рабочую группу;
      • Если был задан Соответственный – добавляет его в Рабочую группу.
    • AccountPreDelete:
      При удалении записи удаляет соответствующую служебную Рабочую группу;
    • AccountPostUpdate
      • Если до обновления в лукапе Соответствнный содержался Пользователь, то удаляем его из служебной Рабочей группы;
      • Если после обновления в лукапе Соответствнный имеется Пользователь, то добавляем его в служебную Рабочую группу.
  • Зарегистрируйте плагин в CRM добавьте к нему следующие шаги и «снимки»:
    • o AccountPostCreate
      • Шаг на Create, объекта Организации, стадия Post;
    • o AccountPreDelete
      • Шаг на Delete, объекта Организации, стадия Pre;
      • Снимок на Pre с именем «preImage» и полем name.
    • o AccountPostUpdate
      • Шаг на Update, объекта Организации, стадия Post. Плагин должен реагировать только на изменение атрибута new_coownerid;
      • Снимок на Pre и Post с именем «Image» и полями name и new_coownerid.

Готово – идем создавать Организацию…

З.Ы. В данной реализации есть, что еще дорабатывать: проверку на удаление служебной Рабочей группы, проверку на уникальность названий Рабочих групп и т.д.





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

*

code