Identity-driven member level security for SAS 9.2 OLAP cubes, as the name suggests, uses the identity of the requesting user to restrict access to specific members of a dimension, and in so doing control the subset of cube data that the user has access to. Imagine that you have an OLAP cube containing sales data for all sales people, but an individual sales person is restricted to only seeing a subset of that data. Among all of the other dimensions, perhaps one of the dimensions in the cube has the sales person’s user id contained within it. We can construct an identity-driven MDX expression that can be used to filter the cube data on that dimension to only include members that match an identity attribute such as user id. Of course there are also other ways of using other identity attributes to limit access to cube data but I’ll keep it simple in this example.
To quote from SAS® 9.2 OLAP Server: User’s Guide > What’s New in the SAS 9.2 OLAP Server > Security For Cubes documentation:
Identity-driven security enables you to substitute identity values in a permission condition. It enables you to insert a placeholder into the permission condition that, at query time, gets resolved to a string that represents the user identity.
You can see the various identity value placeholder properties available to you in: SAS® 9.2 OLAP Server: User’s Guide > Modifying and Maintaining Cubes > Cube Security > Identity-Driven Security. Some of the ones that we might use with an individual user include:
- SAS.Userid – the normalized user id for the person querying the cube (e.g. BILLY@MYDOMAIN)
- SAS.IdentityName (or SAS.PersonName) – the name of the person querying the cube, as seen in the SAS Management Console User Manager plug-in (e.g. Billy Baxter)
- SAS.ExternalIdentity – a site specific identifier for people that have been bulk-loaded into metadata from directories such as Active Directory, OpenLDAP etc. This could be an employee number for example (e.g. e12345678)
In this post I’m going to highlight a simple example for a couple of reasons. Firstly, whilst the documentation includes lots of general examples, I couldn’t find many that demonstrate the use of an identity-driven member level security filter. Now I can refer to this post when I need to remember how it’s done. Additionally, during my investigation, I kept getting stumped by an MDX syntax error dialog during the definition of the permission condition. It took me a while to discover that I could just accept the error because at run-time, when the identity substitution happens, the MDX will become syntactically valid. Publishing my experiences might help others save themselves a bit of time and frustration too.
My example cube was built from a SAS table that included a sales person user id column. The value of that column for each row was the user id for the sales person that had access to that data – i.e. that row would contribute to the cube subset that the sales person would be able to see when they query the cube. The format of the user id in that column was quite specific. It was in a normalised format, as explained in the SAS documentation. The user id was converted to uppercase and included a domain suffix on Windows. In Windows environments it would most likely be in the format USERID@DOMAIN (e.g. BILLY@MYDOMAIN), whereas in some UNIX environments it might be in the format USERID (e.g. BILLY). If you don’t know what the format is for your environment you can probably work it out by looking in the SAS Metadata Server logs or turning on some of the debugging options for the SAS OLAP Server. If you don’t get the format correct you are likely to end up with an MDX filter that filters out everything so there is nothing to display!
Information on how to set up member level security on a dimension can be found in the SAS® 9.2 OLAP Server: User’s Guide > Cube Building and Modifying Examples > Setting Member Authorizations On A Dimension. I won’t duplicate that information here, suffice to say that in my example cube I want to apply an identity-driven member level security filter on normalized userid members in the lowest level of a DimSalesPerson dimension using the SAS.UserId property. I want it to apply to all known SAS identities (i.e. SASUSERS group) so I start out by adding an explicit grant of the Read permission to the SASUSERS group on the DimSalesPerson dimension in the cube. This enables the Edit Authorization… button that I click to open the Add Authorization dialog. I select the radio button for Create an advanced MDX expression using the expression builder then click the Build Formula… button. I can then enter the MDX expression to filter the cube data for the current user identity.
When constructing the MDX filter expression you can insert placeholders for Identity Values (such as SAS.Userid) which will be substituted at run-time with the appropriate value corresponding to the user making the request. These Identity Values need to be in a specific format SUB::IdentityValueName (e.g. SUB::SAS.Userid). Whilst I couldn’t find the format documented anywhere, you will see an example of it if you use the Build Formula dialog’s Data Sources list to add an Identity Value into the expression. There is also an example screenshot showing SUB::SAS.IdentityGroups at the bottom of SAS® 9.2 OLAP Server: User’s Guide > Cube Building and Modifying Examples > Setting Identity Driven Security. It doesn’t show it used within a larger MDX expression however.
In my example I want to use the normalized user id to choose the appropriate member of the DimSalesPerson dimension, so I type the following into the Build Formula dialog’s Expression Text field and click the OK button.
{[DimSalesPerson].[All Sales People].[SUB::SAS.Userid]}
Tip: if you want help in working out the syntax for this filter, open the OLAP cube in SAS Enterprise Guide, use point-and-click to filter in the slicer on an example user id in the appropriate dimension. When the table looks like it displays data for a single user, view the MDX:
SELECT CrossJoin({[DimTime].[All Time].Children }, {[Measures].[SaleAmountSUM] }) ON COLUMNS,
{[DimGeo].[All States].Children } ON ROWS
FROM [TransactionsCube]
WHERE ([DimSalesPerson].[All Sales People].[BILLY@MYDOMAIN])You should see what you need in the MDX WHERE clause and you can replace the user id with the identity property – SUB::SAS.UserId in this case.
This is where, initially, I got stuck for a while. I saw the following error dialog because it was not valid MDX since SUB::SAS.Userid was not actually a real member.
It turns out this was ok, because although it was not itself a valid member, at run-time when the user queried the cube it was replaced with the real member value (e.g. BILLY@MYDOMAIN) and became valid MDX (assuming of course that BILLY@MYDOMAIN was a member!).
Tip: How can I tell whether the syntax error is due to the placeholder or the rest of the MDX? One method that I use is to start out by using a constant for a valid member (e.g. {[DimSalesPerson].[All Sales People].[BILLY@MYDOMAIN]}) instead of the placeholder. Verify the MDX syntax is ok for the constant value and that the filter works as expected when you open the cube (i.e. you see the subset of data for that constant user). If all is ok then go back and replace the constant with the placeholder (e.g. {[DimSalesPerson].[All Sales People].[SUB::SAS.Userid]})
So even though I had this error I could click the OK button. I then saw the following warning dialog where I could confirm I did indeed want to save the invalid MDX expression as a permission condition by clicking the Yes button.
With the identity driven member level security filter applied, now whenever the cube was queried the user would only see the subset of data they had been granted access to.
As I mentioned earlier this is a relatively simple example. In reality you would probably want to apply the identity-driven filter to a suitable sales-people group and allow some management-level people to see larger or complete subsets of the cube. You might also want to filter based on group memberships and probably make the filter a bit more robust in case the user id doesn’t exist in the cube. Also, if you are running the third maintenance of SAS 9.2 you could also apply the permission conditions in batch using a permissions table.
The method I use to review all the SAS OLAP cube member level security permission conditions that are present in metadata is to use our Metacoda Security Plug-ins ACE Reviewer. You can click on the image below if you want to see it full size:
Finally, the following SAS usage notes can be very helpful for debugging or troubleshooting with the SAS OLAP Server:
- SAS Usage Note 38429: Generating journal files for the SAS® OLAP Server by using SAS® Management Console
- SAS Usage Note 36728: Setting additional debug options for the SAS® OLAP Server 9.2
BTW: The inspiration for this post came from a question asked on the SAS Discussion Forums > SAS Stored Processes > Thread: Role based security as input parameter, data read from oracle db on demand
Update 02Feb2011: If you’re reading this post then you might also be interested in a related post on Angela Hall’s Blog: Implementing OLAP Member Level Security for All Vantage Points
Update 03Feb2011: In some testing I found the following permission condition useful for filtering on a user id level (that has unique member values across the entire dimension) in a cube at an arbitrary level (i.e. not knowing/specifying how deep the level is within the dimension/hierarchy or what it’s parents are) and then including all unknown ancestors above it.
Ancestors(Head(Filter([DIM_SALES].AllMembers,[DIM_SALES].CurrentMember.Level.Name = 'SALESPERSON_ID' and [DIM_SALES].CurrentMember.Name = 'SUB::SAS.UserId')).Item(0))
I’m likely to forget this so am jotting it down here in case I might need to use it as the basis for future OLAP member level security work. I don’t know about you but I always find MDX work is like mental gymnastics, better than sudoku :) , so I like to keep examples of MDX functions in action. I hope it might give you some ideas too.
Update 04Feb2011: Thanks to a post from Bob in the SAS discussion forum thread linked above I discovered the SAS MDX <!–CONDITION–> ‘operator’ today (looks like an XML comment!). It is used for specifying member level security permission conditions when you have more than one hierarchy in the dimension you are securing. I can’t find it in any of the SAS reference docs but there are a couple of usage notes that mention it:
Brilliant! This helped a lot!
Hi Paul,
This helped us a lot. At the client we are working right now somebody got stuck exactly as you describe trying to implement this for the first time for cubes. However, we haven’t got it working in the way we wanted initially, so a question remains. See further below.
One suggestion: at the start of your blog you mention the problem of getting to know how the value for
SAS.UserId
will look like. You can see that when you go into Information Map Studio and start defining a filter using Identity Values.When you get to the point in the dialog where you can select the different identity properties you will see the current values for your own identity.
Also, we noticed that when you are in the Build Formula dialog and add Identity Values to the expression from the Data Sources tab the placeholder is surrounded by double quotes, something like <code {[DimSalesPerson].[All Sales People].["SUB::SAS.Userid"]}. You will have remove those quotes.
Now our remaining problem.
The cubes we are building are to be accessed by people who should be allowed only slices regarding their organisation. We will have several individuals per organisation. So our idea is to group identities in metadata groups, and grant access to slices through the groups. So we have tried to use the
SUB::SAS.IdentityGroups
construct in the formula.But we have not succeeded in creating MDX that works, probably because this will resolve in a list of values and we don’t know how to handle this in MDX.
An alternative would be to grant all groups explicit access to each respective slice, but that would be a rather cumbersome task…
Any suggestions welcome!
Hi Frank,
Thanks for your comment and the additional points about using the identity driven properties in SAS Information Map Studio.
Regarding the use of SAS.IdentityGroups, I know what you mean about trying to construct an MDX expression from the list of string values. I have yet to find a way to do the required string manipulation/comparison in MDX. Perhaps there is a way, but I haven’t found it yet :)
I would suggest you go down the path of setting multiple permissions conditions as appropriate for the relevant groups. If you were to do this manually it might get tedious especially if you had to repeat it at some later date. If you are running SAS 9.2 M3 or later then you could look into the batch permissions table facility. This allows you to apply all of the permission conditions from the contents of a SAS table. Being a SAS table you might be able to generate it from code if there is a pattern to the conditions and groups.
I tried the batch permissions facility a little while back and took a few notes intending to write a blog post about it – but haven’t finished it yet ;). The documentation for permission tables in SAS 9.2 can be found in a short section Applying Batch Security with Permission Tables in the SAS 9.2 OLAP Server: User’s Guide under Modifying and Maintaining Cubes, Cube Security (look at the very end of the page). I spent a bit of time looking into the SAS dataset structure. I couldn’t find it documented for SAS 9.2 but the SAS 9.3 version of the same document details the table format and its looks very similar to the table I saw in SAS 9.2.
There are also a couple of SAS usage notes you might be interested in:
Problem Note 40424: Permissions tables for use with member-level security are not supported for UNIX libraries or for libraries that use relative paths – I had this issue on my Linux installation – there is a hotfix or you can switch to an absolute path.
Problem Note 42603: The reload button is not available after using code to create a batch permissions table
I hope this helps.
Cheers
Paul
Hi Paul,
Thanks for the suggestions.
The permission tables were quickly implemented for a first cube, and can be used for all other cubes as well.
We had already set up things in such a way that there is simple relationship between the group identities to which access is to be granted and the member names to be put into the MDX string.
A look at the log after applying the permission table also gives you some nice insight in how these things are specified in the metadata – if you are interested in such things ;-).
Cheers,
Frank
Hi Frank,
No problem. I’m glad the permission table suggestion helped you implement your OLAP member level security requirements. It’s good that SAS provided a batch method for applying the permission conditions.
Thanks for coming back to the blog and sharing your experiences.
Cheers
Paul
I just want to add a warning when using the permissions per member utility. I haven’t investigated all the subtleties yet, but this is what I found so far, and as far as I understand it.
Using a ‘permissions table’ you can very nicely grant a read per member. But before an identity can get there it has to have a read for the cube as a whole. That can be accomplished by granting a read for each group, but it is easier and more elegantly to do that through a larger group (that the users in all those smaller groups belong to). In our case SASUSERS seemed a good choice.
That works, but if you have a user that directly or indirectly belongs to that larger group, but is not a member of any of the smaller groups, that user will by default get access to all the members!
This is so because the dimension object to which you applied the permissions per subgroup also inherited the read for the larger group you assigned to the cube object. You cannot remove that, but you can and should change the grant into a deny.
Alternatively you could use a separate group to which you grant the read on the cube, and make only the smaller groups member of the larger group. That way no user can ever get read access to the cube without being limited to particular member(s) of the dimension in question.
This seems a safer way, but I have yet to figure out how to fit that in the larger security scheme without creating a set up that will be difficult to maintain.
Hi Frank,
Thanks for the follow up comment about your experiences with the permissions table. I can see how if you use SASUSERS as the group to grant the read permission on the cube (or the folder the cube is in) but not all SASUSERS should have access to the cube contents then, because of the lack of a permission condition for SASUSERS, they would get access to all of the cube data since none of the permission conditions are relevant for their identity. Have you tried adding a permission condition for SASUSERS (or PUBLIC) that filters out all of the data (equivalent to “where false”) for such cases? I imagine from a security modelling perspective it might be better to avoid such gaps in the first place and only grant the read permission to the same subset of SASUSERS that are present in the permission conditions (as you suggested).
Perhaps it is worth making a SASware ballot suggestion to suggest that by default, as a conservative fail-safe security measure, in the event of such gaps, when there are permission conditions on a cube, if the user’s identity is not present in any of the cubes permission conditions, and they are not a member (direct or indirect) of any of the groups in the cubes permission conditions, then it returns an empty result set?
Cheers
Paul
Regarding Paul’s first suggestion, to add a condition that denies SASUSERS access to all members, I haven’t tried that. It would create a conflict between two conditions. If the general strategy that a right ‘from nearby’ supersedes the right from further away works here as well, things would be OK in the case I described, but I haven’t tested it.
Moreover, when the right to a member is indirect as well it might not work.
The second suggestion, for a SASballot item looks worthwhile, but yet again it might create problems in more complex situations. And it should always possible to get back the current behaviour without complicated constructions, because that might be the desired situation.
Another observation from working with the permissions.
If you give users the right to several members of a dimension, by creating in list in the permission condition, like
{[Dimension].[Dimension].[All members].[mem1],
[Dimension].[Dimension].[All members].[mem2],
[Dimension].[Dimension].[All members].[mem3]
}
it is the order in which you specify the members in this condition that determines the order in which the result is displayed in (e.g.) a web report. The specification in the cube metadata (formatted or unformatted order, asc or desc) no longer has effect.
I don’t know what happens if an identity is a member of several groups, and thus has a collection of members that come from different conditions…
Hi Frank,
It has been a while, but I just noticed this quote in the SAS documentation which clarifies the situation when there is a conflict between multiple conditions due to multiple memberships. It can be found in the SAS® 9.2 Intelligence Platform: Security Administration Guide under Fine-Grained Controls for Data – How are Fine-Grained Controls Assigned?:
Cheers
Paul