September 13, 2024

IIQCommon overview: ThingAccessUtils to lock down every thing

This article is part of a blog series providing an overview of the many SailPoint IIQ utilities and tools available in Instrumental ID’s open-source iiq-common-public library. (For an overview of the entire library, see this earlier post.)

This post will be about the whimsically named ThingAccessUtils class, a part of IIQCommon’s security tools. This class implements the Common Security behavior behind most of Instrumental ID’s plugins, utilities, and other IIQ enhancements.

Checking access

At the most basic level, an access check requires:

  • Who is doing the action? – the subject
  • What are they doing it to? – the target
  • What permissions do they have? – the configuration

checkThingAccess

ThingAccessUtils exposes a variety of variations on a checkThingAccess method, with the most commonly used (at least in my code!) being this one:

public static boolean checkThingAccess(UserContext pluginContext, Identity targetIdentity, Map<String, Object> configuration) throws GeneralException {

The first argument can be supplied by a BasePluginResource (which implements UserContext), if you’re invoking the security check from a plugin. Otherwise, you can either create your own or use IIQCommon’s DummyAuthContext to simulate one.

The second argument is the target of the action. If you leave this null, ThingAccessUtils will use the subject user as the target. This seems odd, but is frequently what you want. There is often no actual target Identity, in practice.

The third argument is a Map containing Common Security parameters. (You can also build an actual CommonSecurityConfig object, if you wish. Sometimes it’s easier, and it’s certainly more type-safe!)

Common Security

Common Security permits the following properties to specify an access control, any or all of which can be in your Map. The properties are evaluated in the order listed below, with evaluation stopping after the first “denied” result. 

  • disabled: If set to true, access is denied for all users.

  • oneOf: Contains a nested list of Common Security maps and allows access if any spec in the list allows access.

  • allOf: Contains a nested list of Common Security maps and allows access if all of the specs in the list allow access.

  • not: Contains a single nested Common Security map and inverts it. Allows access only if the nested spec does not allow access.

  • settingOffSwitch: If the given plugin setting is true, access is not allowed for any user.

  • accessCheckScript: If the given Beanshell script returns boolean true, access is allowed. Otherwise, access is denied. The script will be passed an identity (the target identity being viewed) and requester (the subject identity doing the viewing).

  • accessCheckRule: If the specified Beanshell Rule returns boolean true, access is allowed. Otherwise, access is denied. The Rule will be passed an identity (the target identity being viewed) and requester (the subject identity doing the viewing).

  • requiredRights: Access is allowed if the subject user has any of the listed SPRights.

  • excludedRights: Access is allowed if the subject user has none of the listed SPRights.

  • requiredCapabilities: Access is allowed if the subject user has any of the listed Capabilities.

  • excludedCapabilities: Access is allowed if the subject user has none of the listed Capabilities.

  • requiredWorkgroups: Access is allowed if the subject user is in any of the listed workgroups.

  • excludedWorkgroups: Access is allowed if the subject user is in none of the listed workgroups.

  • accessCheckSelector: Access is allowed if the subject user matches the given IdentitySelector. This is where you would specify a Filter for access control, for example.

  • mirrorQuicklinkPopulation: Access is allowed if the combination of subject user and target user would be allowed by the given QuickLink Population.

  • validTargetExcludedRights: Access is allowed if the target user does not have any of the listed SPRights.

  • validTargetExcludedCapabilities: Access is allowed if the target user does not have any of the listed Capabilities.

  • invalidTargetFilter: Access is allowed if the action target does not match the filter string.

  • validTargetWorkgroups: Access is allowed if the action target is a member of any of the listed workgroups.

  • validTargetCapabilities: Access is allowed if the action target is assigned any of the listed capabilities.

  • validTargetSelector: Access is allowed if the action target matches the given selector.

  • validTargetFilter: Access is allowed if the action target matches the given filter string.

Other entries in your Map will simply be ignored, which – as you’ll see shortly – will simplify your code.

Putting it all together

It will be easiest to demonstrate with an example. The following is a Fancy Button specification from our UI Enhancer Plugin

Buttons are specified as Map entries in a Custom object. Each Map specifies both the behavior of a button and the security around it. Buttons will only be displayed if the user is allowed to see them. (Security is also double-checked a second time on button click.)

				
					<Map>
  <entry key="icon" value="fa-file-text-o"/>
  <entry key="order" value="10"/>
  <entry key="endpoint" value="idwAdminNotes"/>
  <entry key="validTargetFilter" value="correlated == true"/>
  <entry key="requiredRights" value="IDW_Enhanced_Attributes_AdminNotes"/>
  <entry key="title" value="Opens the Admin Notes panel"/>
  
  <!-- This plugin setting, if true, turns this button off -->
  <entry key="settingOffSwitch" value="disableAdminNotes"/>
</Map>
				
			

Of these entries, validTargetFilter, requiredRights, and settingOffSwitch are Common Security options, while the remainder specify Fancy Button metadata. 

Since ThingAccessUtils will ignore any properties that aren’t a part of Common Security, we can conveniently pass this Map as-is to determine whether the user can access this button. If the user does not have the IDW_Enhanced_Attributes_AdminNotes right, for example, checkThingAccess will return false.

				
					// In real life, this ID is passed as a Query Parameter to the REST API
Identity targetUser = context.getObject(Identity.class, targetIdentityId);

Map<String, Object> buttonConfig = readButtonConfig("idwAdminNotes");

// BasePluginResource implements UserContext, so we can just pass 'this'
boolean allowed = ThingAccessUtils.checkThingAccess(this, targetUser, buttonConfig);

if (allowed) {
  // Do the button action
}
				
			

Caching

Access checks can have a performance impact, what with all of those capability and IIQ filter and script checks. Since a person’s access to do or see a thing is unlikely to change frequently, ThingAccessUtil (or, really, AccessCheck) caches the outcome of a security check for one minute. A second check with the same subject, target, and Map will quickly produce the same outcome.

If you expect a person’s access to really change more frequently, or if you’re testing, you can disable caching by setting noCache as a Common Security property.

For testing, you can also clear the whole cache by invoking ThingAccessUtils.clearCachedResults(). Our Rule Runner plugin is a good place to do this.

The cache mechanism is multi-layered to account for the fact that this class is often included, in obfuscated form, along with our proprietary plugins. When a plugin is upgraded, IIQ does some Java ClassLoader magic to allow a hot-deploy of plugin classes. To avoid strange ClassCastExceptions, the cache studiously avoids returning cached objects if the plugin loader has changed or if they don’t appear to be a current class type.