Is it possible to circumvent role-based row-level security with a well-crafted DAX query?
Recently, while discussing role-based security in SSAS Tabular models with a few colleagues, the following question came up: Is it possible to write a DAX query in such a way that row-level security is bypassed?
At the time, we all agreed that the notion seemed ridiculous…if that were possible, what’s the point of even having row filters in roles? On the other hand, I’d just finished Rob Collie’s (b | t) excellent book in which I learned that the CALCULATE function can be used to “remove existing filter context”.
Hrmmm….can it be? Let’s find out!
Since row-level security in a SSAS 2012 Tabular model is implemented as a “Row Filter” configured with a role:
…and the CALCULATE function can be used to modify the filter context in which the expression is evaluated (per MSDN):
If the data has been filtered, the CALCULATE function changes the context in which the data is filtered, and evaluates the expression in the new context that you specify. For each column used in a filter argument, any existing filters on that column are removed, and the filter used in the filter argument is applied instead.
…then, it just might be possible for a DAX measure to be written such that the row filter defined in the role is ignored (or removed from the current context) and thus rendered useless.
To test my hypothesis, I’ve put together the following experiment.
Step 1: create a basic Tabular model:
Step 2: create a security role with a row filter on one of the
dimension lookup tables (that’s right, tabular models have “lookup” tables, not “dimensions”):
Step 3: create two DAX measures based on a CALCULATE function call with a filter argument referencing the same attribute as our security role row filter:
In the first measure (TotalResellerSalesAmount_NoSecurity01), the use of the ALL() function in the filter clause of the CALCULATE function basically says, drop any existing filters on the SalesTerritoryGroup column/attribute before summing up ResellerSalesAmount. The second calculated measure (TotalResellerSalesAmount_NoSecurity02) is the same as the first except this time the ALL() function is applied to the entire DimSalesTerritory table (not just the column/attribute) and will therefore remove any existing filters from any of the columns/attributes in that lookup table (not just the ones on SalesTerritoryGroup).
Step 4: build/deploy the model
Step 5: test the hypothesis by executing the following DAX query via SSMS:
If my hypothesis is correct, when members of the NorthAmericanUser group (created up above) execute this query in SSMS they should see Reseller Sales for all Sales Territory Groups (North America, Europe, and Pacific) in the “TotalResellerSalesAmount_NoSecurity01” and “TotalResellerSalesAmount_NoSecurity01” columns but only see Reseller Sales for the North America sales territory in the “TotalResellerSalesAmount” column.
The screenshot below is simply for comparison…I snagged it before removing my current user account from the server administrators list. Since permissions are cumulative and server administrators have full access to all models on the server the role-based security is completely ignored and we have reseller sales amounts for all 3 sales territory groups for each of the measures.
After removing my account from the server admin list, my only access to the model is via the NorthAmericaUsers role which has a row filter set for NorthAmerica. So when I run the query now:
And boom goes the dynamite…Hypothesis DISPROVED!
I’m actually a little relieved.