RLS filter with multiple values assigned? - powerbi

I need to filter my Power BI report by the App IDs associated with the current user (using the USERPRINCIPALNAME function). So I have three tables in my model, DimApp, DimUser, and FactRegisters, where a User_Id may be related to 1 or more App_Ids in my Fact table.
DimApp table
DimUser table
FactRegister table
As you can see in FactRegisters table there are two App_Ids (3 and 1) for User_Id 201. The following is the DAX rule defined in App_Id column from DimApp table to filter the data:
VAR userId =
LOOKUPVALUE (
DimUser[User_Id],
DimUser[Email], USERPRINCIPALNAME()
)
VAR app =
LOOKUPVALUE (
FactRegisters[Application_Id],
FactRegisters[User_Id], userId
)
RETURN DimApplication[Application_Id] IN {app}
Verifying the DAX expression doesn't return an error, however, when I choose to "View as" that role I'm not able to see the data in the visuals. The error states: "Couldn't load the data for this visual. An error was encountered during the evaluation of the row-level security expression defined in table DimApp. A table of multiple values was supplied where a single value was expected."
Cannot display the visual viewing as role
However, when a single App_Id is associated with the User_Id, I'm able to visualize the data on the report visuals using the same DAX rule. Here is how FactRegisters table looks like when User_Id 201 has a single App_Id (3) associated:
FactRegisters table when User_Id with single App_Id
User_Id with a single App_Id visual
Now I'm I able to visualize data in the report. This is not a suitable case scenario as a User_Id can have many App_Ids.
I also tried the following static DAX rule in my App_Id column from DimApp just to test and pass multiple values to that column, and I succeed in visualizing data for multiple App_Ids:
DimApplication[Application_Id] IN {1,3}
Static RLS with multiple values by App_Id column
But this is not the goal (it's not dynamic). The goal is to visualize the data from all the Apps associated with the current user. Is it possible? Can't I pass more than one value to a column while filtering in RLS?

[Application_Id] IN
CALCULATETABLE(
VALUES('FactRegister'[Application_Id]),
FILTER ('FactRegister',
'FactRegister'[User_Id] = LOOKUPVALUE (DimUser[User_Id], DimUser[Email],USERPRINCIPALNAME())
)
)

Related

How to resolve `single value for column cannot be determined` error?

DimUser and DimCustomer filter the FactSales table.
I have created a RLS role with the following DAX on the DimCustomer table:
[DW_CustomerID] IN
SELECTCOLUMNS(FILTER(Dimuser,DimUser[User_Email]=USERPRINCIPALNAME())
, "DW_CustomerID", FactSales[DW_CustomerID])
My intention is to filter the DimUser based on the current user's email, then retrieve the filtered Customer ID's from the FactSales table. Effectively the logged in user can only those customer for the user has made sales.
The DAX is giving following error:
A single value for column DW_CustomerID in table FactSales cannot be
determined.
How to resolve this error?
You are looking for a column that does not exist in the table you are looking for it in. The first parameter of the SELECTCOLUMNS() function is a table, in your case you have provided a derived table built by the FILTER() function being used on the DimUser table. Therefore, your derived table is one row with all the columns from the DimUser table. The FactSales[DW_CustomerID] column is not in this table.
I would try rewriting it to be something closer to the following:
[DW_CustomerID] IN
CALCULATETABLE(
VALUES(FactSales[DW_CustomerID]),
DimUser[User_Email] = USERPRINCIPALNAME()
)
Without seeing your model it is tough to know for sure though.

How to filter a dimension based on RLS applied to another dimension?

Assume I have 2 dimension tables and 1 fact table.
DimUser
DimClients
DimCompany
FactSales
All the dims have a 1:M relation with filter flowing from the 1 -> M side.
Assume I have configured RLS filter on DimUser ([Username] = USERNAME()) so that the user of the report can see his own data based on email address, so user can see his own record entry in DimUser. Thus it will auto filter the FactSales also, effectively the user will be able to see only his sales.
However the user can see all clients and companies in the slicer. I want to setup filter on the DimClients and DimCompany so that the user can see only clients and conpanies for which he has made sales. How can I achieve this?
For example:
How to apply RLS to the DimClients and DimCompany so that it will filter only those ClientIds that appear in the sales table based on dynamic filter [Username] = USERNAME() applied on the DimUsers table?
The other option is to enable bidirectional filtering but this is not allowed for multiple tables.
You can create a measure that looks like this:
UserFlag =
CONTAINSSTRING (
CONCATENATEX ( FactSales, RELATED ( DimUsers[UserEmail] ), "," ),
USERNAME()
)
Create your RLS role and add this filter to each dimension.
[UserFlag] = True()
The result: If a row from a dimension is related to a fact row that is in turn related to the DimUser row whose email matches USERNAME(), then the measure returns 'True.' And so RLS is filtering only to those rows.

How do I write a DAX expression in Power BI for row-level security?

I am trying to implement row-level security on one table based on a separate users table. I've seen this talked about in places like this, but haven't been able to get things working for my case.
Users table:
Transactions table:
The table I'd like to secure is called Transactions. One of the fields in each row is CompanyID. The Users table contains three columns: AccountID, UserEmail, and CompanyID. What I'd like is for only users assigned to given CompanyIDs to be able to view rows in the Transactions table with those CompanyIDs.
Since more than one User may view a given row, I established a one-to-many relationship from Transactions to Users on the CompanyID field.
I created a DAX expression that filters on the Users table with the following:
[UserEmail] = USERPRINCIPALNAME()
When I select "View As -> Other User" in Power BI Desktop and enter a random email, though, I can still see the entire report. Any idea what I'm leaving out?
EDIT:
I left out an important stipulation: Any user associated with a CompanyID of 1 can view all the records of the Transaction table. I've tried approaches similar to this
[UserEmail] = USERPRINCIPALNAME() ||
COUNTROWS(FILTER('Users', [UserEmail] = USERPRINCIPALNAME() && [CompanyId] = 1)) = 1
but they don't work. Even users with CompanyId of 1 are prohibited from viewing the table.
From the docs:
By default, row-level security filtering uses single-directional
filters, whether the relationships are set to single direction or
bi-directional. You can manually enable bi-directional cross-filtering
with row-level security by selecting the relationship and checking the
Apply security filter in both directions checkbox. Select this option
when you've also implemented dynamic row-level security at the server
level, where row-level security is based on username or login ID.
https://learn.microsoft.com/en-us/power-bi/admin/service-admin-rls

Default Power View for Power BI User

I am trying to build a page where it returns table for the logged in user. And then you can use the filter to look at other users records .
User = USERPRINCIPALNAME()
I am having problems filtering the table for the logged in user . Without using row level security with data model changes like below . Is there a way for the Power BI table to return data just for the logged in user ? No SSAS involved.
https://medium.com/#barrasa8/dynamic-data-masking-in-powerbi-based-on-rls-927eb6a34e5d**strong text**
The data model is a FACT table linked to a USER dimension. In the User Dimension , there is an email address which is what the USERPRINCIPALNAME() resolves to.
I thought about a DAX summary table with summarise and may try that later . Then 2 buttons on the page , one to show current logged in user and the other button just gives you details about all other users data and work with all the filters on the page .
So basic want is
Logged In User : X
Table - Col 1 , 2 ,3 .... ( Filtered for User x only by default )
Then I would like a way for the logged in user then to see others user data easily.
Unfortunately there is no way to use USERNAME() or USERPRINCIPALNAME() in DAX measures.
What you're left with is using row level security but that would mean it'll not be possible to show the data of other users.
The best alternative I can think of is to load the data twice. Then set Row Level Security on one table, display that one as "Your data", don't use RLS on the second table and display that as "Other people's data". Put them side by side for easy comparison.
I just wrote a blog post about a similar challenge on how to make sure you can still filter both tables: https://www.linkedin.com/pulse/calculating-totals-row-level-security-using-powerbi-van-der-pasch

Display Matched and Non Matched Values based on a slicer value Power BI

I am working on a Viewership table which tells which customer watches which asset. Based on the asset filter, I need to display the customers who watched the show & customers who didn't watched the show. below is my example table
If the asset_id selected as 1 in the slicer, the desired output will be as below
I have tried creating a cross-join table with asset_id and customer_id , but that approach taking much time with large data. Request the experts here to suggest the best optimal solution to achieve this.
First, create a new table "Asset":
This table contains unique assets, and we will use it to create a slicer that affects DAX measure but does not affect the visual (table). To achieve that, the Asset table must be disconnected from the Viewership table (no relationships).
In your viewership table, I just renamed "asset" to "asset_id", to be consistent:
Next, create a measure:
Status =
VAR Selected_Asset = SELECTEDVALUE(Asset[asset_id])
VAR Customer_Asset = SELECTEDVALUE(Viewership[asset_id])
RETURN
IF(Customer_Asset = Selected_Asset, "Watched", "Not Watched")
Result:
Slicer here is created from the "Asset" table, and table is a table visual with customer_id and asset_id from the Viewership table (set them as "don't summarize" values). I turned off "total", assuming you don't need it.
This design requires to set Asset slicer to "single selection" mode, to make sure that you are only getting one value from it. If you want the model to work with multi-select slicer, change DAX measure as follows:
Multi Status =
VAR Selected_Assets = ALLSELECTED(Asset[asset_id])
VAR Customer_Asset = SELECTEDVALUE(Viewership[asset_id])
RETURN
IF(Customer_Asset IN Selected_Assets, "Watched", "Not Watched")
Result:
Edit:
To make it work at the customer level:
Customer Status =
VAR Selected_Assets = ALLSELECTED(Asset[asset_id])
VAR Customer_Assets = VALUES(Viewership[asset_id])
VAR Assets_Watched = COUNTROWS(INTERSECT(Customer_Assets, Selected_Assets))
RETURN
IF(Assets_Watched > 0, "Watched", "Not Watched")
Result:
Explanation: store selected assets in a table variable. Then, store assets visible per customer in another table variable. Find an intersect of the two tables (what they have in common), and count intersect rows. If none - not watched, otherwise watched. If you want, you can actually display the number of movies watched (just return "Assets_Watched" instead of IF statement).