Look, there’s no two ways about it — permission sets are not that exciting. No one jumps out of bed on Monday looking forward to building a permission set. So, why write a post about permission sets and permission set groups? Because permission set groups just got a whole lot more useful! We’ve made permission set group assignments re-calculable in unit tests. Read on to learn why that’s awesome.

Quick recap: What’s a permission set group?

Alright, quick recap. A permission set is a collection of settings and permissions that give users access to various tools and functions. Permission sets extend users’ functional access without changing their profiles. In fact, you can even largely replace the use of profiles by combining the standard minimum-access profile with permission sets. These permission sets are then assigned to selected users.

Permission sets are a bit like classes; they should be focused on a single area of responsibility. For instance, let’s look at users who are assigned to the Revenue Forecasting group. You might create a permission set that grants them read access to the Revenue fields on all opportunities. Because this permission is only needed by revenue forecasters, you wouldn’t want to combine those permissions with the rest of the permissions for the Opportunities group.

This inevitably leads to lots of permission sets in your org. It’s likely that any given user will need to be assigned to a number of permission sets! This can be a bit of an administrative click-wait-click-wait fest, so Salesforce introduced Permission Set Groups. As the name may imply, a permission set group is a collection of permission sets that can be assigned all at once. One way to think about how permission set groups work is the analogy of a persona. You first build individual permission sets for a given job task, such as updating contact information. You then establish a permission set group of these individual job tasks to define the persona, such as “Account Executive.”

This Venn diagram shows the relationship between permission sets and permission set groups. The red, yellow, and blue circle segments reference individual permission sets — in this case, permission sets for access to Account, Opportunity, and Revenue schedules. The gray, purple, and green segments represent permission set groups, or personas, that are defined as the union of those permission sets. Note that the orange segment doesn’t represent an existing persona; in this example, no one needs access to Account and Revenue schedules without also needing Opportunity access.

The problem

Permission set groups have been around for a few releases, but until now, they’ve had a bit of an issue. Developers writing unit tests that use permission set groups could assign them to a user for testing. Unfortunately, those tests would fail as if the permission set group assignment was never made. This, of course, is… sub-optimal. If you can’t test it in your code, it’s hard to recommend them for adoption in your org.

@isTest public class PSGFAIL {
  @isTest static void testThisFailsSadly() {
    // get the PSG by name (may have been modified in deployment)
    PermissionSetGroup psg = [SELECT Id, Status 
                                FROM PermissionSetGroup
                                WHERE DeveloperName='SalesPersona'
                             ];
                             
    // Create a TestUser, using helper method from Apex Recipes
    User testUser = TestFactory.createMinAccessUser(true);
    
    // assign PSG to current user (this fails if PSG is Outdated)
    insert new PermissionSetAssignment(PermissionSetGroupId = psg.Id, AssigneeId = UserInfo.getUserId());
    Boolean hasAccess = false;
    System.runAs(testUser){
        // This uses the CanTheUser library from Apex Recipes
        hasAccess = CanTheUser.flsAccessible('Account', 'TradeStyle');
    }
   
    /**
     * Sadly this assertion fails prior to Spring '22!
     */
    System.assert(hasAccess, 'Expected a min-access profile with the Sales Persona PSG to have access to tradeStyle'); } }

The solution

The braintrust of Cheryl Feldman, Chris Peterson, Daniel Ballinger, and their team of engineers saw this problem, and in Spring ’22, they provide us with a solution in the form of a new method in the Test namespace. Given a permission set group ID, Test.calculatePermissionSetGroup(psg.Id); recalculates the permissions for that permission set group, so they are properly applied when assigned! Combined with the Status field of the permission set group record, you can optimize your tests to trigger recalculation only when necessary. Let’s look at fixing our above unit test with a call to recalculate the permission set group.

@isTest public class PSG {
  @isTest static void testRecalculatePSG() {
    // get the PSG by name (may have been modified in deployment)
    PermissionSetGroup psg = [SELECT Id, Status 
                                FROM PermissionSetGroup
                                WHERE DeveloperName='SalesPersona'
                             ];
    
    // force calculation of the PSG if it is not already Updated
    if (psg.Status != 'Updated') {
      Test.calculatePermissionSetGroup(psg.Id);
    }
    
    // Create a TestUser, using helper method from Apex Recipes
    User testUser = TestFactory.createMinAccessUser(true);
    
    // assign PSG to current user (this fails if PSG is Outdated)
    insert new PermissionSetAssignment(PermissionSetGroupId = psg.Id, AssigneeId = UserInfo.getUserId());
    Boolean hasAccess = false;
    System.runAs(testUser){
        // This uses the CanTheUser library from Apex Recipes
        hasAccess = CanTheUser.flsAccessible('Account', 'TradeStyle');     }         /**      * Success with Spring '22!      */     System.assert(hasAccess, 'Expected a min-access profile with the Sales Persona PSG to have access to tradeStyle'); } }

Given that recalculating the permission set group can take a second, it’s best practice to check the status field before making the call to calculate.

You can even create and insert new permission set group records within your unit tests! Check out this snippet:

// create a PS with a user perm enabled
PermissionSet ps = new PermissionSet(Name='apexPS', Label='apexPS');
ps.PermissionsIsSsoEnabled = true;
insert ps;

// create a PSG and add PS as a component
PermissionSetGroup psg = new PermissionSetGroup(DeveloperName='apexPSG', MasterLabel='apexPSG');
insert psg;

// add the PS to the PSG
insert new PermissionSetGroupComponent(PermissionSetGroupId = psg.Id, PermissionSetId = ps.Id);

// force synchronous calculation
Test.calculatePermissionSetGroup(psg.Id);
    
// assign PSG to a user (this would fail if PSG was not previously calculated)
insert new PermissionSetAssignment(PermissionSetGroupId = psg.Id, AssigneeId = UserInfo.getUserId());

What do I do with this?

I have to admit that permission sets and permission set groups aren’t the most exciting topic. But they are crucial to secure software development on the Salesforce Platform. But just using permission sets and permission set groups isn’t enough. You need to know your code honors these permission sets and how it reacts when it fails for insufficient access (or if it fails at all). Otherwise, you’ve just coded yourself a nice security hole!

Likewise, help your admin teammates by ensuring that when your code fails, at least it tells you what permission is missing. The easiest way to do this is to write unit tests, and that means writing tests that assign different permission set groups. Remember that your tests also contribute to Hammer testing, which helps to ensure that the platform doesn’t introduce breaking changes.

But wait! There’s more. Permission sets and permission set groups are getting even better. With Spring ‘22, two new features are in Beta: Auto-expiration of Permission Set Group assignments and Create Expanded Transaction Security Policies for Permission Set Events. I’m looking forward to seeing how orgs use auto expiring permission set group assignments.

Learn MOAR this week

Product Managers and Developer Relations are back to share the latest features and functionality to help you develop faster with new content from Developer Relations covering their favorite new features. Make sure to check out Release Readiness Live on Friday February 4th at 9:00 PST. Lastly, and keep an eye on the Salesforce Developers blog for more posts on Spring ’22!

To learn even more, check out the Spring ’22 trailmix.

About the author

public with sharing KevinPoorman {
public static String pronouns = 'he/him';
public static Double startedWithSalesforceAtApiLevel = 11.0;
public static String[] interests = ['Apex', 'Testing', 'iOS SDK', 'Generics', 'Metaprogramming'];
public static String funFact = 'Has two daughters he\'s training to take over the world.';
public static String twitterHandle = '@Codefriar';
}

Get the latest Salesforce Developer blog posts and podcast episodes via Slack or RSS.

Add to Slack Subscribe to RSS