Use Row Level Security for a single Power BI report

One of my clients recently asked me: โ€œI want to use Row level security (RLS) for this reportโ€. The problem however is: Itโ€™s a very big model, used for over 30 reports, and they didnโ€™t want RLS for all the other reports. Report users only get viewing rights (via the app) and there is a limited amount of report creators.

  • First idea: make a copy of the table. Problem: 6 million extra rows.
  • Second idea: make a copy of the whole data model (or at least the required tables). Problem: one extra model to maintain (and to keep in sync with the main model).
  • Third idea: Create a live connection, add a table for the RLS, and use that data model for the report. Below youโ€™ll read how I did that.

The main report

First, letโ€™s recreate the model a bit. Imagine a group of shops that all sell the four same products. Hereโ€™s what the report looks like without RLS:

The report without any RLS

The data model:

The Power BI data model

After we publish this model, it can be used for all the reports they want to create, which do not require RLS. Now letโ€™s go create the second data model.


The second data model

First, we create a live connection to the data model we just published, and after that, we try to import some extra data. The only way to do that is when we enable DirectQuery over a live connection:

Warning for creation a DirectQuery connection

After we select Add a local model, we can add more data.

In the table, we only add our three store names.

Now the data model looks like this:

Please note the blue bars (which means the live connection) and the Storage mode: Mixed at the bottom. And now the magic is happening, because how can we use our _RLS Table to actually filter the Sales table, if there is no relationship between them? Say hello to calculation groups!

Note: when you enable calculation groups, you canโ€™t use implicit measures anymore!

Our Calculation Group only gets one item:

RLS Filter = 
CALCULATE(
    SELECTEDMEASURE()
    ,KEEPFILTERS( Shop[Shop Name] IN VALUES('_RLS Table'[Shop Name]) )
)

We then put this calculation item in our Filter Pane to filter the entire report:

Use the calculation item as a report level filter.

What this will do, is put all the measures that we add to our report in a filtered state. Where it only shows the values from a selected shop. Now itโ€™s time to create the roles for RLS.

Per store, I make one role that filters on only that store:

Define roles per shop

Now this is how the report looks like without any role:

Report without a selected role.

And this is how it looks like when a role is selected:

Report with a selected role.

So, as you can see, we finally have RLS! But, we can still select all the shops. This can easily be solved by creating a new measure:

_ Filter Shop RLS =
INT(
ย  ย  NOT ISEMPTY(
ย  ย  ย  ย  'Shop'
ย  ย  )
)

Now we put this measure as a visual level filter for our shop slicers, and equal it to 1:

Shop filtered

Now we can publish the report to the service, and add the people to the specific roles. When we go to the lineage view of our workspace, this is how it looks like:

Lineage view with two data models connected to each other.

Every change we apply to the original data model will automatically reflect in our RLS model! So you only have to maintain one data set, and for the RLS model a single table.

I hope this helps whenever you get this question!

Take care.

Leave a comment

search previous next tag category expand menu location phone mail time cart zoom edit close