Динамическая безопасность на уровне строк в Power BI с организационной иерархией и несколькими позициями в отношениях «многие ко многим» - часть 2

Tags: Power BI, PowerBI, RLS

В предыдущем посте вы узнали о проблеме безопасности при ее реализации с использованием сценария с несколькими позициями в сочетании с организационной иерархией. Мы начали внедрять решение с использованием DAX для обеспечения безопасности на уровне строк. Этот пост является второй частью решения.

Необходимое условие

Образец набора данных для этого примера можно скачать здесь.

Первая часть этой статьи здесь.

Полезные материалы, связанные с этой статьей

Мы написали несколько статей о безопасности на уровне строк в Power BI, советуем вам прочитать их:

Безопасность на уровне строк в Power BI

Динамическая безопасность на уровне строк

Динамическая безопасность на уровне строк с доступом на уровне менеджера

Динамическая безопасность на уровне строк с пользователями и профилями

Динамическая безопасность на уровне строк с организационной иерархией

Безопасность на уровне строк с подключением служб Analysis Services в реальном времени

Продолжая решение

Расширение иерархии

Нам нужно расширить иерархию организации, чтобы иметь возможность искать пользователя через нее. Мы можем использовать функцию Path () DAX для этого. Ниже приведен расчетный столбец, добавленный в таблицу организации:

1

2

3

4

Path = PATH(

    Organization[ID],

    Organization[Manager ID]

    )

Функция Path принимает два параметра: Столбец ID (столбец ID в таблице Organization) и столбец родительского идентификатора (Manager ID). в результате у нас будет строка значений, разделенных «|», показывающая всю иерархию для этого члена организации.

Поиск идентификатора пользователя организации в иерархии организации

Теперь, когда мы знаем, какие идентификаторы организации связаны с пользователем, а также иерархию организации, нам нужно искать совпадения в каждой строке.

Реализация цикла

Поскольку весь процесс должен быть динамическим в зависимости от пользователя, вошедшего в систему, мы не можем осуществить это как вычисляемый столбец в таблице Organization, мы должны сделать это в нашем расчете измерения (продолжение расчета из предыдущей части этой статьи):

 

1

2

3

4

5

6

7

8

9

10

11

12

Measure =

CROSSJOIN(

    FILTER(

            'Users Organizations',

            'Users Organizations'[User ID]=

                LOOKUPVALUE(

                    Users[ID],

                    Users[Email],

                    USERPRINCIPALNAME()

                )

        ),

    Organization)


Как вы могли заметить, мы удалили ConcatenateX, потому что он нужен только для того, чтобы показать представление данных в таблице, и провели CrossJoin () двух таблиц. CrossJoin получает имя таблицы и создает декартово произведение строк в обеих таблицах. Для каждой строки в таблице Organization у нас будут все строки, полученные в результате функции Filter (организации, связанные с текущим пользователем).

Мы просто поместили приведенное выше выражение в другой ConcatenateX, чтобы показать там значения:

 

Проверяем, содержит ли путь идентификатор организации текущего пользователя

Теперь, когда мы создали структуру цикла с помощью функции CrossJoin, мы можем добавить столбец в эту виртуальную таблицу и посмотреть, содержит ли столбец Path в каждой строке идентификатор организации текущего пользователя или нет, мы можем сделать это с помощью функции AddColumn () в DAX. :

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

Measure =

ADDCOLUMNS(

    CROSSJOIN(

        FILTER(

                'Users Organizations',

                'Users Organizations'[User ID]=

                    LOOKUPVALUE(

                        Users[ID],

                        Users[Email],

                        USERPRINCIPALNAME()

                    )

            ),

        Organization),

    "Path Contains This Organization",

    PATHCONTAINS(

        Organization[Path],

        [Organization ID])

    )


AddColumns добавит столбец к существующей таблице, используя три параметра: таблицу для добавления столбца к нему (выходная таблица из шага CrossJoin), имя нового столбца (“Path Contains This Organization”) и выражение для этого столбца (PathContains). PathContains () - это функция DAX, которая проверяет, содержит ли путь значение или нет.

Вывод вышеприведенного выражения по-прежнему является таблицей и не может быть визуализирован. Однако, если вы используете другой ConcatenateX для просмотра выходной таблицы, это выглядит так:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

Measure =

CONCATENATEX(

ADDCOLUMNS(

    CROSSJOIN(

        FILTER(

                'Users Organizations',

                'Users Organizations'[User ID]=

                    LOOKUPVALUE(

                        Users[ID],

                        Users[Email],

                        USERPRINCIPALNAME()

                    )

            ),

        Organization),

    "Path Contains This Organization",

    PATHCONTAINS(

        Organization[Path],

        [Organization ID])

    ),

    Organization[Path]&"-"&'Users Organizations'[Organization ID]&"-"&[Path Contains This Organization],

    "

    ")

Вывод вышеприведенного выражения выглядит следующим образом:

 

Все строки, которые показывают TRUE, являются строками, в которых этот пользователь находится где-то на пути его организации, поэтому в результате мы должны фильтровать только эти строки.

Фильтрация только тех строк, где пользователь является частью организационной иерархии

Наконец, давайте отфильтруем только строки с True в их вычисляемом столбце, который мы фильтровали выше. Мы можем сделать это легко с помощью функции FILTER ();

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

Measure =

FILTER(

    ADDCOLUMNS(

        CROSSJOIN(

            FILTER(

                    'Users Organizations',

                    'Users Organizations'[User ID]=

                        LOOKUPVALUE(

                            Users[ID],

                            Users[Email],

                            USERPRINCIPALNAME()

                        )

                ),

            Organization),

        "Path Contains This Organization",

        PATHCONTAINS(

            Organization[Path],

            [Organization ID])

        ),

    [Path Contains This Organization])

Вывод будет похож на следующее (мы использовали ConcatenateX для него):

 

Теперь из выходных данных, приведенных выше, мы хотим получить отдельный список столбцов Organization [ID], и скажем, отдельный вывод этого.

Различный список идентификаторов Organization

В качестве последнего шага этого выражения DAX мы используем SelectColumns(), чтобы выбрать только столбец Organization ID из выходных данных таблицы из предыдущего шага, а затем получаем выходные данные Distinct();

 


6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

Measure =

DISTINCT(

    SELECTCOLUMNS(

        FILTER(

            ADDCOLUMNS(

                CROSSJOIN(

                    FILTER(

                            'Users Organizations',

                            'Users Organizations'[User ID]=

                                LOOKUPVALUE(

                                    Users[ID],

                                    Users[Email],

                                    USERPRINCIPALNAME()

                                )

                        ),

                    Organization),

                "Path Contains This Organization",

                PATHCONTAINS(

                    Organization[Path],

                    [Organization ID])

                ),

            [Path Contains This Organization]),

        "Organization ID",

        Organization[ID]

    )

)

Вывод выражения выше (с использованием ConcatenateX) выглядит следующим образом:

 

Установите роль

Итак, самое трудное позади! Мы готовы установить роль. Все, что нам нужно сделать, это отфильтровать таблицу организации, чтобы столбец идентификатора организации в этой таблице находился в одном из значений, выведенных из вышеприведенного выражения. Мы начнем с перехода к разделу Manage Roles в разделе Modeling Section.

 

Все выражение безопасности уровня строки здесь:

[ID]

IN

DISTINCT(

    SELECTCOLUMNS(

        FILTER(

            ADDCOLUMNS(

                CROSSJOIN(

                    FILTER(

                            'Users Organizations',

                            'Users Organizations'[User ID]=

                                LOOKUPVALUE(

                                    Users[ID],

                                    Users[Email],

                                    USERPRINCIPALNAME()

                                )

                        ),

                    Organization),

                "Path Contains This Organization",

                PATHCONTAINS(

                    Organization[Path],

                    [Organization ID])

                ),

            [Path Contains This Organization]),

        "Organization ID",

        Organization[ID]

    )

)

 

Тестирование решения

Теперь давайте проверим решение и посмотрим на результат. Как вы можете видеть ниже, мы добавили несколько других мер, чтобы показать имя текущего пользователя, а также текущие позиции:

 

Как видно из приведенного выше представления, Реза является финансовым директором и будет видеть все, кроме информации о генеральном директоре и операционном директоре.

Теперь, если мы сделаем Резу генеральным директором (как легко стать генеральным директором в нашей организации), а также операционным директором, он будет видеть все.

 

Из-за безопасности на уровне строк, определенной в таблице Organization, таблица User и таблица User Organization не будут фильтроваться при входе пользователя в систему. Вы можете скрыть таблицу User Organization, а затем использовать такое измеренеие для текущего пользователя:

 

1

2

3

4

Current User = LOOKUPVALUE(

    Users[Name],

    Users[Email],

    USERPRINCIPALNAME())

И измерение, вроде этого, для всех текущих позиций:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

Current Organization =

CONCATENATEX(

FILTER(

    Organization,

    Organization[ID] in

    SELECTCOLUMNS(

            FILTER(

                'Users Organizations',

                'Users Organizations'[User ID]=

                    LOOKUPVALUE(

                        Users[ID],

                        Users[Email],

                        USERPRINCIPALNAME()

                    )

            ),

        "Organization ID",

        'Users Organizations'[Organization ID])

            )

,Organization[Position],", ")

Эти два показателя используются в следующем отчете:

 

Резюме

Реализация защиты на уровне строк может быть сложной в сценариях, подобных тем, что вы видели в этой статье. Вы видели сочетание организационной иерархии и отношения «многие ко многим», что создает некоторые проблемы при реализации. Тем не менее, с помощью DAX вы можете решить все проблемы. Выражение, которое мы здесь рассмотрели, использовало некоторые функции, такие как Filter, SelectColumns, Distinct, CrossJoin, LookupValue, AddColumns и т. д.

No Comments

Add a Comment