Back in February this year, I wrote here describing examples of row-level security in SAS Viya, illustrating row-level grants for groups, the most common way row-level grants are used.
A recent conversation with a SAS colleague highlighted that there are important aspects of CAS row-level security which I did not cover in that post. We also need to know how row-level grants for Authenticated Users, groups or the user behave when other access controls are also present. In other words, how does precedence work in the CAS authorization system, and in particular how does it work for row-level security?
In this post we will see how precedence works when there is a combination of row-level grants, and ordinary grants and denies, when there are access controls applied at several different levels of the user hierarchy. This post also presents a proposed Authorization Decision Flowchart for CAS permissions.
Because we are mainly thinking about row-level grants in this post, our attention will focus mainly on the Select permission, which controls whether the user is allowed to see data in the table (and for a row-level grant, which rows in the table they can see). When designing a security model, you will also need to consider other permissions, especially the ReadInfo permission on both the table and its parent CAS library. The way that ReadInfo and other permissions work is more straightforward, and easier to understand. Row-level grants on the Select permission are the most complex, and deserve a bit of extra attention.
CAS access controls can be applied to any of these objects, which form a simple object hierarchy:
Note: while the CAS engine supports column-level access controls, set via CAS actions called from CASL or another supported programming language, they are not (at least, not widely) supported in SAS Viya visual interfaces, and as I understand it are only usable in programming-only usage scenarios. Row-level grants work well throughout SAS Viya.
In general, it is important to understand and use the CAS object hierarchy listed above when designing your CAS security model, to secure your data. However, because this post is concerned with row-level security, that object hierarchy will not be very important to this discussion. As the documentation under Security > Authorization > CAS Authorization > Concepts > Authorization Decisions in the SAS Viya Administration Guide explains:
In the CAS authorization system, precedence is determined by where an access control is set and who an access control is assigned to.
- Direct access controls have precedence over inherited settings.
- The principal precedence hierarchy is relatively flat. It consists of only the following three levels: 1) individual users, 2) user groups, and 3) the construct Authenticated Users.
Note: All user group memberships are at the same level of precedence, even if groups are nested.
Direct access controls have precedence over inherited access controls, regardless of who the principal is.
We will not spend much time in this post considering whether the user inherits a grant or a deny of the select permission from the parent CAS library. If the select permission is set on the CAS library for the user, or for a group to which the user belongs, and no direct access control for the user or one of their groups is set on the CAS table, then yes of course that inherited permission will determine whether the user sees all rows or no rows in the table.
But in this post we are considering row-level security, and the situation described in the previous paragraph is one in which you have not defined any table-level access controls at all for the user. That puts such a scenario a little outside the scope of this post. As we read above ‘Direct access controls have precedence over inherited access controls, regardless of who the principal is’, so any access control set directly on the table (even one for Authenticated Users) will have precedence over any inherited access control (even one for the user). We can therefore keep things simple when thinking about row-level security by focussing only on direct access controls applied to the table, for Authenticated Users, groups or the individual user.
To fully understand how CAS row-level security works, we need to consider where each row-level grant is applied, and how it behaves with other row level grants and other permissions applied elsewhere in the object hierarchy. Focussing on the select permission on a table, we can have the following, any combination:
However complex the set of access controls which may apply to an individual user, it should in fact be quite straightforward to determine how the user's effective access to rows of data is evaluated using the Authorization Decision Flowchart for CAS permissions, focussing on the Select permission below.
My previous post focussed on simple row-level grants for groups only, and how they were applied cumulatively. As the documentation explains (the following is the Multiple Filters and Cumulative Access section in full):
If multiple row-level filters are applicable to a user, only the highest precedence filter provides access. If there is an identity precedence tie (the user is a member of multiple groups, each of which has a filter), the user can access any row that meets any of the filters.
Here are details:
The filters for multiple row-level grants provide cumulative access only if all of the following circumstances exist:
- The requesting user does not have a direct access control for the Select permission.
- None of the requesting user’s groups have a direct grant or denial for the Select permission.
- Two or more of the requesting user’s groups have row-level grants.
Note: All custom and LDAP groups have equal precedence, regardless of any nested memberships.
Note: A filter for a row-level grant that is assigned to Authenticated Users is never cumulative (joined with other filters by OR). Authenticated Users is a construct that has lower precedence than any group.
This explanation is absolutely correct, but personally, I still managed to find it hard to visualise (and remember afterwards) how authorization decisions for row level security worked in more complex situations. In fact, when discussing one such complex situation with a SAS colleague, I am ashamed to admit I got it quite wrong. To learn from this experience, I felt that a diagram might help me understand it better, and I hope it will be helpful to you too.
The following diagram is my proposed Authorization Decision Flowchart for CAS permissions, focussing on the Select permission in the implementation of row-level security. It is inspired by the Authorization Decision Flow diagram in the SAS 9.2 documentation designed by Catherine Hitti.
Select the image to see a larger version.
Mobile users: To view the image, select the "Full" version at the bottom of the page.
The diagram is rather complex. Here are some details that may help interpret it.
First, consider the outer green boxes labelled “On the table” and “On the table’s parent CAS library”. Remember that for row-level security, we focus on how the select permission is applied to the table itself. Inherited permissions are effective only if there are no direct access controls for the user, their groups, or for Authenticated Users on the table. If any of those principals has a direct access control on the CAS table, it will have precedence over whatever is inherited from the parent caslib. Also note that you cannot set row-level grants on the select permission for a caslib.
Second, consider the top blue box labelled “Does the user have a direct deny, grant or row-level grant of the select permission?”. Any access control set directly on the table for the user will have precedence over an access control for one of the groups to which the user belongs, or an access control for Authenticated Users. So:
Third, if there are no access controls for the user which allowed us to reach an authorization decision in the previous step, consider the blue box labelled “Do the groups to which the user belongs have any direct denies, grants or row-level grants of the select permission?”. Any access controls set for groups to which the user belongs will have precedence over any access controls set for Authenticated Users. So:
Fourth, if there are no access controls set for the user, nor for any of the groups to which the user belongs, we consider the next blue box titled “Does Authenticated Users have a direct deny, grant or row-level grant of the select permission?”.
Lastly, only if there are no access controls for the user, any of their groups, or AUs set directly on the table itself, effective access inherited from the parent caslib is considered. This will not be a row-level grant, so we are not so interested in that situation in this post. If no access controls are set anywhere, access to data is denied and the user sees no rows.
If you want a row-level filter to apply to all Authenticated Users and have it work cumulatively with other row-level filters for other groups, the way the rules are evaluated can make this seem a bit of a challenge.
Here is an example of such a requirement, based loosely on a real conversation with my SAS colleague. Suppose you have a table in a travel system CAS library, listing employee trips with columns for the traveller’s userID, the region the trip was from, the region it was to, and the region the traveller reports to, among many other columns. Then suppose that you want to create:
and have that filter be cumulative with
The problem is that when CAS determines which rows the user in the Asia Travel Approvers group should see, the access controls for their groups take precedence over the access controls for Authenticated Users. Any access controls for Authenticated Users are simply not considered when evaluating which rows the user can see. So that user will see all trips to or from Asia or for employees who work for the Asia region, but if the user has trips themselves, which were not to or from an Asian location and they do not report to an Asian region themselves, the user does not see their own trips! This is not what was intended by the report designer.
But there is a solution, if your LDAP directory has an LDAP group which contains all users. If it does not, you may consider asking your LDAP administrator to create such a group!
You can replace the row-level grant for Authenticated Users with a row-level grant for the LDAP group which contains all users. When you have done that, the ‘all users’ group-level row-level grant will combine with other group-level row-level filters in a union join of rows that each user is allowed to see, producing the desired result.
With thanks to Richard Clowes and Teri Patsilaras for their efforts in understanding and explaining this.
I hope this helps if you ever need to design an approach to row-level security in the context of other permissions, especially when you have good reason to apply row-level grants at more than one level of the identity hierarchy. If it does, perhaps you would consider a thumbs-up below? And please do leave a reply if you have any questions or observations about this topic, or the content of this post.
See you next time!
Registration is open! SAS is returning to Vegas for an AI and analytics experience like no other! Whether you're an executive, manager, end user or SAS partner, SAS Innovate is designed for everyone on your team. Register for just $495 by 12/31/2023.
If you are interested in speaking, there is still time to submit a session idea. More details are posted on the website.
Data Literacy is for all, even absolute beginners. Jump on board with this free e-learning and boost your career prospects.