Grouper ABAC with scripted groups
- 1 Grouper ABAC
- 2 Use case
- 3 Attribute based access control (ABAC) overview
- 4 Syntax
- 5 Boolean logic
- 6 Global variables
- 7 JEXL loaded groups
- 8 UI to configure
- 9 Subject source configuration
- 10 Incremental daemon
- 11 Daemon screen
- 12 Scripts
- 13 How it works in v5+
- 14 Analyze policy
- 15 Policy patterns
- 16 Access
- 17 Max membership size
- 18 Features
- 19 See also
Grouper ABAC
Grouper does a great job with group relationships and group math. Basis groups can be loaded into Grouper but that is a single relationship from a user to an attribute (group). For instance:
You can load groups that represent every affiliation with the users who have that affiliation (e.g. faculty, staff, contractors)
You can load groups that represent organizations with the users who are in those organizations (e.g. business school, engineering school)
But you cannot take those groups and use group math to calculate which users have a specific affiliation in a specific organization (both are many to many relationships). (e.g. staff/contractors in the business school)
You would need to load the cross product of the data which is not scalable as the number of attributes increases. e.g. a loader that has groups for staff in business school, contractors in nursing school, etc
ABAC allows you to model rows of data for a user, and then make an ABAC script to specify criteria in that row of data. You could instantly make a group for users who have certain affiliations in certain org in their primary job in a full time capacity. Previously you needed to make a loader job to load a group with a SQL query that can join various data elements from a data warehouse.
Grouper ABAC Blog
Check out this Grouper ABAC blog from November 2025 for info on using ABAC to reduce the burden of loader jobs.
Use case
At U Penn over 15 years we now have 700 loader jobs. Only Grouper sysadmins can manage these loader jobs for security reasons. It takes tickets to create the job, update the job, and troubleshoot the data. This valuable staff time is greatly reduced with ABAC. These loader daemons generally do not have real time updates since that is difficult to configure for every job, so hourly full syncs are scheduled which waste resources. There is no way to do grace periods on the source data unless the source database keeps data history (which likely is not the case).
Enter Grouper ABAC, with a few data providers (identity data, student data, payroll data, training data, etc), we can replace 2/3 of our loader jobs with ABAC. The data will flow to Grouper in real time (with fewer data feeds by two orders of magnitude it is feasible to configure real time updates). Grouper keeps history on all the data so grace periods at the row level are available. Each data provider has its own security policies at at the column level (with three access levels) which can be securely delegated to eligible power users. Users can use ABAC scripts to configure, troubleshoot, and update their own groups.
A dynamic data dictionary informs users which attributes are available, what the values mean, and how to use them. The analysis screen shows the numbers of all the parts of the script, and if test users have each attribute or the result. This troubleshooting in SQL is very time consuming. The groups will be updated in real time and the attribute values change. A single ABAC script can use data from any data provider and even Grouper memberships (which cannot be done with loaders without supporting ETL jobs).
Loaders (to build multiple groups at once) can point to Grouper's attribute repository to have consistent data and real time updates. The reduction in loader jobs from external data sources will reduce the network query traffic and database loads. This ABAC data can be used to replace subject sources, provision rich object representations, manage users' lifecycles, and populate reports.
The ability to manage groups by rows of data has been requested for years and will revolutionize access management. It further differentiates Grouper from other IAM products.
Attribute based access control (ABAC) overview
To implement access policies, it has often been necessary to set up intermediate groups, include/exclude, requirement groups, and allow/deny manual groups. Grouper has features to help in this area including: rules, hooks, templates, move/copy, import/export, and GSH scripts.
The ABAC with scripted groups feature is designed to offer increased efficiency in implementing access policies. It's important for the common groups and policy language to be well documented and people to be properly trained.
Syntax
Type | Concept | Example | Description |
|---|---|---|---|
Entity attribute | member of | 'ref:mfaEnrolled' entity.memberOf('ref:mfaEnrolled') | Users that are members of this group (by system name / ID path) |
Entity attribute | member of any group | entity.memberOfAny(['ref:staff', 'ref:faculty']) | Users that are members of any of the specified groups (by system name / ID path) |
Entity attribute | recent member of | entity.recentMemberOf('ref:staff', '30 days') entity.recentMemberOf('ref:staff', '1 hour') | Users that were recently (but not currently) members of this group (by system name / ID path) |
Entity attribute | has attribute | org 'org' entity.hasAttribute(org) entity.hasAttribute('org') | User has this attribute assigned or true for boolean attribute or has the attribute with any value for other types |
Entity attribute | has attribute string | org==abc org=='01234' 'org'=='012#$%45' entity.hasAttribute(org, abc) entity.hasAttribute(org, 'abc') entity.hasAttribute('org', 'abc') | Users that have this string attribute assigned to them |
Entity attribute | does not have attribute string | org != 'abc' | User that do not have this attribute with this string. Could also write as !(org == 'abc') |
Entity attribute | has attribute integer | org==123 'org'==123 entity.hasAttribute(org, 123) | Users that have this integer attribute assigned to them |
Entity attribute | does not have attribute integer | org != 123 | User who do not have this integer attribute assigned to them |
Entity attribute | comparison operator | entity.hasAttributeLessThan(org, 55) entity.hasAttributeLessThanOrEqual(org, 55) entity.hasAttributeGreaterThan(org, 55) entity.hasAttributeGreaterThanOrEqual(org, 5) | Users have a value compared to the scalar value. In the first example, org must be less than 55. In Grouper v5.17.2+. |
Entity attribute | has any attribute string in list | jobCode =~ [abc, def] jobCode =~ ['abc', 'def'] entity.hasAttributeAny('jobCode', ['abc', 'def']) | Users that have any of these values for this attribute |
Entity attribute | has any attribute integer in list | jobCode =~ [123, 234] entity.hasAttributeAny('jobNumber', [123, -234]) | Users that have any of these values for this attribute |
Entity attribute | has attribute value like | entity.hasAttributeLike(org, '%\\_2%') | Users that have an attribute value like the SQL likeString. Note: "like" expressions are more efficient than regex |
Entity attribute | has attribute with value matching regex | org =~ '^.*2.*$' entity.hasAttributeRegex(org, '^.*2.*$') | Users that have an attribute value that matches the regex. Recommended regex site to build and test a regex. Escape quotes and slashes in jexl with backslash. Less efficient than SQL like string. |
Entity attribute | time from now | entity.hasAttributeLessThan('accessTokenExpiresAt', timeFromNow('now')) entity.hasAttributeGreaterThan('lastLoginAt', timeFromNow('-30 days')) | Resolves to a millisecond-since-epoch value offset from the current time at script-analysis time. Argument is a single string: 'now' for the current instant, or a signed integer + unit (e.g. '30 days', '-5 minutes', '-1 year'). Negative for past, positive for future. Units accepted (singular or plural, case-insensitive): minutes, hours, days, weeks, months, years. Use anywhere a numeric / timestamp value is expected. In the last example: users who logged in within the last 30 days. |
Entity row | has row with attribute assignment | entity.hasRow('affiliation', 'active') | Users that have an affiliation row with an attribute assigned or true for boolean, or any value for other types |
Entity row | has row with attribute value | entity.hasRow('affiliation', 'affiliationCode==staff') | Users with a row of affiliation with a column value of attributeCode or value staff. "staff" is a string that doesnt start with an integer or have special characters in it. |
Entity row | has row with attribute value | entity.hasRow('affiliation', "affiliationCode=='01234' ") | Users with a row of affiliation with a column value of attributeCode or value 01234. "01234" has quotes around it since it has special chars or starts with an integer |
Entity row | does not have row with attribute value | entity.hasRow('affiliation', | Users who do not have a row of affiliation with a column value of attributeCode or value 01234. "01234" has quotes around it since it has special chars or starts with an integer |
Entity row | has row with attribute with integer value | entity.hasRow('affiliation', "affiliationCode == 1234 ") | Users with a row of affiliation with a column value of attributeCode or value 1234. In this case the affiliationCode is an integer type attribute. |
Entity row | does not have row with attribute with integer value | entity.hasRow('affiliation', "affiliationCode != 1234 ") | Users who do not have a row of affiliation with a column value of attributeCode or value 1234. In this case the affiliationCode is an integer type attribute. |
Entity row | has row with attribute value using comparison operator | entity.hasRow('affiliation', "affiliationCode > 1234 ") | Users with a row of affiliation with a column value of attributeCode greater than value 1234. Can use <, <=, >, >=. In Grouper v5.17.2+. |
Entity row | has row with attribute string in list | entity.hasRow('affiliation', 'affiliationCode =~ [staff, fac, alum]') | Users with row of affiliation and has column affiliation code in staff, fac, alum |
Entity row | has row with attribute string not in list | entity.hasRow('affiliation', 'affiliationCode !~ [staff, fac, alum]') | Users with row of affiliation that does not have a column affiliation code in staff, fac, alum |
Entity row | has row with attribute value SQL like string | entity.hasRow('affiliation', "hasAttributeLike(affiliationCode, '%f%') ") | Users with row affiliation where a value for column affiliationCode is has an f in it. This is more efficient than regex. |
Entity row | has row with attribute value that matches a regex | entity.hasRow('affiliation', "hasAttributeRegex(affiliationCode, '^.*f.*$' )" ) | Users with row affiliation where a value for column affiliationCode has an f in it. Users that have an attribute value that matches the regex. Recommended regex site to build and test a regex. Escape quotes and slashes in jexl with backslash. Less efficient than SQL like string. |
Entity row | compare two columns | entity.hasRow('affiliation', 'attributeCompare(affiliationDeptNumber < affiliationDeptNumberPrimary) | Users with row affiliation where column affiliationDeptNumber is less than column affiliationDeptNumberPrimary. Operator can be < > == <= >= != |
Entity row | compare two columns with addition or subtraction | entity.hasRow('affiliation', 'attributeCompare(affiliationDeptNumber + 5 < affiliationDeptNumberPrimary) | Users with row affiliation where column affiliationDeptNumber (plus 5) is less than column affiliationDeptNumberPrimary. Operator can be < > == <= >= != |
Entity row | range of strings or ints | entity.hasRow('affiliation', " | Users with row affiliation where the affiliationDeptNumber between two numbers or strings, inclusive or exclusive. The same column must be used in both arguments, and the order of the scalars and the column must be as shown. Can only user < or <= to denote exclusive and inclusive. |
Entity row | time from now | entity.hasRow('enrollment', "startDate <= timeFromNow('now') && endDate >= timeFromNow('now')") | Users with an enrollment row where the start date is on or before now, and the end date is on or after now — i.e. an enrollment that is currently in effect. timeFromNow can be used inside hasRow for row column comparisons (<, <=, >, >=, ==, !=) and inside hasAttributeBetween. |
Member attribute | subject source id | member.subjectSourceId == 'jdbc' | Members from a particular subject source. |