Динамическая безопасность на уровне строк в Power BI с организационной иерархией и несколькими позициями в отношениях «многие ко многим» - часть 1
Ранее мы писали о динамической защите на уровне строк и некоторых ее схемах. Две из наиболее распространенных описанных нами моделей - Организационная иерархия и ситуации “многие ко многим” в отношениях пользователей и профилей. Есть еще один интересный шаблон динамического RLS: когда у нас есть несколько позиций для некоторых пользователей в компании, и каждая позиция является частью организационной иерархии. Когда пользователь входит в систему, мы хотим, чтобы он или она видел информацию обо всех его или ее позициях, а также дерево позиций в его или ее организационной иерархии. Не думайте, что такая ситуация возникает нечасто: вы уже знаете некоторых людей в вашей компании, которые занимают более одной должности и имеют более одного менеджера. Итак, давайте посмотрим, как это возможно.
Необходимое условие
Образец набора данных для этого примера можно скачать здесь.
Полезные материалы, связанные с этой статьей
Мы написали несколько статей о безопасности на уровне строк в Power BI, советуем вам прочитать их:
Безопасность на уровне строк в Power BI
Динамическая безопасность на уровне строк
Динамическая безопасность на уровне строк с доступом на уровне менеджера
Динамическая безопасность на уровне строк с пользователями и профилями
Динамическая безопасность на уровне строк с организационной иерархией
Безопасность на уровне строк с подключением служб Analysis Services в реальном времени
Объяснение сценария
Каждая организация имеет иерархию сотрудников в зависимости от их роли. Вот пример такой иерархии:
Однако иерархия не всегда так проста. Иногда (мы можем даже сказать, в большинстве организаций) есть некоторые люди, которые выполняют несколько ролей. иногда, потому что они выполняли другую роль в то время, пока приходит замена. Таким образом, ситуация будет выглядеть так, у нас будет таблица User, как показано ниже:
У нас также есть таблица для позиций и организационной иерархии, как показано ниже, таблица Organization :
Столбец идентификатора менеджера в каждой строке представляет собой ссылку на поле идентификатора другой записи в той же таблице, представляющей организационную иерархию. В действии иерархия организации выглядит следующим образом:
Наши транзакции связаны с таблицей организации. Предположим, у нас есть записи о продажах, связанные с каждой ролью: таблица Sales Transaction:
Как видите, транзакции связаны с идентификаторами организации, а не с пользователями, потому что каждый пользователь может быть частью двух организаций (несколько должностей) или одной организации (должности) может быть назначено несколько пользователей.
Последняя таблица - это таблица, которая создает отношение «многие ко многим» между пользователями и организациями (должности), таблица User Organization:
Вот модель данных с отношениями:
Образец
Если, например, в этом наборе данных, Reza Logins, его идентификатор пользователя равен 2, что связано с ролью финансового директора организации, и он должен видеть не только транзакции, связанные с финансовым директором, но также и другие роли под ним (финансовое руководство, бухгалтерское руководство и так далее).
Если Джек входит в систему, у него две организационные позиции (главный бухгалтер и бухгалтер), и он должен видеть транзакции, связанные с обеими ролями, а также все остальное в иерархии под этими ролями.
Проблема
Динамическая безопасность на уровне строк означает, что мы получаем имя пользователя, вошедшего в систему с помощью функции, такой как UserPrincipalName () или UserName () в DAX, а затем фильтруем таблицы на основе этого. Теперь, когда вы знаете сценарий выше, давайте поговорим о том, с какой проблемой мы сталкиваемся в этой реализации для безопасности.
Если мы реализуем динамическую фильтрацию RLS в пользовательской таблице; когда такой пользователь, как Reza, входит в систему, эта таблица будет отфильтрована, и в ней будет только запись Reza, в результате таблица организации пользователя также будет отфильтрована, и в нее войдут только организации, частью которых является Reza. Однако из-за однонаправленной связи между таблицей организации пользователя и таблицей организации фильтр не пройдет через остальную часть модели. В результате этот пользователь увидит все организации и все транзакции независимо от RLS, реализованного в пользовательской таблице! Следующая диаграмма отображает эту ситуацию.
Отношение «многие ко многим» в модели приведет к необходимости двусторонних отношений, что не рекомендуется.
Если мы поменяем отношения на двунаправленные, мы получим еще одну проблему; Реза входит в систему, он связан с организацией CFO, поэтому будет видеть только CFO (поскольку двусторонние отношения проходят через фильтр) и будет видеть только транзакции CFO. Это не то, что мы хотим для этого требования.
Реза должен быть в состоянии видеть транзакции главы по финансам и главного бухгалтера и другие роли под ним. Отношения в обоих направлениях к таблице организации будут фильтровать таблицу организации только для позиций текущего пользователя.
Отношения в обоих направлениях и организационная иерархия не очень хорошо работают друг с другом, так как нам нужно увидеть все дерево организации под ним.
Решение
Отношения в обоих направлениях не являются решением, поэтому мы изменяем модель на однонаправленную:
Теперь, когда вы знаете проблему, давайте посмотрим, как ее исправить. Целью динамической безопасности на уровне строк является сфера DAX, и ничто не помешает вам достичь вам желаемого. Ваши способности ограничены только вашими навыками DAX. Эту проблему можно решить и с помощью DAX. Тем не менее, выражение будет немного длинным. Чтобы было легче понять, мы разбили его на части и пройдемся по нему шаг за шагом;
Целью динамической безопасности на уровне строк является сфера DAX, и ничто не помешает вам достичь того, чего вы хотите. Ваши способности ограничены только вашими навыками DAX.
Шаг 1: Получите адрес электронной почты зарегистрированного пользователя
Давайте начнем с получения имени пользователя; следующее измерение показывает зарегистрированное имя пользователя: Использование функции DAX UserPrincipalName ();
1 |
Measure = USERPRINCIPALNAME() |
Шаг 2: Получите идентификатор текущего пользователя
Теперь, в качестве второго шага, нам нужно выяснить, какой идентификатор в таблице User зарегистрирован для пользователя, что достижимо с помощью DAX-функции LookupValue ();
1 2 3 4 5 6 |
Measure = LOOKUPVALUE( Users[ID], Users[Email], USERPRINCIPALNAME() ) |
Функция LookupValue имеет три параметра: столбец, для которого мы хотим получить его значение в качестве выходного (ID), столбец, в котором мы ищем значение в нем (электронная почта), и само значение (полученное из функции UserPrincipalName ()) , В результате это показывает идентификатор пользователя, вошедшего в систему;
Шаг 3: Получите все идентификаторы организации, связанные с текущим пользователем
В качестве третьего шага нам нужно получить все таблицы идентификаторов организации (из организации пользователя), которые связаны с текущим пользователем. Это означает фильтрацию таблицы User Organization, в которой поле User ID совпадает с выводом предыдущего шага. Мы можем добиться этого, используя функцию Filter () в DAX:
1 2 3 4 5 6 7 8 9 10 |
Measure = FILTER( 'Users Organizations', 'Users Organizations'[User ID]= LOOKUPVALUE( Users[ID], Users[Email], USERPRINCIPALNAME() ) ) |
Функция фильтра получает таблицу в качестве входных данных (User Organization), а затем критерий фильтрации, который будет идентификатором пользователя, равен выходным данным предыдущего шага расчета.
Это дает нам все строки в таблице User Organization, у которых их идентификатор пользователя равен идентификатору текущего пользователя. Однако, поскольку выходные данные функции Filter представляют собой таблицу, вы не можете отобразить ее в показателе, поэтому мы получаем ошибку выше. На этом этапе мы все еще находимся в середине нашего пути к окончательному расчету. Тем не менее, если вы хотите увидеть, как это все получается, вы можете использовать следующий метод :
Метод ConcatenateX покажет некоторые значения в таблице
Этот шаг не является частью всего выражения. Он просто для того, чтобы показать вам, как мы можем получить список идентификаторов организации из текущей таблицы, выведенной функцией Filter, используя ConcatenateX (вы можете пропустить этот шаг)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Measure = CONCATENATEX( FILTER( 'Users Organizations', 'Users Organizations'[User ID]= LOOKUPVALUE( Users[ID], Users[Email], USERPRINCIPALNAME() ) ), 'Users Organizations'[Organization ID], ",") |
ConcatenateX получает таблицу в качестве входных данных (выходные данные предыдущего шага), затем выражение, которое мы хотим объединить (столбец идентификатора организации), и текст, который мы хотим добавить между каждыми двумя выражениями для объединения («,» как разделитель запятой).
В качестве примера, если Лейла вошла в систему:
Ее идентификатор пользователя равен 1, и у нее есть две строки организации, связанные с ее учетной записью, что будет выводом нашего последнего вычисления:
Следующие шаги
Остальная часть процесса будет описана в следующем посте.