Итак, вам требуется создать какие-то собственные разрешения в SharePoint, которые бы имели смысл с точки зрения бизнес-логики. Существует много общеиспользуемых способов сделать это. Примеры:
- Создать группу SharePoint, и попросить заказчика добавлять в эту группу соответствующих людей. В коде проверять вхождение текущего пользователя в эту группу.
Минусов немало: группу могут удалить по незнанию - следовательно придется писать код для ее пересоздания; в код приходится либо хардкодить заголовок группы (что неприемлимо с локализацией), либо сохранять ID группы куда-нибудь после создания группы - опять лишний код; группу нельзя проверить из XSLT и из стандартных веб-частей, таких как SPSecurityTrimmedControl; группу нельзя прописать в Rights для CustomAction - т.е. придется изобретать кучу javascript чтобы проверить права и т.д. - Хранить права пользователя в свойстве его профиля, и в коде проверять наличие этого свойства и его значение.
Минусов еще больше. Во-первых, обращение к UserProfileService по традиции занимает много времени и поэтому в этом случае есть риски напороться на проблемы с производительностью и потом долго рефакторить код их решая. Во-вторых, менять бизнес-роли пользователя в этом случае можно только через Central Administration и таким образом для всего тенанта/фермы, т.е. нельзя назначить эти права на сайт/список/элемент списка и т.п. (или же придется сериализовать целыми объектами и писать отдельный GUI - что займет кучу времени) - Хранить права пользователя в списках или в каких-нибудь PropertyBag'ах или в SPPersistentObject. Минусы в целом все такие же: требуется писать код, чтобы это работало.
Как правильно
Идеальный вариант управления правами доступа в SharePoint - это использование стандартных разрешений.
Т.е. вы не создаете никаких кастомных разрешений совсем, а стараетесь все сделать на уровне SharePoint. В этом случае трюк состоит в том, чтобы "замэпить" SharePoint-объекты на бизнес-объекты.
Например, давайте представим что мы создаем портал для проектной деятельности по методологии SCRUM. С точки зрения SCRUM, существуют следующие объекты:
- Проект
- Бэклог проекта
- Спринт
- Бэклог спринта
- Команда
- Скрам-мастер
- Члены команды
Например, один из вариантов может быть следующий:
- Проект - это коллекция узлов
- Бэклог проекта - это список в корневом сайте коллекции
- Спринт - это подсайт
- Бэклог спринта - это список на подсайте
- Команда - это группа SharePoint
- SCRUM-мастер - это член группы "Scrum-мастеры" (одновременно входит в одну или несколько команд)
- Scrum-мастер может создавать сайты, и единственный доступный шаблон это шаблон спринта. Следовательно Scrum-мастер может создать сайт спринта и автоматически получит права владельца на него.
- В onet.xml сайта можно добавить фичу, которая при активации автоматически выдаст права на этот сайт команде, в которой состоит владелец сайта - хотя можно даже и без этого: Scrum-мастер легко сможет определить права на свой сайт вручную, это недолго и всего раз в 1-2 недели.
Но всё-таки, прежде чем двигаться дальше, остановитесь и подумайте как следует - ведь вариантов довольно много.
Распространенный пример - Field Level Security. SharePoint не предлагает готового пути решения этой проблемы, но в некоторых частных случаях ее решить довольно легко. Это делается путем выноса тех полей, доступ к которым нужно ограничить, в отдельный список. Дальше мы можем назначить на этот список отдельные разрешения, с добавлением lookup-поля ссылающегося на исходный список. При отображении объединить списки не проблема: можно либо добавить в представление исходного списка ссылку на соответствующий элемент дополнительного списка, или же объединить формы просмотра элементов этих двух списков, или даже вытянуть "секретные" поля в представление исходного списка с помощью lookup-ов и т.п.
Напомню прописную истину - НЕЛЬЗЯ делать security только путем кастомизации представления списка или формы списка (т.е. через javascript, XSLT или ListFieldIterator). Это частая ошибка, и если не повезет она может вам стоить вашего места работы. Помните, всегда есть возможность достать содержимое списка через Client Object Model или веб-сервисы. Это 10 строк JavaScript!...
Чтобы уметь "вписываться" в стандартные разрешения SharePoint, нужно знать их наизусть и знать какие разрешения за что отвечают. Тщательно изучите SPBasePermissions, и тщательно изучите разрешения которые можно задавать на Service Applications (UPS, BCS и т.п.).
Кастомные разрешения
Бывает, когда вариант с мэппингом совсем не подходит. В этом случае приходиться вводить в систему уникальные разрешения.
К примеру, у вас есть список с клиентами вашей компании, и вы хотите сделать возможность отсылать им SMS (кстати, если вдруг кто-то не видел, у меня есть интересная статья про SMS в SharePoint), но естественно эта функция должна быть включена только у строго определенных пользователей, поскольку не бесплатно :) Более того, для VIP-клиентов и международных клиентов должны быть вообще назначены уникальные разрешения на отсылку SMS.
Конечно, даже в этом случае можно извернуться и создать отдельный список с отдельными правами и одной колонкой "Отправить SMS" и попробовать протащить ее лукапом в исходный, но чересчур "извращаться" на самом деле тоже плохая идея - в итоге у вас получится хрупкое решение, которое будет неудобно поддерживать при смене требований. Старайтесь всегда выбирать решение которое наиболее подходит к задаче.
В общем, в этом случае я бы ввел отдельное разрешение "Отсылка SMS".
Есть два хороших способа делать "простые" кастомные разрешения в SharePoint:
- Создать пустую нередактируемую роль
- Создать нередактируемую роль с нестандартным base permission
Пустая роль
Первый вариант - пустая нередактируемая роль.
"Нередактируемость", кстати, условие необязательное, но желательное. Это как с полями - забудете поставить Sealed у поля, и его кто-нибудь обязательно поменяет и в итоге либо придется его все-таки делать Sealed, либо вставлять кучу проверок. А каждая проверка - это строчка кода, которую придется поддерживать и в которой придется отлавливать баги... Так что, "нередактируемая"! :)
Чтобы создать нередактируемую роль, к сожалению, придется воспользоваться Reflection :( Код следующий:
var requestProperty = typeof(SPWeb).GetProperty("Request", BindingFlags.NonPublic | BindingFlags.Instance); var spRequest = requestProperty.GetValue(rootWeb, null); var addRoleDefMethod = spRequest.GetType().GetMethod(“AddRoleDef”); addRoleDefMethod.Invoke(spRequest, new object[] { rootWeb.Url, "Test Role", "Test role description", false, 1000, (ulong)0, (byte)1, /* Use 1 or 5 to disable editing */ 0 });
Аналогичный Powershell-код:
$w = get-spweb http://localhost $requestProperty = $w.GetType().GetProperty("Request", [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance); $request = $requestProperty.GetValue($w, $null) $addRoleMethod = $request.GetType().GetMethod("AddRoleDef") $addRoleMethod.Invoke($request, @($w.Url, "Test Role", "Test role description", $false, 1000, [system.uint64]0, [system.byte]1, 0))
Удалять тоже через Reflection, только через метод RemoveRoleDef(string webUrl, int roleId).
В целом идея в том, чтобы просто создать роль и потом проверять ее наличие у пользователя программно из всех мест где это требуется. Пример для прав SPWeb:
public bool IsUserInRoleForWeb(SPUser user, SPWeb web, string roleName) { var info = web.GetUserEffectivePermissionInfo(user.LoginName); var roleDefinition = web.RoleDefinitions[roleName]; foreach (SPRoleAssignment roleAssignment in info.RoleAssignments) { if (roleAssignment.RoleDefinitionBindings.Contains(roleDefinition)) return true; } return false; }
Проверку роли можно осуществлять в том числе через Client Object Model.
Преимущества подхода по сравнению с методами, озвученными вначале статьи:
- У пользователя уже есть готовый GUI, чтобы добавить вашу роль в одну или более групп на сайте и включить нужных ему пользователей в эти группы.
- Можно присваивать роль напрямую, на уровне сайта, списка, элемента списка. Проверять можно также отовсюду: например, из SPD Workflow (см. скриншот). Все гибко и удобно, и идеально вписывается в SharePoint.
- Роль не удаляемая.
- Почти не придется писать код (и следовательно - поддерживать его потом).
Роль с custom base permission
Этот вариант основан на статье Hristo Pavlov.
Я уже вскользь упоминал выше, что стандартные права SharePoint реализованы перечислением SPBasePermissions (флаговый enum). Как известно, перечисления нельзя редактировать, но по большому счету это не более чем обертка для чисел (short/int/long), и что интересно, любое число можно привести к любому перечислению, даже если в этом перечислении не задан элемент, соответствующий этому числу.
Это означает, что в некотором ограниченном виде мы можем использовать незанятые значения SPBasePermissions в собственных целях.
Конечно, идеально было бы дать пользователю самому создавать роль. К сожалению, интерфейс, который реализует редактирование роли, совершенно не расширяем. Как вы знаете, на странице изменения/создания роли разрешения объединены в группы, снабжены подробными описаниями и даже имеют зависимости друг от друга. Как это реализовано? Если интересно, поисследуйте на досуге классы CBaseRole и CPerms из сборки Microsoft.SharePoint.ApplicationPages.dll и файлик 15/TEMPLATE/LAYOUTS/EditRole.aspx. Самый страшный говнокод который вы видели вам покажется идеальной абстракцией :))
Так что если давать редактировать роли - это нужно будет переписать весь SharePoint'овский интерфейс, и переопределить поведение кнопки "Ribbon.Permission.Manage.PermLevels". С другой стороны, можно создать опять же нередактируемую роль и включить в нее наш custom base permission - в этом случае никаких интерфейсов писать не придется, и решение получается не менее гибким и не менее удобным.
Таким образом, из кода теперь можно использовать проверки DoesUserHavePermissions и т.д., следующим образом:
(web as ISecurableObject).DoesUserHavePermissions((SPBasePermissions)CustomPermissions.CanSendSMS)
Плюс получаем еще несколько интересных возможностей, которых лишен первый вариант:
- Можно использовать функцию ddwrt:HasRights в XSLT-коде в OOTB-вебчастях DFWP, XLV и т.д. (хотя не забывайте что нельзя на нее всецело полагаться, т.е. использовать надо только в сочетании с действительными запретами в серверном коде)
- Можно использовать SPSecurityTrimmedControl с нашим кастомным разрешением (заводить его в PermissionString в виде числа).
- Можно задавать права для CustomAction'ов (атрибут Rights) - включая пункты меню, ссылки на страницах Site Settings и List Settings, элементы на риббоне, добавляемые элементы ECB, и т.д.
В целом, этот вариант представляется наиболее близким к идеальныму. Единственная проблема - это то что перечисление SPBasePermissions вполне может измениться, и со времен 2007го SharePoint'а в него действительно было добавлено 3 новых пункта (вот вам кстати квест - найти, какие :) ). Кроме того, нельзя забывать, что другие люди тоже могут тоже захотеть использовать SPBasePermissions в своих решениях... Так что некоторый риск безусловно присутствует.
Заключение
SharePoint хорош тем, что позволяет создавать решения быстро. Но для этого нужно очень хорошо знать функционал SharePoint'а и уметь его правильно и по назначению использовать. В случае прав доступа, рекомендую прежде всего постараться обойтись совсем без кастомных разрешений. В крайнем случае используйте нередактируемые роли - они очень гибкие и весь GUI для управления ими в SharePoint уже присутствует.
В общем, больше думайте, меньше пишите кода. Удачи!
P.S. Спасибо Стасу Выщепану за интересную беседу которая у нас была по теме этой статьи.
Понравилось: "Если интересно, поисследуйте на досуге классы CBaseRole и CPerms из сборки Microsoft.SharePoint.ApplicationPages.dll и файлик 15/TEMPLATE/LAYOUTS/EditRole.aspx. Самый страшный говнокод который вы видели вам покажется идеальной абстракцией :))"
ОтветитьУдалитьда там ужас :(
УдалитьДа, частенько приходится смотреть рефлектором, и я прекрасно понимаю какой там говнокод. Мы внутри команды, просто воспринимает это как "Индусскую обфускацию" :)
УдалитьСтоило бы ещё упомянуть об ограничении на количество уникальных разрешений (security scope). В SharePoint 2013 по умолчанию стоит ограничение в 1000 областей безопасности на список. Конечно, этот порог можно поднять, но тогда Майкрософт не гарантирует, что не начнутся дикие тормоза при работе со списком, и, собственно, люди действительно с такими дикими тормозами сталкиваются на практике. Это печально, потому что порог в 1000 областей превышается довольно легко.
ОтветитьУдалитьСогласен. Уникальные разрешения в SharePoint я бы использовал только в исключительных случаях, когда иначе совсем никак - они реально тормозят на больших объемах данных и порой даже приводят к фатальным "блокировкам", когда тот или иной список становится вовсе невозможно использовать.
УдалитьНужно стараться по возможности уникальные разрешения разложить в группы, под группы создать папки (или отдельные библиотеки) и уже на папки назначать уникальные права. Т.е. группировать.
Например в SharePoint Server есть фича "Content Organizer", которая может на основе метаданных автоматически переносить загружаемые на портал документы в соответствующую папку в библиотеке документов, и на эти папки уже можно выставлять права - но не на отдельные документы. Я недавно использовал решение на основе Content Organizer для избавления от уникальных разрешений. В SharePoint Foundation наверное что-то похожее несложно сделать с помощью Event Receiver'ов, возможно в сочетании с Work Item Timer Job'ами.
Насчет уникальных разрешений.
Удалитьhttp://blog.itayasaservice.com/2012/09/01/the-unique-security-scopes-per-list-limit/
Я видел в списки с 3000 элементов с уникальными разрешениями. Работает, но медленно.
Проводил тесты - если есть 50,000 уникальных разрешений, то 50,001 уже не получается прервать наследование, падает с ошибкой.
Насчёт уникальных разрешений:
Удалитьhttp://www.martinhatch.com/2011/10/scaling-to-10000-unique-permissions.html
Во второй части описывается, как можно обойти это ограничение.
Спасибо, очень интересно, надо будет попробовать так сделать.
УдалитьАндрей, так и не понял как правильно реализовать Field Level Security. Создавая роль мне все равно придется везде проверять кодом есть ли у пользователя право видеть field. А как быть с веб-сервисами?
ОтветитьУдалитьДенис, как я выше описал, на мой взгляд наиболее правильный способ реализации Field Level Security - это вынос полей, к которым должен быть запрещен доступ, в другой список/списки. И потом настройка прав на уровне списков.
УдалитьРолью реализовать можно тоже, да, но только в том случае, если запрещать доступ к списку для всех пользователей, и отображать его внутренности кастомной веб-частью через RunWithElevatedPrivileges. Т.е. в этом случае отображение придется писать "с нуля".
Денис, если поля надо только отображать или прятать , то я бы сделал формой InfoPath ну максимум 4 часа.
УдалитьЕсли данные в полях надо именно обезопасить, т.е. предотвратить раскрытие информации через подписки или поиск и т.п., то как советует Андрей через отдельный список и назначение прав.
Мы на практике мержим оба подхода: InfoPath и управляемый вывод, + запер поиска и заперт подписок (если это допустимо), и отдельный список где раздаем права. выглядит все как: карточка (чего нибуть) в InfoPath на своей закладке, и на отдельной закладке список (или карточка) секьюрных данных.
А вообще шарик это система совместной работы и в рамках совместной работы как бы друг от друга особо прятать не чего, а если есть чего, например бюджеты от разработчиков, то ведите эту бухгалтерию на отдельном узле.
Коллеги добрый день.
ОтветитьУдалитьКак все-таки лучше решить задачу отображения записей списка для разных пользователей ?
Кейс такой:
- есть 100 пользователей (внешних - авторизация Forms)
- есть список 100 тыс записей
- в списке есть поле с типом "Пользователь"
Нужно:
- Чтобы пользователю (внешнему) возвращались только те записи списка, которые ему принадлежат по правилу значение поля = авторизованный пользователь. Причем переделывать все отображение не хотелось бы.
Заранее спасибо за ответ.
С Уважением, Дмитрий