[ELMA3] Создание прав доступа для записей объекта
В статье приведен пример создания различных прав доступа к записям Вашего объекта, а также доступ к модулю. В примере показано как создавать права доступа, такие как: просмотр записи объекта, редактирование записи объекта. Продемонстрировано добавление собственных ролей или использование уже существующих ролей. В качестве наглядного примера был разработан модуль с объектом, а также реализована выдача прав доступа. При создании данного примера были реализованы следующие точки расширения/базовые классы точек расширения:
- IPermissionProvider (Интерфейс расширения списка привилегий).
- IModuleAccessPermissionProvider (Интерфейс провайдера, определяющего привилегии для доступа к модулям).
- InstanceSettingsPermissionBase (Базовый класс интерфейса прав доступа к экземпляру объекта (IInstanceSettingsPermission) на основе настроек).
- IPermissionsDelegate (Данный интерфейс реализуется для каждого типа, экземпляры которого соотносятся с привилегиями).
- IEntityInstanceDefaultPermission (Права по умолчанию для объектов сущности).
- IPermissionRoleTypeProvider (Привилегии для объекта типа, роли для привилегий).
Пример отображения данных
Рис. 1. Список записей объекта IMyObject. При отсутствии прав доступа – запись у пользователя не показывается
Рис. 2. Права доступа к записи объекта
Рис. 3. Роли при назначении прав доступа
Рис. 4. Раздел с настройками к модулю в глобальных настройках доступа
Методы интерфейса IPermissionProvider
/// <summary> /// Получить список привилегий /// </summary> /// <returns></returns> IEnumerable<Permission> GetPermissions(); /// <summary> /// Получить информацию о назначении привилегий ролям по умолчанию /// </summary> /// <returns></returns> IEnumerable<PermissionStereotype> GetPermissionStereotypes();
Методы интерфейса IModuleAccessPermissionProvider
/// <summary> /// Получить словарь из значений "Идентификатор модуля->Привилегия доступа к модулю" /// </summary> /// <returns></returns> Dictionary<string, Permission> GetModuleAccessPermissions();
Методы интерфейса IInstanceSettingsPermission
/// <summary> /// Тип сущности /// </summary> Type EntityType { get; } /// <summary> /// Получить коллекцию объектов, в которых хранится информация об обладателях привилегий /// </summary> /// <returns></returns> ICollection<IInstanceSettingsPermissionHolder> GetPermissionCollection(object target); /// <summary> /// Нужно ли фильтровать экземпляры сущности при загрузке автоматически /// </summary> bool Filtering { get; } /// <summary> /// Имя свойства-ссылки на сущность в коллекции, хранящей предоставленные привилегии /// </summary> string TargetPropetyName { get; } /// <summary> /// Имя сущности, хранящей предоставленные привилегии (в коллекции) /// </summary> Type PermissionHolderType { get; } /// <summary> /// Привилегия администрирования сущностей, если задана, /// то пользователь, обладающий данной привилегией, получает все привилегии на объекты EntityType /// </summary> Permission AdminPermission { get; } /// <summary> /// Проверить, может ли пользователь предоставлять другим пользователям привилегии на объект /// </summary> /// <param name="user">Пользователь</param> /// <param name="target">Объект, на который выдаются права</param> /// <returns><c>true</c>, если пользователь может предоставлять другим пользователям привилегии на объект</returns> bool CanGrandPermissions(IUser user, object target); /// <summary> /// Расчет привилегий, которые может раздавать данный пользователь /// Если null, то раздавать можно любую привилегию /// </summary> /// <param name="user">Пользователь</param> /// <param name="target">Объект, на который выдаются права</param> /// <returns></returns> ICollection<Permission> CanGrandLevel(IUser user, object target); /// <summary> /// Можно ли раздавать права на элемент /// </summary> /// <param name="elementPermission"></param> /// <param name="userPermission"></param> /// <param name="permissionToCheck"></param> /// <param name="permissionRoleTypeId">Идентификатор типа роли</param> /// <param name="permissionRole">Роль</param> /// <param name="target">Объект, для которого выполняется настройка прав.</param> /// <returns><c>true</c>, если можно раздать права на элемент</returns> bool CanGrandToElement(Permission[] elementPermission, Permission[] userPermission, Permission permissionToCheck, Guid permissionRoleTypeId, IEntity permissionRole, object target); /// <summary> /// Системная роль, которую нельзя удалять. /// </summary> /// <param name="permissionRoleTypeId">Идентификатор типа роли.</param> /// <param name="permissionRole">Роль</param> /// <param name="target">Объект, для которого выполняется настройка прав.</param> /// <returns><c>true</c>, если роль системная</returns> bool IsSystemRole(Guid permissionRoleTypeId, IEntity permissionRole, object target);
Методы интерфейса IPermissionsDelegate
/// <summary> /// Проверить тип на соответсвие /// </summary> /// <param name="type">Тип</param> /// <param name="permission">Привилегия</param> /// <returns><c>true</c>, если тип соответствует указанной привилегии</returns> bool CanCheckPermissions(Type type, Permission permission); /// <summary> /// Проверить наличие привилегии /// </summary> /// <param name="user">Пользователь</param> /// <param name="permission">Привелегия</param> /// <param name="target">Целевой объект</param> /// <param name="skipAdmin">Пропустить проверку Административной привилегии</param> /// <returns><c>true</c>, если привилегия существует</returns> bool HasPermission(IUser user, Permission permission, object target, bool skipAdmin = false);
Методы интерфейса IEntityInstanceDefaultPermission
/// <summary> /// Тип сущности с привилегиями /// </summary> Type EntityType { get; } /// <summary> /// Создать привилегии /// </summary> /// <param name="entity">Сущность</param> void CreatePermissions(IEntity entity);
Методы интерфейса IPermissionRoleTypeProvider
/// <summary> /// Получить соответствие тип, привилегия, роль. /// По этот метод задает какие привилегии можно задавать на объекты типа, и какие роли возможны для привилегий /// </summary> IEnumerable<PermissionRoleTypeStereotype> GetTypePermissionRoleStereotypes(); /// <summary> /// Получить роли, относящиеся к привилегиям /// </summary> IEnumerable<PermissionRoleType> GetRoleTypes();
В примерах используются GUID, которые по возможности нужно генерировать собственные и не использовать представленные в примере. Для генереции GUID можно использовать встроенную в VS2013 утилиту генерации GUID. Путь доступа Tools – Create GUID. Пример на рис. 5. Чтобы сгенерировать новый GUID нужно нажать на кнопку Создать GUID, затем на Копировать. Сгенерированный GUID в поле Результат будет записан в буфер обмена, затем вы можете использовать в Вашем коде.
Рис. 5. Генерация Guid
Создание новой привилегии
Для создания новой привилегии необходимо передать 4 обязательных параметра:
- Идентификатор привилегии GUID.
- Название привилегии.
- Описание привилегии.
- Категория. Пример: в CRM имеются следующие категории привилегий: Общие настройки модуля, Компании, Контакты и т.д.
Также при создании привилегии можно указать необязательные параметры, обращение к которым выполняется при помощи конструкции: название_параметра: значение_параметра. Пример: moduleUid: Module.
Необязательные параметры:
- Идентификатор модуля moduleUid (по умолчанию установлено null). Пример: moduleUid: Module.
- Тип привилегии в отношении permissionType (по умолчанию установлено PermissionType.Global). Пример: permissionType: PermissionType.Instance.
Существует 3 типа привилегий:- PermissionType.Global – глобальная привилегия, например, "Доступ к модулю" или "Администрирование пользователей";
- PermissionType.EntityType – право на тип сущности, например, "Редактирование" для справочника Города;
- PermissionType.Instance – привилегия уровня экземпляра объекта, например, "Редактирование документа id=124".
- Тип сущности к которой относится привилегия entityType (по умолчанию установлено null). На какой объект направлена данная привилегия. Пример: InterfaceActivator.TypeOf<IMyObject>().
- Базовая привилегия @base (по умолчанию установлено null). Ссылка на базовую привилегию системы. Пример: @base: CommonPermissions.View.
Если Вам необходимо реализовать собственные привилегии для Вашего объекта, то при создании привилегии её тип будет PermissionType.Instance. Когда создается привилегия уровня Instance, то происходит поиск базовой привилегии View на данный объект, если базовая привилегия View не найдена, появится ошибка о том, что обязательна реализация базовой привилегии View.
Примечание: стоит отметить, что при загрузке нескольких объектов для проверки привилегий будет присоединена таблица с привилегиями, тогда как при загрузке одного объекта будет добавлено ко всему прочему дополнительное условие к выборке. Это необходимо учитывать при реализации привилегий с использованием базовых привилегий, так как от этого зависит общее быстродействие. - Отображение привилегии в глобальных настройках showInGlobalSettings (по умолчанию установлено true). Пример: showInGlobalSettings: false
- Привилегия только для чтения readOnly (по умолчанию false). Признак того, что для данной привилегии нельзя менять список её обладателей.
- Привилегии от которых зависит данная привилегия dependencies. Если роли назначается привилегия, то система автоматические добавляет все зависимые привилегии и наоборот, если у роли удаляется зависимая привилегия то и основная привилегия удаляется. Пример: привилегия "редактирование пользователей" зависит от "просмотр пользователей". Пример: dependencies: new[] {UserViewPermission}
Пример создания новой привилегии:
new Permission(ViewItemPermissionId, SR.M("Просмотр"), "", SR.M("Право на просмотр записи объекта"), permissionType: PermissionType.Instance, entityType: InterfaceActivator.TypeOf<IMyObject>(), @base: CommonPermissions.View);
Жизненные циклы работы привилегий.
Рис. 6. Работа привилегий при переходе в объект
Рис. 7. Работа привилегий в системе ELMA при создании новой записи объекта IMyObject
Рис. 8. Работа привилегий в системе ELMA при выдаче прав доступа
Пример реализации интерфейса IPermissionProvider
[Component] public sealed class ModulePermissions : IPermissionProvider { public const string Module = __ModuleInfo.ModuleId; public static string ModuleName = SR.M("Ваш модуль"); /// <summary> /// Идентификатор для "Доступ к Вашему модулю" /// </summary> public const string ModuleAccessPermissionId = "{184A1311-8CCF-49c6-A1C1-C11825232389}"; /// <summary> /// Идентификатор для "Права на просмотр" /// </summary> public const string ViewItemPermissionId = "{81E3A934-AB85-4970-8CF4-E38913BB6DCB}"; /// <summary> /// Идентификатор для "Права на редактирование" /// </summary> public const string EditItemPermissionId = "{702F4CFB-B1D4-4102-8F6A-4E36DBCED8E3}"; /// <summary> /// Идентификатор для админских прав на объект /// </summary> public const string AdminPermissionId = "{2D53DE58-A90D-4fc8-A937-AFEEFDB0AFB3}"; /// <summary> /// Доступ к Вашему модулю /// </summary> public static readonly Permission ModuleAccessPermission = new Permission(ModuleAccessPermissionId, SR.M("Доступ к Вашему модулю"), SR.M("Возможность работы с модулем"), ModuleName, moduleUid: Module //Можно добавить ,readOnly: true - тогда в данной привилегии будет только указанная группа. ); /// <summary> /// Права на просмотр /// </summary> public static readonly Permission ViewItemPermission = new Permission(ViewItemPermissionId, SR.M("Просмотр"), "", SR.M("Право на просмотр записи объекта"), permissionType: PermissionType.Instance, entityType: InterfaceActivator.TypeOf<IMyObject>(), @base: CommonPermissions.View); /// <summary> /// Права на редактирование /// </summary> public static readonly Permission EditItemPermission = new Permission(EditItemPermissionId, SR.M("Редактирование"), "", SR.M("Право на редактирование записи объекта"), permissionType: PermissionType.Instance, entityType: InterfaceActivator.TypeOf<IMyObject>(), @base: CommonPermissions.Edit); /// <summary> /// Полные права /// </summary> public static readonly Permission AdminPermission = new Permission(AdminPermissionId, SR.M("Права администратора на объект"), "", SR.M("Полные права на объект"), permissionType: PermissionType.Instance, entityType: InterfaceActivator.TypeOf<IMyObject>(), @base: CommonPermissions.AdminPermission); /// <summary> /// Получить список привилегий /// </summary> /// <returns></returns> public IEnumerable<Permission> GetPermissions() { return new[] { ViewItemPermission, EditItemPermission, ModuleAccessPermission, AdminPermission }; } /// <summary> /// Получить информацию о назначении привилегий ролям по умолчанию /// </summary> /// <returns></returns> public IEnumerable<PermissionStereotype> GetPermissionStereotypes() { return EmptyArray<PermissionStereotype>.Instance; } public List<string> LocalizedItemsNames { get { return null; } } public List<string> LocalizedItemsDescriptions { get { return null; } } public List<string> LocalizedItemsCategories { get { return null; } } }
Данные права доступа будут использованы в модуле и в дальнейших реализациях точек расширений. Как можно заметить, права доступа ViewItemPermission, EditItemPermission и AdminPermission используют базовые права доступа сущности, тогда как ModuleAccessPermission является правом доступа к модулю.
Пример реализации интерфейса IModuleAccessPermissionProvider
[Component] internal class ModuleAccessPermission : IModuleAccessPermissionProvider { public const string Module = __ModuleInfo.ModuleId; public Dictionary<string, Permission> GetModuleAccessPermissions() { return new Dictionary<string, Permission> { { Module, ModulePermissions.ModuleAccessPermission } }; } }
Привилегия ModulePermissions.ModuleAccessPermission будет отображаться в разделе Глобальные настройки доступа. Пример показан на рисунке 4. В данном примере представлена реализация доступа к модулю (отображается или скрывается соответствующий пункт меню в зависимости от прав доступа). Как создать свой пункт меню, Вы можете прочитать в данной статье.
После активации модуля в глобальных настройках доступа появится раздел с Вашими привилегиями. Для применения привилегий можно воспользоваться двумя способами:
- Навесить атрибут с привилегией на контроллер или метод контроллера.
- Проверка привилегий в отдельных методах.
Пример кода контроллера с атрибутом:
public class HomeController : BPMController { [ContentItem] [Permission(ModuleAccessPermission.ModuleAccessPermissionId)] public ActionResult ViewItem() { return View("View"); } } Пример проверки привилегий в методах/представлениях: if (SecurityService.HasPermission(UserManager.Instance.GetCurrentUser(), ModuleAccessPermission.ModulePermissionAccess)) { //Ваш код }
Пример реализации базового класса IInstanceSettingsPermission
[Component] internal class InstanceSettingsPermission : InstanceSettingsPermissionBase<IMyObject, IMyObjectPermissions> { public InstanceSettingsPermission() : base(access => access.MyObject) // выражение со ссылкой на сущность { } protected override ICollection<IMyObjectPermissions> GetPermissionHolderCollection(IMyObject target) { return target.Permissions; // коллекция для хранения настроек привилегий } }
Для реализации настроек необходим сам объект IMyObject, а также объект для хранения настроек IMyObjectPermissions. Структура объекта IMyObject:
Рис. 9. Структура объекта IMyObject
Рис. 10. Структура объекта с настройками
На вкладке Дополнительно необходимо указать, что данный объект реализует интерфейс EleWise.ELMA.Security.Services.ISecuritySetIdHolder. Также необходимо установить флажок Дополнительный код. Необходимость реализации дополнительного кода обусловлена тем, что необходима реализация интерфейса ISecuritySetIdHolder.
Свойства интерфейса ISecuritySetIdHolder:
/// <summary> /// Обладатель привилегии (пользователь, группа, эл-т организационной структуры и пр.) /// </summary> IEntity Assigned { get; set; } /// <summary> /// Роль обладателя привилегии, это может быть автор, инициатор процесса, группа пользователей и пр. /// </summary> Guid TypeRoleId { get; set; } /// <summary> /// Идентификатор привилегии /// </summary> Guid PermissionId { get; set; } /// <summary> /// Предмет привилегии ( то на что предоставлена привилегия) /// </summary> object Target { get; set; }
Дополнительный код для объекта с настройками:
using System; using EleWise.ELMA.Model.Entities; using EleWise.ELMA.Security.Models; using EleWise.ELMA.Security.Services; namespace ModuleAccessPermissionProvider.Models { public partial class MyModuleSettings { public virtual IEntity Assigned { get //Получаем значение свойства в зависимости от обладателя привилегии { if (User != null) return User; if (Group != null) return Group; if (OrganizationItem != null) return OrganizationItem; if (this.OrganizationItemEmployee != null) return OrganizationItemEmployee; return null; } set //Определяем тип значения и задаем его в созданное свойство обладателя привилегии { if (value is User) User = (User)value; else if (value is UserGroup) Group = (UserGroup)value; else if (value is OrganizationItem) { var organizationItem = (OrganizationItem)value; if (organizationItem.ItemType == OrganizationItemType.Position) OrganizationItem = organizationItem; else OrganizationItemEmployee = organizationItem; } else throw new ArgumentException(value.ToString()); } } public virtual object Target { get { return MyObject; } // Получаем значение сущности, на которую предоставлена привилегия set { MyObject = (MyObject)value; } // Задаем значение свойства с приведением к типу } public virtual Guid TypeRoleId { get { return PermissionRole; } // Получаем значение set { PermissionRole = value; } // Задаем значение свойства } } }
Пример реализации базового класса IPermissionsDelegate
Для доступа к экземпляру указанного типа (IMyObject) необходима реализация данной точки расширения. Если не реализовать данную точку расширения, то при входе в справочник пользователь получит ошибку о том, что необходимо заделегировать привилегии.
[Component] internal class MyObjectPermissionsDelegate : IPermissionsDelegate { public ISecurityService SecurityService { get { return Locator.GetService<ISecurityService>(); } } public InstanceSettingsPermissionsDelegate DefaultInstanceSettingsPermissionsDelegate { get; set; } private readonly Permission[] _permissions = new[] { ModulePermissions.ViewItemPermission, ModulePermissions.EditItemPermission }; public bool CanCheckPermissions(Type type, Permission permission) { return typeof(IMyObject).IsAssignableFrom(type) && _permissions.Contains(permission); } public bool HasPermission(IUser user, Permission permission, object target, bool skipAdmin = false) { var obj = target as IMyObject; if (permission == null || obj == null) return false; if (permission.Id == ModulePermissions.ViewItemPermission.Id) { return DefaultInstanceSettingsPermissionsDelegate.HasPermission(user, permission, target); } if (permission.Id == ModulePermissions.EditItemPermission.Id) { return DefaultInstanceSettingsPermissionsDelegate.HasPermission(user, permission, target); } return false; } }
Пример реализации базового класса IEntityInstanceDefaultPermission
[Component] internal class DefaultPermission : IEntityInstanceDefaultPermission { public Type EntityType { get { return typeof(IMyObject); } } /// <summary> /// Создать привилегии /// </summary> /// <param name="entity">Сущность</param> public void CreatePermissions(IEntity entity) { var obj = (IMyObject) entity; if (obj.Permissions.Count == 0) { var h = new PermissionHelper(obj); // Права доступа к объекту h.AddPermission(ModulePermissions.ViewItemPermissionId, CommonRoleTypes.Author); h.AddPermission(ModulePermissions.EditItemPermissionId, CommonRoleTypes.Author); } } private class PermissionHelper { public PermissionHelper(IMyObject myObject) { this.myObject = myObject; } public void AddPermission(string permissionId, PermissionRoleType role) { var item = new InstanceOf<IMyObjectPermissions> { New = { PermissionRole = role.UID, MyObject = myObject, PermissionId = new Guid(permissionId) } }.New; if (role == CommonRoleTypes.Author) { item.User = myObject.CreationAuthor; item.TypeRoleId = CommonRoleTypes.Author.Id; } if (role == RoleType.AllUsers) { item.Group = UserGroupManager.Instance.Load(SecurityConstants.AllUsersGroupUid); item.TypeRoleId = RoleType.AllUsers.Id; } myObject.Permissions.Add(item); } private IMyObject myObject; } }
При создании объекта в него будут добавлены права по умолчанию. Автор создания получит права доступа на просмотр и редактирование объекта.
Пример реализации базового класса IPermissionRoleTypeProvider
[Component] internal class RoleType : IPermissionRoleTypeProvider { /// <summary> /// Все пользователи /// </summary> public static readonly PermissionRoleType ObjectRole = new PermissionRoleType(new Guid("58DEC298-AB48-468a-9A59-E245826A4B1F"), "Роль Вашего объекта", InterfaceActivator.TypeOf<IUserGroup>(), "#x16/User.png", "javascript:showUserGroupInfo({0});"); public IEnumerable<PermissionRoleTypeStereotype> GetTypePermissionRoleStereotypes() { var organizationItem = CommonRoleTypes.OrganizationItem; var group = CommonRoleTypes.Group; var user = CommonRoleTypes.User; var author = CommonRoleTypes.Author; var allTypes = new[] { ObjectRole, group, organizationItem, user, author, user }; return new[] { new PermissionRoleTypeStereotype(InterfaceActivator.TypeOf<IMyObject>(), ModulePermissions.ViewItemPermission, allTypes), new PermissionRoleTypeStereotype(InterfaceActivator.TypeOf<IMyObject>(), ModulePermissions.EditItemPermission, allTypes), new PermissionRoleTypeStereotype(InterfaceActivator.TypeOf<IMyObject>(), ModulePermissions.AdminPermission, new [] {author}) }; } public IEnumerable<PermissionRoleType> GetRoleTypes() { return new[] { ObjectRole }; } public List<string> LocalizedItemNames() { return new List<string>() { SR.T("Роль Вашего объекта") }; } }
Как можно заметить, в примере используются существующие системные роли, а также создана своя собственная роль. Пример отображения представлен на рисунке 3. Для каждой из привилегий (просмотр, редактирование, права администратора) определены роли, которые можно выбрать для конкретной привилегии. Например, для привилегии "просмотр" можно выбрать все 5 ролей: все пользователи, группа, оргструктура, пользователь и автор, тогда как для привилегии "Администратор объекта" можно выбрать только автора создания.
Чтобы была возможность назначать права доступа, нужно реализовать её в веб-части Вашего модуля. В данном примере это реализовано при помощи действия в контроллере. В данном примере была реализована карточка объекта, в тулбаре которой находится две кнопки: Назад и Настройки. Код тулбара:
@(Html.Toolbar() .Group("tb-group1") .Button(b => b .Uid("toolbar-action-Back") .Text(SR.Back) .IconUrl("#x32/Prev.png") .Click("javascript:history.back(-1);") ) .Button(b => b .Uid("toolbar-action-Settings") .Text(SR.T("Настройки")) .IconUrl("#x32/settings.png") .Url(Url.Action("EntityPermissionSettings", "PermissionManagment", new { area = "EleWise.ELMA.BPM.Web.Security", id = Model.Id, type = EleWise.ELMA.Model.Services.InterfaceActivator.TypeOf<IMyObject>().AssemblyQualifiedName })) ) )
При нажатии на кнопку пользователю будут показаны права доступа, представленные на рисунке 2.
Ссылки на элементы API
- IPermissionProvider (для версий 3.13, 3.15, 4.0);
- IModuleAccessPermissionProvider (для версий 3.13, 3.15, 4.0);
- IInstanceSettingsPermission (для версий 3.13, 3.15, 4.0);
- IPermissionsDelegate (для версий 3.13, 3.15, 4.0);
- IEntityInstanceDefaultPermission (для версий 3.13, 3.15, 4.0);
- IPermissionRoleTypeProvider (для версий 3.13, 3.15, 4.0).
Исходники данного примера см. во вложении.