In a recent TrailheaDX 2021 session, Salesforce announced a next-generation licensing and provisioning platform that will expand how ISV partners will be able to author, sell, and distribute their own license types for their solution. This new licensing platform is not yet available, however, so in this technical blog post, we’ll take a look at how per-user licensing currently behaves within managed packages and one possible approach to testing this behavior.

How does per-user licensing work in a managed package?

Per-user licensing restricts access to the data of a managed package, even if the user is granted access to objects and fields via permission sets. You configure Per-User licensing when associating your managed package to an LMA. This can be done before security review, but only after a managed released version of your package has been created.

However, access to managed package code is not restricted, so Lightning Web Components included on record pages will still show up, for instance. Additionally, AuraEnabled Apex code included in the managed package will still execute if access is granted via permission sets. If this Apex code accesses and delivers managed package data, it will be delivered to the end user, and describe results will say the user should have the access granted via permission sets.

So, if you need to restrict access to managed components, you need to do the enforcement yourself and design an error state, whether it’s a message, empty screen, or the ability to assign a license, which is possible in Apex like so:

PackageLicense pl = [SELECT Id FROM PackageLicense WHERE NamespacePrefix = ‘Test’];
User u = [SELECT Id FROM user WHERE FirstName = ‘Test’];
Insert new UserPackageLicense(UserId = u.id, PackageLicenseId = pl.id);

Programmatically checking for a package license

Salesforce exposes this method to check if a user has a license for a particular namespace:

System.UserInfo.isCurrentUserLicensed(NAMESPACE)

This method has the following behavior:

  • It throws an error if the package is not installed (code is deployed to any org):
    System.TypeException: Managed Package corresponding to namespace prefix not found
  • When a beta package version is installed, it always returns true.
  • When a released package version is installed, it checks the UserPackageLicense object to verify that a user does have a record for the associated PackageLicense in production orgs. It will still return true in scratch orgs and sandboxes.

You can also run this query to check yourself:
Select Id From UserPackageLicense Where PackageLicense.NamespacePrefix=’NAMESPACE’ And UserId=’Id’

This means that for development purposes, you will need a way to simulate a user license, as the only way to test the true behavior is to make a managed released package, associate it with your LMA and configure per-user licensing, install the released version of the package in a production org, and assign the licenses.

Note that if you have a multi-package same-namespace 2GP solution, you cannot distinguish between licenses assigned per-package since this only checks based on namespace, not the package’s name or id.

One way to test per-user licensing behavior in managed packages

One possible approach is to make a custom permission that negates the license and include it in a permission set. Both the custom permission and permission set will be unpackaged. Then, if the user has that custom permission, treat them as if they do not have a package license. This is the implementation that CodeScience used in a recent build for one of our clients, but is not the only possible approach.

public class Utils {
  /**
   * @description isRunningUserLicensed returns whether the running user is currently licensed
   * for the [COMPANY] app
   * @return   Boolean indicating whether the user is licensed
   */
     public static Boolean isRunningUserLicensed() {
   Boolean isLicensed;
   try {
       //When installed as a beta package, this returns true.
       //When installed as a released version, this is dependent on user license assignment
       String packageId = System.Packaging.getCurrentPackageId();
       isLicensed = System.UserInfo.isCurrentUserLicensedForPackage(packageId);
   } catch (Exception e) {
       // if source is deployed rather than a package install, this method will throw an error.
       // Treat like license is assigned
       isLicensed = true;
   }
   if (isLicensed) {
       // Add ability to override licensing for development and testing
       // before creating a released package version. Checks unpackaged
       // custom permission to simulate a missing user license.
       isLicensed = !FeatureManagement.checkPermission(‘[COMPANY]_Missing_License’);
   }
   return isLicensed;
  }
}
@isTest
public class UtilsTest {
  @isTest
  static void test_checking_license() {
   User u = UserTest.createUserWithPerms(new List<String>{ ‘[COMPANY]_Admin_Integration_Access’ });
   try {
       PackageLicense pl = [SELECT Id FROM PackageLicense WHERE NamespacePrefix = :Constants.NAMESPACE];
       UserPackageLicense upl = new UserPackageLicense(UserId = u.id, PackageLicenseId = pl.id);
       insert upl;
   } catch (Exception e) {
       // PackageLicense won’t exist unless installed
   }
   System.runAs(u) {
       System.assert(Utils.isRunningUserLicensed(), ‘License should default to true’);
      }
         try {
       UserTest.assignPermissionSet(‘[COMPANY]_Missing_License’, u.Id);
       System.runAs(u) {
           System.assert(!Utils.isRunningUserLicensed(), ‘License should be overridden to false’);
       }
  } catch (Exception e) {
       // Permission set is unpackaged, could be unavailable for assignment
   }
  }
}

We hope this information is helpful to you as you navigate per-user licensing in managed packages. Good luck and feel free to reach out if you have any questions!

Additional Resources

Salesforce SOAP API Developer Guide: UserPackageLicense object

Salesforce SOAP API Developer Guide: PackageLicense object



CodeScience has helped build more than 10% of the apps on the AppExchange. Contact us today to learn how we can help your business thrive.