Grouper ABAC with scripted groups

Grouper ABAC with scripted groups

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

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
% (percent) matches any zero or more any characters
_ (underscore) matches exactly one any character
\\ (double backslash) escapes the next percent, underscore, or backslash
\\\\ (quadruple backslash) literal backslash
\' (backslash single quote) literal single quote in single quoted string
\" (backslash double quote) literal double quote in double quoted string

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',
"affiliationCode != '01234'  ")

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 <  >  ==  <=  >=  !=
In Grouper v6.1.2+, v7.1.0+

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 <  >  ==  <=  >=  !=
Note: the math can be on the left or right or both of the operator.  The scalar must be to the right of the column data field (i.e. 5 + affiliationDeptNumber is not allowed.  Multiple math operations or parens or other things are not allowed.
In Grouper v6.1.2+, v7.1.0+

Entity row

range of strings or ints

entity.hasRow('affiliation', "
hasAttributeBetween(
  '0200' <= affiliationDeptNumber,
  affiliationDeptNumber < '0248'
)")

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.
In Grouper v6.1.2+, v7.1.0+

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.