diff --git a/trigger-actions-framework/main/default/classes/ActionUtility.cls b/trigger-actions-framework/main/default/classes/ActionUtility.cls new file mode 100644 index 0000000..3de45ac --- /dev/null +++ b/trigger-actions-framework/main/default/classes/ActionUtility.cls @@ -0,0 +1,75 @@ +/** + * Utilities for managing actions. + * + * Allows for the retrieval of the current user's permissions only once during the entire transaction + * Consolidates code that is shared between MetadataTriggerHandler and FinalizerHandler classes + */ + +public with sharing class ActionUtility { + @TestVisible + public static Set userPermissions { + get { + if (userPermissions == null) { + userPermissions = new Set(); + + List permSets = [ + SELECT AssigneeId, Assignee.FirstName, PermissionSetId, PermissionSet.Name + FROM PermissionSetAssignment + WHERE AssigneeId = :UserInfo.getUserId() + ]; + Set permSetIds = new Set(); + for (PermissionSetAssignment psa : permSets) { + permSetIds.add(psa.PermissionSetId); + } + List customPerms = [ + SELECT DeveloperName, MasterLabel, NamespacePrefix + FROM CustomPermission + WHERE Id IN (SELECT SetupEntityId FROM SetupEntityAccess WHERE ParentId IN :permSetIds) + ]; + for (CustomPermission cp : customPerms) { + String qualifiedPerm = String.isBlank(cp.NamespacePrefix) + ? cp.DeveloperName + : cp.NamespacePrefix + '__' + cp.DeveloperName; + userPermissions.add(qualifiedPerm); + } + } + return userPermissions; + } + private set; + } + + public static boolean isNotBypassed(String requiredPermission, String bypassPermission) { + return !((requiredPermission != null && userPermissions.contains(requiredPermission)) || + (bypassPermission != null && !userPermissions.contains(bypassPermission))); + } + + public static Object getActionInstance(String className) { + String namespace = ''; + System.Type actionType = Type.forName(className); + /** Type.forName(fullyQualifiedName) allowed some messyness and ambiguity in dealing with namespace + * If config does not provide the correct namespace (likely if upgrading from older versions of this framework) we need to fallback in two scenarios + * - package and class namespaced but namespace wasn't specified + * - namespace is actually in the class field in the form namespace.classname + */ + // try shared Namespace + if (actionType == null) { + // Get the namespace of the current class. + String[] parts = String.valueOf(ActionUtility.class).split('\\.', 2); + namespace = parts.size() == 2 ? parts[0] : ''; + + // try again with the new namespace + actionType = Type.forName(namespace, className); + } + // try namespace in Class_Name field + if (actionType == null) { + String[] parts = className.split('\\.', 2); + if (parts.size() == 2) { + namespace = parts[0]; + className = parts[1]; + actionType = Type.forName(namespace, className); + } + } + Object dynamicInstance = actionType.newInstance(); + return dynamicInstance; + } +} diff --git a/trigger-actions-framework/main/default/classes/ActionUtility.cls-meta.xml b/trigger-actions-framework/main/default/classes/ActionUtility.cls-meta.xml new file mode 100644 index 0000000..7d5f9e8 --- /dev/null +++ b/trigger-actions-framework/main/default/classes/ActionUtility.cls-meta.xml @@ -0,0 +1,5 @@ + + + 61.0 + Active + \ No newline at end of file diff --git a/trigger-actions-framework/main/default/classes/FinalizerHandler.cls b/trigger-actions-framework/main/default/classes/FinalizerHandler.cls index a038a4a..d1b1cc6 100644 --- a/trigger-actions-framework/main/default/classes/FinalizerHandler.cls +++ b/trigger-actions-framework/main/default/classes/FinalizerHandler.cls @@ -58,9 +58,6 @@ public with sharing virtual class FinalizerHandler { @TestVisible private static Set bypassedFinalizers = new Set(); - @TestVisible - private static Map permissionMap = new Map(); - /** * @description Bypass the execution of a specific finalizer. * @param finalizer The name of the finalizer to bypass. @@ -103,10 +100,8 @@ public with sharing virtual class FinalizerHandler { if (finalizerMetadata.Bypass_Execution__c) { return; } - populatePermissionMap(finalizerMetadata.Bypass_Permission__c); - populatePermissionMap(finalizerMetadata.Required_Permission__c); if ( - isNotBypassed( + ActionUtility.isNotBypassed( finalizerMetadata.Bypass_Permission__c, finalizerMetadata.Required_Permission__c ) @@ -131,7 +126,7 @@ public with sharing virtual class FinalizerHandler { return; } try { - dynamicInstance = Type.forName(className).newInstance(); + dynamicInstance = ActionUtility.getActionInstance(className); } catch (System.NullPointerException e) { handleFinalizerException(INVALID_CLASS_ERROR_FINALIZER, className); } @@ -171,34 +166,6 @@ public with sharing virtual class FinalizerHandler { ); } - /** - * @description Check if the finalizer is not bypassed. - * @param requiredPermission The required permission. - * @param bypassPermission The bypass permission. - * @return True if the finalizer is not bypassed, false otherwise. - */ - private boolean isNotBypassed( - String requiredPermission, - String bypassPermission - ) { - return !((requiredPermission != null && - permissionMap.get(requiredPermission)) || - (bypassPermission != null && !permissionMap.get(bypassPermission))); - } - - /** - * @description Populate the permission map. - * @param permissionName The permission name. - */ - private void populatePermissionMap(String permissionName) { - if (permissionName != null && !permissionMap.containsKey(permissionName)) { - permissionMap.put( - permissionName, - FeatureManagement.checkPermission(permissionName) - ); - } - } - @TestVisible private List allFinalizers { get { diff --git a/trigger-actions-framework/main/default/classes/FinalizerHandlerTest.cls b/trigger-actions-framework/main/default/classes/FinalizerHandlerTest.cls index 637fbb8..488e9ac 100644 --- a/trigger-actions-framework/main/default/classes/FinalizerHandlerTest.cls +++ b/trigger-actions-framework/main/default/classes/FinalizerHandlerTest.cls @@ -119,7 +119,7 @@ private with sharing class FinalizerHandlerTest { ) }; - FinalizerHandler.permissionMap.put(BYPASS_PERMISSION, true); + ActionUtility.userPermissions.add(BYPASS_PERMISSION); handler.handleDynamicFinalizers(); System.Assert.isTrue( @@ -138,7 +138,7 @@ private with sharing class FinalizerHandlerTest { ) }; - FinalizerHandler.permissionMap.put(REQUIRED_PERMISSION, false); + ActionUtility.userPermissions = new Set(); handler.handleDynamicFinalizers(); System.Assert.isTrue( diff --git a/trigger-actions-framework/main/default/classes/MetadataTriggerHandler.cls b/trigger-actions-framework/main/default/classes/MetadataTriggerHandler.cls index a5e871b..873a44c 100644 --- a/trigger-actions-framework/main/default/classes/MetadataTriggerHandler.cls +++ b/trigger-actions-framework/main/default/classes/MetadataTriggerHandler.cls @@ -109,8 +109,6 @@ public inherited sharing class MetadataTriggerHandler extends TriggerBase implem private static Set bypassedActions = new Set(); @TestVisible private static Selector selector = new Selector(); - @TestVisible - private static Map permissionMap = new Map(); private static Map>> sObjectToContextToActions = new Map>>(); @TestVisible private static FinalizerHandler finalizerHandler = new FinalizerHandler(); @@ -225,20 +223,6 @@ public inherited sharing class MetadataTriggerHandler extends TriggerBase implem finalizerHandler.handleDynamicFinalizers(); } - /** - * @description Populate the permission map. - * - * @param permissionName The name of the permission to check. - */ - private void populatePermissionMap(String permissionName) { - if (permissionName != null && !permissionMap.containsKey(permissionName)) { - permissionMap.put( - permissionName, - FeatureManagement.checkPermission(permissionName) - ); - } - } - /** * @description Get the Trigger Action metadata. * @@ -311,38 +295,12 @@ public inherited sharing class MetadataTriggerHandler extends TriggerBase implem relationshipName + RELATIONSHIP_SUFFIX )) .get(sObject_Trigger_Setting__mdt.Required_Permission__c); - for ( - String permissionName : new List{ - actionMetadata.Bypass_Permission__c, - actionMetadata.Required_Permission__c, - sObjectBypassPermissionName, - sObjectRequiredPermissionName - } - ) { - populatePermissionMap(permissionName); - } - return isNotBypassed( + return ActionUtility.isNotBypassed( actionMetadata.Bypass_Permission__c, actionMetadata.Required_Permission__c ) && - isNotBypassed(sObjectBypassPermissionName, sObjectRequiredPermissionName); - } - - /** - * @description Check if the Trigger Action is not bypassed. - * - * @param requiredPermission The required permission for the Trigger Action. - * @param bypassPermission The bypass permission for the Trigger Action. - * @return True if the Trigger Action is not bypassed, false otherwise. - */ - private static boolean isNotBypassed( - String requiredPermission, - String bypassPermission - ) { - return !((requiredPermission != null && - permissionMap.get(requiredPermission)) || - (bypassPermission != null && !permissionMap.get(bypassPermission))); + ActionUtility.isNotBypassed(sObjectBypassPermissionName, sObjectRequiredPermissionName); } /** @@ -385,8 +343,7 @@ public inherited sharing class MetadataTriggerHandler extends TriggerBase implem for (Trigger_Action__mdt triggerMetadata : actionMetadata) { Object triggerAction; try { - triggerAction = Type.forName(triggerMetadata.Apex_Class_Name__c) - .newInstance(); + triggerAction = ActionUtility.getActionInstance(triggerMetadata.Apex_Class_Name__c); if (triggerMetadata.Flow_Name__c != null) { ((TriggerActionFlow) triggerAction) .flowName = triggerMetadata.Flow_Name__c; diff --git a/trigger-actions-framework/main/default/classes/MetadataTriggerHandlerTest.cls b/trigger-actions-framework/main/default/classes/MetadataTriggerHandlerTest.cls index b089be2..0ad9648 100644 --- a/trigger-actions-framework/main/default/classes/MetadataTriggerHandlerTest.cls +++ b/trigger-actions-framework/main/default/classes/MetadataTriggerHandlerTest.cls @@ -626,7 +626,7 @@ private class MetadataTriggerHandlerTest { new List{ action } ); - MetadataTriggerHandler.permissionMap.clear(); + ActionUtility.userPermissions = new Set(); handler.beforeInsert(handler.triggerNew); @@ -641,7 +641,7 @@ private class MetadataTriggerHandlerTest { new List{ action } ); - MetadataTriggerHandler.permissionMap.clear(); + ActionUtility.userPermissions = new Set(); handler.beforeInsert(handler.triggerNew); @@ -654,9 +654,7 @@ private class MetadataTriggerHandlerTest { MetadataTriggerHandler.selector = new FakeSelector( new List{ action } ); - MetadataTriggerHandler.permissionMap = new Map{ - REQUIRED_PERMISSION => false - }; + ActionUtility.userPermissions = new Set(); handler.beforeInsert(handler.triggerNew); @@ -669,9 +667,7 @@ private class MetadataTriggerHandlerTest { MetadataTriggerHandler.selector = new FakeSelector( new List{ action } ); - MetadataTriggerHandler.permissionMap = new Map{ - REQUIRED_PERMISSION => false - }; + ActionUtility.userPermissions = new Set(); handler.beforeInsert(handler.triggerNew); @@ -684,9 +680,7 @@ private class MetadataTriggerHandlerTest { MetadataTriggerHandler.selector = new FakeSelector( new List{ action } ); - MetadataTriggerHandler.permissionMap = new Map{ - BYPASS_PERMISSION => true - }; + ActionUtility.userPermissions = new Set{BYPASS_PERMISSION}; handler.beforeInsert(handler.triggerNew); @@ -699,9 +693,7 @@ private class MetadataTriggerHandlerTest { MetadataTriggerHandler.selector = new FakeSelector( new List{ action } ); - MetadataTriggerHandler.permissionMap = new Map{ - BYPASS_PERMISSION => true - }; + ActionUtility.userPermissions = new Set{BYPASS_PERMISSION}; handler.beforeInsert(handler.triggerNew);