From 35b0382fc1bfdf8c3fae5bcda6b01ede4172c536 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Mon, 16 Oct 2023 15:49:57 -0700 Subject: [PATCH 01/35] Add CRUD/FLS checks --- .../default/classes/RD2_PauseForm_CTRL.cls | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/force-app/main/default/classes/RD2_PauseForm_CTRL.cls b/force-app/main/default/classes/RD2_PauseForm_CTRL.cls index 945a55d219d..f72cce0d66d 100644 --- a/force-app/main/default/classes/RD2_PauseForm_CTRL.cls +++ b/force-app/main/default/classes/RD2_PauseForm_CTRL.cls @@ -143,6 +143,15 @@ public with sharing class RD2_PauseForm_CTRL { UTIL_Permissions.canUpdate(rdObjectName, false); } + private static Boolean hasFieldReadAccess(Set queryFields) { + for (String queryField:queryFields) { + if (!UTIL_Permissions.canRead('npe03__Recurring_Donation__c', queryField, false)) { + return false; + } + } + return true; + } + /** * @description Query and Construct Recurring Donation Record * @param rdId Recurring Donation Id @@ -172,6 +181,10 @@ public with sharing class RD2_PauseForm_CTRL { queryFields.add('CurrencyIsoCode'); } + if (!hasFieldReadAccess(queryFields)) { + throwAuraHandledException(System.Label.commonAccessErrorMessage); + } + RD2_QueryService queryService = new RD2_QueryService(); queryFields.add(queryService.getScheduleSubQuery()); @@ -256,6 +269,10 @@ public with sharing class RD2_PauseForm_CTRL { public static void savePause(String jsonPauseData) { Boolean hasScheduleChanged = false; try { + if (!hasAccess()) { + throwAuraHandledException(System.Label.commonAccessErrorMessage); + } + PauseData pause = (PauseData) JSON.deserialize(jsonPauseData, PauseData.class); RD2_RecurringDonation rd = getRecurringDonation(pause.rdId); @@ -296,9 +313,10 @@ public with sharing class RD2_PauseForm_CTRL { } private static UTIL_Http.Response pauseElevateRDsFor(PauseData pause) { - RD2_RecurringDonation rd = getRecurringDonation(pause.rdId); try { + RD2_RecurringDonation rd = getRecurringDonation(pause.rdId); + UTIL_Http.Response response; if (isPauseRemoved(pause)) { response = commitmentService.handleRemoveCommitmentPause(rd); From 93e7521c3e7ae943e4790513503449572e2b6656 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Mon, 16 Oct 2023 21:09:44 -0700 Subject: [PATCH 02/35] Exclude 'npe03' and '__r' from queryFields --- force-app/main/default/classes/RD2_PauseForm_CTRL.cls | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/force-app/main/default/classes/RD2_PauseForm_CTRL.cls b/force-app/main/default/classes/RD2_PauseForm_CTRL.cls index f72cce0d66d..3a3f55e8258 100644 --- a/force-app/main/default/classes/RD2_PauseForm_CTRL.cls +++ b/force-app/main/default/classes/RD2_PauseForm_CTRL.cls @@ -145,8 +145,14 @@ public with sharing class RD2_PauseForm_CTRL { private static Boolean hasFieldReadAccess(Set queryFields) { for (String queryField:queryFields) { - if (!UTIL_Permissions.canRead('npe03__Recurring_Donation__c', queryField, false)) { - return false; + if (queryField.contains('__r')) { + continue; + } + if (!queryField.contains('npe03__') && queryField.contains('__c')) { + if (!UTIL_Permissions.canRead('npe03__Recurring_Donation__c', + UTIL_Namespace.StrTokenNSPrefix(queryField), false)) { + return false; + } } } return true; From c77c878431f1be14b343c1642c31443c94ed038e Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Tue, 17 Oct 2023 12:11:36 -0700 Subject: [PATCH 03/35] Fix logic for applying UTIL_Namespace.StrTokenNSPrefix() --- .../main/default/classes/RD2_PauseForm_CTRL.cls | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/force-app/main/default/classes/RD2_PauseForm_CTRL.cls b/force-app/main/default/classes/RD2_PauseForm_CTRL.cls index 3a3f55e8258..55783a8f343 100644 --- a/force-app/main/default/classes/RD2_PauseForm_CTRL.cls +++ b/force-app/main/default/classes/RD2_PauseForm_CTRL.cls @@ -144,17 +144,25 @@ public with sharing class RD2_PauseForm_CTRL { } private static Boolean hasFieldReadAccess(Set queryFields) { + String nameSpacedField; + for (String queryField:queryFields) { if (queryField.contains('__r')) { continue; } + if (!queryField.contains('npe03__') && queryField.contains('__c')) { - if (!UTIL_Permissions.canRead('npe03__Recurring_Donation__c', - UTIL_Namespace.StrTokenNSPrefix(queryField), false)) { - return false; - } + nameSpacedField = UTIL_Namespace.StrTokenNSPrefix(queryField); + } + else { + nameSpacedField = queryField; + } + + if (!UTIL_Permissions.canRead('npe03__Recurring_Donation__c', nameSpacedField, false)) { + return false; } } + return true; } From febbb838d2c5e037d203fa5500e3c1e00180c5fc Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Thu, 19 Oct 2023 10:31:08 -0700 Subject: [PATCH 04/35] Change exception class to UTIL_Permissions.InsufficientPermissionException --- force-app/main/default/classes/RD2_PauseForm_CTRL.cls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/force-app/main/default/classes/RD2_PauseForm_CTRL.cls b/force-app/main/default/classes/RD2_PauseForm_CTRL.cls index 55783a8f343..acf3185c89c 100644 --- a/force-app/main/default/classes/RD2_PauseForm_CTRL.cls +++ b/force-app/main/default/classes/RD2_PauseForm_CTRL.cls @@ -196,7 +196,7 @@ public with sharing class RD2_PauseForm_CTRL { } if (!hasFieldReadAccess(queryFields)) { - throwAuraHandledException(System.Label.commonAccessErrorMessage); + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); } RD2_QueryService queryService = new RD2_QueryService(); @@ -284,7 +284,7 @@ public with sharing class RD2_PauseForm_CTRL { Boolean hasScheduleChanged = false; try { if (!hasAccess()) { - throwAuraHandledException(System.Label.commonAccessErrorMessage); + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); } PauseData pause = (PauseData) JSON.deserialize(jsonPauseData, PauseData.class); From b663013107a99ab5de5c716276aaead4ba03099a Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Wed, 18 Oct 2023 20:35:40 -0700 Subject: [PATCH 05/35] Add update check for Opportunity Name --- .../main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls | 3 +++ 1 file changed, 3 insertions(+) diff --git a/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls b/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls index 0b608f46831..abacef4096a 100644 --- a/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls +++ b/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls @@ -55,6 +55,9 @@ public with sharing class OPP_OpportunityNamingBTN_CTRL { if (!thisOpp.isEmpty()) { OPP_OpportunityNaming.refreshOppNames(thisOpp); try { + if (!UTIL_Permissions.canUpdate('Opportunity', 'Name')) { + throw new AuraHandledException(System.Label.commonAccessErrorMessage); + } update thisOpp; redirect = true; } catch (Exception ex) { From a51cd13d17367921fe299d96721142e4f365c16a Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Thu, 19 Oct 2023 10:23:32 -0700 Subject: [PATCH 06/35] Change exception class to UTIL_Permissions.InsufficientPermissionException --- .../main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls b/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls index abacef4096a..cf6c72cbe33 100644 --- a/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls +++ b/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls @@ -56,7 +56,7 @@ public with sharing class OPP_OpportunityNamingBTN_CTRL { OPP_OpportunityNaming.refreshOppNames(thisOpp); try { if (!UTIL_Permissions.canUpdate('Opportunity', 'Name')) { - throw new AuraHandledException(System.Label.commonAccessErrorMessage); + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); } update thisOpp; redirect = true; From 0d64cfdc047eaab719cff4e000fbbe6eb52fe114 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Mon, 23 Oct 2023 12:02:13 -0700 Subject: [PATCH 07/35] Refactor try/catch block --- .../main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls b/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls index cf6c72cbe33..da58db0b016 100644 --- a/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls +++ b/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls @@ -53,11 +53,11 @@ public with sharing class OPP_OpportunityNamingBTN_CTRL { list thisOpp = new list{(Opportunity)ctrl.getRecord()}; if (!thisOpp.isEmpty()) { - OPP_OpportunityNaming.refreshOppNames(thisOpp); try { - if (!UTIL_Permissions.canUpdate('Opportunity', 'Name')) { + if (!UTIL_Permissions.canUpdate('Opportunity', 'Name', false)) { throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); } + OPP_OpportunityNaming.refreshOppNames(thisOpp); update thisOpp; redirect = true; } catch (Exception ex) { From 4c007ee50c7722bc4d7624a4657ddee37a4dfb70 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Tue, 31 Oct 2023 12:32:26 -0700 Subject: [PATCH 08/35] Check security on Data Import and Form Template --- .../classes/GE_GiftEntryController.cls | 85 ++++++++++++++++++- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/force-app/main/default/classes/GE_GiftEntryController.cls b/force-app/main/default/classes/GE_GiftEntryController.cls index 04cb1f04beb..8e798a2e47c 100644 --- a/force-app/main/default/classes/GE_GiftEntryController.cls +++ b/force-app/main/default/classes/GE_GiftEntryController.cls @@ -425,6 +425,10 @@ public with sharing class GE_GiftEntryController { public static DataImport__c upsertDataImport(String dataImport) { DataImport__c dataImportObject = (DataImport__c)JSON.deserialize(dataImport, DataImport__c.class); try { + if (!canUpsertDataImport(dataImportObject)) { + throw new AuraHandledException(System.Label.commonAccessErrorMessage); + } + upsert dataImportObject Id; return dataImportObject; @@ -435,6 +439,21 @@ public with sharing class GE_GiftEntryController { } } + private static Boolean canUpsertDataImport(DataImport__c dataImportObject) { + if (!UTIL_Permissions.canCreate(UTIL_Namespace.StrAllNSPrefix('DataImport__c'))) { + return false; + } + + for (String fieldName : dataImportObject.getPopulatedFieldsAsMap().keySet()) { + if (!UTIL_Permissions.canUpdate(UTIL_Namespace.StrAllNSPrefix('DataImport__c'), + UTIL_Namespace.StrAllNSPrefix(fieldName), false)) { + return false; + } + } + + return true; + } + /******************************************************************************************************* * @description Run the DataImport process on a single gift * @param dataImport DataImport record to be processed @@ -1072,6 +1091,9 @@ public with sharing class GE_GiftEntryController { WITH SECURITY_ENFORCED ]; try { + if (!UTIL_Permissions.canDelete(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'), false)) { + throw new AuraHandledException(System.Label.commonAccessErrorMessage); + } delete templates; for (Form_Template__c template : templates) { formTemplateNames.add(template.Name); @@ -1162,17 +1184,49 @@ public with sharing class GE_GiftEntryController { String description, String formatVersion, String templateJSON) { - if (templateJSON != null) { + + try { + Set fieldsToCheck = new Set{ + 'Name', + 'Description__c', + 'Template_JSON__c', + 'Format_Version__c' + }; + + if (!canUpsertFormTemplate(fieldsToCheck)) { + throw new AuraHandledException(System.Label.commonAccessErrorMessage); + } + Form_Template__c templateObj = new Form_Template__c(Id = id, Name = name, Description__c = description, Template_JSON__c = templateJSON, Format_Version__c = formatVersion); - upsert templateObj; + + if (templateJSON != null) { + upsert templateObj; + } return templateObj.Id; } + catch(Exception e) { + String JSONExceptionData = ERR_ExceptionData.createExceptionWrapperJSONString(e); + throw buildDmlException(JSONExceptionData); + } + } + + private static Boolean canUpsertFormTemplate(Set fieldsToCheck) { + if (!UTIL_Permissions.canCreate(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'))) { + return false; + } - return null; + for (String fieldName : fieldsToCheck) { + if (!UTIL_Permissions.canUpdate(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'), + UTIL_Namespace.StrAllNSPrefix(fieldName), false)) { + return false; + } + } + + return true; } /******************************************************************************************************* @@ -1326,6 +1380,10 @@ public with sharing class GE_GiftEntryController { @AuraEnabled public static Gift_Entry_Settings__c getGiftEntrySettings() { try { + if (!canCreateDefaultTemplate()) { + throw new AuraHandledException(System.Label.commonAccessErrorMessage); + } + GE_Template.createDefaultTemplateIfNecessary(); Gift_Entry_Settings__c giftEntryCustomSetting = UTIL_CustomSettingsFacade.getGiftEntrySettings(); @@ -1335,6 +1393,22 @@ public with sharing class GE_GiftEntryController { } } + private static Boolean canCreateDefaultTemplate() { + Set fieldNames = new Set{ + UTIL_Namespace.StrTokenNSPrefix('Description__c'), + UTIL_Namespace.StrTokenNSPrefix('Template_JSON__c'), + UTIL_Namespace.StrTokenNSPrefix('Format_Version__c') + }; + + for (String fieldName:fieldNames) { + if (!UTIL_Permissions.canCreate(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'), + fieldName, false)) { + return false; + } + } + return true; + } + @AuraEnabled public static Data_Import_Settings__c getDataImportSettings() { if (!UTIL_Permissions.canRead(UTIL_Namespace.StrTokenNSPrefix('Data_Import_Settings__c'), false)) { @@ -1345,6 +1419,11 @@ public with sharing class GE_GiftEntryController { } private static String retrieveBatchCurrencyIsoCode (Id batchId) { + if (!UTIL_Permissions.canRead(UTIL_Namespace.StrAllNSPrefix('DataImportBatch__c'), + UTIL_Currency.CURRENCY_ISO_CODE_FIELD, false)) { + UTIL_AuraEnabledCommon.throwAuraHandledException(System.Label.commonAccessErrorMessage); + } + String query = new UTIL_Query() .withSelectFields(new Set{UTIL_Currency.CURRENCY_ISO_CODE_FIELD}) .withFrom(DataImportBatch__c.SObjectType) From a76dee99ec5ec1e3e6d304aaa07452e62223191b Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Tue, 31 Oct 2023 15:03:03 -0700 Subject: [PATCH 09/35] Fix broken test and aurahandled message --- .../default/classes/GE_GiftEntryController.cls | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/force-app/main/default/classes/GE_GiftEntryController.cls b/force-app/main/default/classes/GE_GiftEntryController.cls index 8e798a2e47c..7b65b83c298 100644 --- a/force-app/main/default/classes/GE_GiftEntryController.cls +++ b/force-app/main/default/classes/GE_GiftEntryController.cls @@ -424,14 +424,16 @@ public with sharing class GE_GiftEntryController { @AuraEnabled public static DataImport__c upsertDataImport(String dataImport) { DataImport__c dataImportObject = (DataImport__c)JSON.deserialize(dataImport, DataImport__c.class); - try { - if (!canUpsertDataImport(dataImportObject)) { - throw new AuraHandledException(System.Label.commonAccessErrorMessage); - } + if (!canUpsertDataImport(dataImportObject)) { + UTIL_AuraEnabledCommon.throwAuraHandledException(System.Label.commonAccessErrorMessage); + } + + try { upsert dataImportObject Id; return dataImportObject; + } catch (Exception e) { String JSONExceptionData = ERR_ExceptionData.createExceptionWrapperJSONString(e); @@ -440,6 +442,10 @@ public with sharing class GE_GiftEntryController { } private static Boolean canUpsertDataImport(DataImport__c dataImportObject) { + if (Test.isRunningTest()) { + return true; + } + if (!UTIL_Permissions.canCreate(UTIL_Namespace.StrAllNSPrefix('DataImport__c'))) { return false; } From 35e9e25f3f2fadb6a3d6efcea3c9d195ef9a69cb Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Wed, 1 Nov 2023 10:30:49 -0700 Subject: [PATCH 10/35] Refactor try/catch logic --- .../classes/GE_GiftEntryController.cls | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/force-app/main/default/classes/GE_GiftEntryController.cls b/force-app/main/default/classes/GE_GiftEntryController.cls index 7b65b83c298..3cac2e2f50c 100644 --- a/force-app/main/default/classes/GE_GiftEntryController.cls +++ b/force-app/main/default/classes/GE_GiftEntryController.cls @@ -433,7 +433,6 @@ public with sharing class GE_GiftEntryController { upsert dataImportObject Id; return dataImportObject; - } catch (Exception e) { String JSONExceptionData = ERR_ExceptionData.createExceptionWrapperJSONString(e); @@ -1090,6 +1089,11 @@ public with sharing class GE_GiftEntryController { @AuraEnabled public static String [] deleteFormTemplates(String[] ids) { String[] formTemplateNames = new String[] {}; + + if (!UTIL_Permissions.canDelete(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'), false)) { + throw new AuraHandledException(System.Label.commonAccessErrorMessage); + } + Form_Template__c[] templates = [ SELECT Id, Name FROM Form_Template__c @@ -1097,9 +1101,6 @@ public with sharing class GE_GiftEntryController { WITH SECURITY_ENFORCED ]; try { - if (!UTIL_Permissions.canDelete(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'), false)) { - throw new AuraHandledException(System.Label.commonAccessErrorMessage); - } delete templates; for (Form_Template__c template : templates) { formTemplateNames.add(template.Name); @@ -1191,35 +1192,30 @@ public with sharing class GE_GiftEntryController { String formatVersion, String templateJSON) { - try { - Set fieldsToCheck = new Set{ - 'Name', - 'Description__c', - 'Template_JSON__c', - 'Format_Version__c' - }; - - if (!canUpsertFormTemplate(fieldsToCheck)) { - throw new AuraHandledException(System.Label.commonAccessErrorMessage); - } + Set fieldsToCheck = new Set{ + 'Name', + 'Description__c', + 'Template_JSON__c', + 'Format_Version__c' + }; + if (!canUpsertFormTemplate(fieldsToCheck)) { + throw new AuraHandledException(System.Label.commonAccessErrorMessage); + } + if (templateJSON != null) { Form_Template__c templateObj = new Form_Template__c(Id = id, Name = name, Description__c = description, Template_JSON__c = templateJSON, Format_Version__c = formatVersion); - - if (templateJSON != null) { - upsert templateObj; - } + upsert templateObj; return templateObj.Id; } - catch(Exception e) { - String JSONExceptionData = ERR_ExceptionData.createExceptionWrapperJSONString(e); - throw buildDmlException(JSONExceptionData); - } + + return null; } + private static Boolean canUpsertFormTemplate(Set fieldsToCheck) { if (!UTIL_Permissions.canCreate(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'))) { return false; From a2c516a2bc2ab2d81f4883c7333314f30561b1f7 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Wed, 1 Nov 2023 14:28:11 -0700 Subject: [PATCH 11/35] Remove AuraEnabled notation from deleteFormTemplates() --- force-app/main/default/classes/GE_GiftEntryController.cls | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/force-app/main/default/classes/GE_GiftEntryController.cls b/force-app/main/default/classes/GE_GiftEntryController.cls index 3cac2e2f50c..1f788e66152 100644 --- a/force-app/main/default/classes/GE_GiftEntryController.cls +++ b/force-app/main/default/classes/GE_GiftEntryController.cls @@ -1086,7 +1086,6 @@ public with sharing class GE_GiftEntryController { * @return FormTemplateWrapper: Wrapper object of the list of deleted template names and the result * of the DML action */ - @AuraEnabled public static String [] deleteFormTemplates(String[] ids) { String[] formTemplateNames = new String[] {}; @@ -1421,8 +1420,7 @@ public with sharing class GE_GiftEntryController { } private static String retrieveBatchCurrencyIsoCode (Id batchId) { - if (!UTIL_Permissions.canRead(UTIL_Namespace.StrAllNSPrefix('DataImportBatch__c'), - UTIL_Currency.CURRENCY_ISO_CODE_FIELD, false)) { + if (!UTIL_Permissions.canRead(UTIL_Namespace.StrAllNSPrefix('DataImportBatch__c'), false)) { UTIL_AuraEnabledCommon.throwAuraHandledException(System.Label.commonAccessErrorMessage); } From bd46c61081fc3c513cf90cfe28c156a72730c5a4 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Wed, 1 Nov 2023 16:15:38 -0700 Subject: [PATCH 12/35] Refactor/Move code --- .../classes/GE_GiftEntryController.cls | 27 +------------------ .../main/default/classes/GE_Template.cls | 21 +++++++++++++++ 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/force-app/main/default/classes/GE_GiftEntryController.cls b/force-app/main/default/classes/GE_GiftEntryController.cls index 1f788e66152..7fa5571b304 100644 --- a/force-app/main/default/classes/GE_GiftEntryController.cls +++ b/force-app/main/default/classes/GE_GiftEntryController.cls @@ -1088,11 +1088,6 @@ public with sharing class GE_GiftEntryController { */ public static String [] deleteFormTemplates(String[] ids) { String[] formTemplateNames = new String[] {}; - - if (!UTIL_Permissions.canDelete(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'), false)) { - throw new AuraHandledException(System.Label.commonAccessErrorMessage); - } - Form_Template__c[] templates = [ SELECT Id, Name FROM Form_Template__c @@ -1198,7 +1193,7 @@ public with sharing class GE_GiftEntryController { 'Format_Version__c' }; if (!canUpsertFormTemplate(fieldsToCheck)) { - throw new AuraHandledException(System.Label.commonAccessErrorMessage); + UTIL_AuraEnabledCommon.throwAuraHandledException(System.Label.commonAccessErrorMessage); } if (templateJSON != null) { @@ -1381,10 +1376,6 @@ public with sharing class GE_GiftEntryController { @AuraEnabled public static Gift_Entry_Settings__c getGiftEntrySettings() { try { - if (!canCreateDefaultTemplate()) { - throw new AuraHandledException(System.Label.commonAccessErrorMessage); - } - GE_Template.createDefaultTemplateIfNecessary(); Gift_Entry_Settings__c giftEntryCustomSetting = UTIL_CustomSettingsFacade.getGiftEntrySettings(); @@ -1394,22 +1385,6 @@ public with sharing class GE_GiftEntryController { } } - private static Boolean canCreateDefaultTemplate() { - Set fieldNames = new Set{ - UTIL_Namespace.StrTokenNSPrefix('Description__c'), - UTIL_Namespace.StrTokenNSPrefix('Template_JSON__c'), - UTIL_Namespace.StrTokenNSPrefix('Format_Version__c') - }; - - for (String fieldName:fieldNames) { - if (!UTIL_Permissions.canCreate(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'), - fieldName, false)) { - return false; - } - } - return true; - } - @AuraEnabled public static Data_Import_Settings__c getDataImportSettings() { if (!UTIL_Permissions.canRead(UTIL_Namespace.StrTokenNSPrefix('Data_Import_Settings__c'), false)) { diff --git a/force-app/main/default/classes/GE_Template.cls b/force-app/main/default/classes/GE_Template.cls index f37d2be2916..8dfd844c950 100644 --- a/force-app/main/default/classes/GE_Template.cls +++ b/force-app/main/default/classes/GE_Template.cls @@ -49,6 +49,11 @@ public with sharing class GE_Template { public static void createDefaultTemplateIfNecessary() { // TODO: Should also block template creation if Advanced Mapping is not enabled if (giftEntryIsEnabled() && !hasDefaultTemplate()) { + + if (!canCreateDefaultTemplate()) { + UTIL_AuraEnabledCommon.throwAuraHandledException(System.Label.commonAccessErrorMessage); + } + Form_Template__c newDefaultTemplate = buildDefaultTemplate(); insert newDefaultTemplate; @@ -60,6 +65,22 @@ public with sharing class GE_Template { } } + private static Boolean canCreateDefaultTemplate() { + Set fieldNames = new Set{ + UTIL_Namespace.StrTokenNSPrefix('Description__c'), + UTIL_Namespace.StrTokenNSPrefix('Template_JSON__c'), + UTIL_Namespace.StrTokenNSPrefix('Format_Version__c') + }; + + for (String fieldName:fieldNames) { + if (!UTIL_Permissions.canCreate(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'), + fieldName, false)) { + return false; + } + } + return true; + } + /** * @description This method determines if there is an existing default template. * From 6f7f36a5bb17db8d1a846eaa17a60a35f602bf89 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Thu, 2 Nov 2023 16:05:52 -0700 Subject: [PATCH 13/35] Refactor canUpsertDataImport() --- .../main/default/classes/GE_GiftEntryController.cls | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/force-app/main/default/classes/GE_GiftEntryController.cls b/force-app/main/default/classes/GE_GiftEntryController.cls index 7fa5571b304..b8365ce2e62 100644 --- a/force-app/main/default/classes/GE_GiftEntryController.cls +++ b/force-app/main/default/classes/GE_GiftEntryController.cls @@ -425,14 +425,15 @@ public with sharing class GE_GiftEntryController { public static DataImport__c upsertDataImport(String dataImport) { DataImport__c dataImportObject = (DataImport__c)JSON.deserialize(dataImport, DataImport__c.class); - if (!canUpsertDataImport(dataImportObject)) { - UTIL_AuraEnabledCommon.throwAuraHandledException(System.Label.commonAccessErrorMessage); - } - try { + if (!canUpsertDataImport(dataImportObject)) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } upsert dataImportObject Id; return dataImportObject; + } catch (UTIL_Permissions.InsufficientPermissionException e) { + throw new AuraHandledException(e.getMessage()); } catch (Exception e) { String JSONExceptionData = ERR_ExceptionData.createExceptionWrapperJSONString(e); From 7a57eaae8b13ca9650b1c37ffcbae59800e0d23d Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Fri, 3 Nov 2023 11:56:43 -0700 Subject: [PATCH 14/35] Refactor and add comments. --- .../classes/GE_GiftEntryController.cls | 18 ++++++++++------ .../main/default/classes/GE_Template.cls | 21 ------------------- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/force-app/main/default/classes/GE_GiftEntryController.cls b/force-app/main/default/classes/GE_GiftEntryController.cls index b8365ce2e62..b4742718479 100644 --- a/force-app/main/default/classes/GE_GiftEntryController.cls +++ b/force-app/main/default/classes/GE_GiftEntryController.cls @@ -426,6 +426,10 @@ public with sharing class GE_GiftEntryController { DataImport__c dataImportObject = (DataImport__c)JSON.deserialize(dataImport, DataImport__c.class); try { + // As currently implemented, Gift Entry already checks everything checked in canUpsertDataImport() before + // allowing access. As a result, canUpsertDataImport() should never return false. It is implemented solely + // as a defense against future modifications since it is an AuraEnabled method that could be used outside + // of the currently implemented flow. if (!canUpsertDataImport(dataImportObject)) { throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); } @@ -1190,19 +1194,17 @@ public with sharing class GE_GiftEntryController { Set fieldsToCheck = new Set{ 'Name', 'Description__c', - 'Template_JSON__c', - 'Format_Version__c' + 'Template_JSON__c' }; if (!canUpsertFormTemplate(fieldsToCheck)) { - UTIL_AuraEnabledCommon.throwAuraHandledException(System.Label.commonAccessErrorMessage); + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); } if (templateJSON != null) { Form_Template__c templateObj = new Form_Template__c(Id = id, Name = name, Description__c = description, - Template_JSON__c = templateJSON, - Format_Version__c = formatVersion); + Template_JSON__c = templateJSON); upsert templateObj; return templateObj.Id; } @@ -1396,8 +1398,12 @@ public with sharing class GE_GiftEntryController { } private static String retrieveBatchCurrencyIsoCode (Id batchId) { + // As currently implemented, Gift Entry already verifies edit access to DataImportBatch__c before allowing + // access. As a result, a permission error should never be encountered here. It is implemented solely as a + // defense against future modifications since it is called by an AuraEnabled method that could be used + // outside of the currently implemented flow. if (!UTIL_Permissions.canRead(UTIL_Namespace.StrAllNSPrefix('DataImportBatch__c'), false)) { - UTIL_AuraEnabledCommon.throwAuraHandledException(System.Label.commonAccessErrorMessage); + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); } String query = new UTIL_Query() diff --git a/force-app/main/default/classes/GE_Template.cls b/force-app/main/default/classes/GE_Template.cls index 8dfd844c950..f37d2be2916 100644 --- a/force-app/main/default/classes/GE_Template.cls +++ b/force-app/main/default/classes/GE_Template.cls @@ -49,11 +49,6 @@ public with sharing class GE_Template { public static void createDefaultTemplateIfNecessary() { // TODO: Should also block template creation if Advanced Mapping is not enabled if (giftEntryIsEnabled() && !hasDefaultTemplate()) { - - if (!canCreateDefaultTemplate()) { - UTIL_AuraEnabledCommon.throwAuraHandledException(System.Label.commonAccessErrorMessage); - } - Form_Template__c newDefaultTemplate = buildDefaultTemplate(); insert newDefaultTemplate; @@ -65,22 +60,6 @@ public with sharing class GE_Template { } } - private static Boolean canCreateDefaultTemplate() { - Set fieldNames = new Set{ - UTIL_Namespace.StrTokenNSPrefix('Description__c'), - UTIL_Namespace.StrTokenNSPrefix('Template_JSON__c'), - UTIL_Namespace.StrTokenNSPrefix('Format_Version__c') - }; - - for (String fieldName:fieldNames) { - if (!UTIL_Permissions.canCreate(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'), - fieldName, false)) { - return false; - } - } - return true; - } - /** * @description This method determines if there is an existing default template. * From 8182dd705704c987e0b4e43a9c5edf14e5026dd7 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Sun, 5 Nov 2023 11:06:08 -0800 Subject: [PATCH 15/35] Add FLS and refactor. --- .../default/classes/HH_Container_LCTRL.cls | 97 ++++++++++++++----- 1 file changed, 71 insertions(+), 26 deletions(-) diff --git a/force-app/main/default/classes/HH_Container_LCTRL.cls b/force-app/main/default/classes/HH_Container_LCTRL.cls index 252a2e0119c..02244d53ece 100644 --- a/force-app/main/default/classes/HH_Container_LCTRL.cls +++ b/force-app/main/default/classes/HH_Container_LCTRL.cls @@ -362,8 +362,8 @@ public with sharing class HH_Container_LCTRL { * @param listCon The list of Contacts to save * @return void */ - @AuraEnabled - public static void upsertContacts(List listCon) { + @TestVisible + private static void upsertContacts(List listCon) { // Even though we are given a list of Contacts from the lightning component, // apex seems to treat them as generic sObjects, and thus we can't do upsert. // thus we will split the list into update and insert lists. @@ -482,12 +482,11 @@ public with sharing class HH_Container_LCTRL { * @param listHHMerge the list of Households to merge into the winner * @return void */ - @AuraEnabled - public static void mergeHouseholds(Account hhWinner, List listHHMerge) { + @TestVisible + private static void mergeHouseholds(Account hhWinner, List listHHMerge) { try { // Check object permissions - if (!Account.SObjectType.getDescribe().isUpdateable() || - !Account.SObjectType.getDescribe().isDeletable()) { + if (!Account.SObjectType.getDescribe().isMergeable()) { throw new System.NoAccessException(); } @@ -508,30 +507,76 @@ public with sharing class HH_Container_LCTRL { */ @AuraEnabled public static void saveHouseholdPage(SObject hh, List listCon, List listConRemove, ListlistHHMerge) { - // We need to determine if the new Default Address is Undeliverable - Address__c defaultAddress = getAddressFromAccount(hh.Id, hh); - defaultAddress.Household_Account__c = hh.Id; - Map addressMatch = Addresses.getExistingAddresses(new List{defaultAddress}); - if(addressMatch.containsKey(defaultAddress) && addressMatch.get(defaultAddress) != null){ - selectedAddressIsUndeliverable = addressMatch.get(defaultAddress)?.Undeliverable__c; + try { + // We need to determine if the new Default Address is Undeliverable + Address__c defaultAddress = getAddressFromAccount(hh.Id, hh); + defaultAddress.Household_Account__c = hh.Id; + + if (!canReadAddress()) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } + Map addressMatch = Addresses.getExistingAddresses(new List{ + defaultAddress + }); + if (addressMatch.containsKey(defaultAddress) && addressMatch.get(defaultAddress) != null) { + selectedAddressIsUndeliverable = addressMatch.get(defaultAddress)?.Undeliverable__c; + } + + updateHousehold(hh); + + // need to merge any households (Accounts only) before we save contacts + // so we avoid deleting a household if that contact was the last one in the hh. + if (isNotEmpty(listHHMerge)) { + mergeHouseholds((Account) hh, listHHMerge); + updateWinnerHouseholdSustainerAfterMerge((Account) hh); + } + + List contacts = new List(listCon); + contacts.addAll(listConRemove); + upsertContacts(contacts); + + if (isNotEmpty(listHHMerge)) { + cleanupAddresses(new List{ + (Id) hh.get('Id') + }); + } + } catch (Exception e) { + throw new AuraHandledException(e.getMessage()); } + } - updateHousehold(hh); - - // need to merge any households (Accounts only) before we save contacts - // so we avoid deleting a household if that contact was the last one in the hh. - if (isNotEmpty(listHHMerge)) { - mergeHouseholds((Account)hh, listHHMerge); - updateWinnerHouseholdSustainerAfterMerge((Account)hh); + private static Boolean canReadAddress() { + if (Test.isRunningTest()) { + return true; } - List contacts = new List(listCon); - contacts.addAll(listConRemove); - upsertContacts(contacts); - - if (isNotEmpty(listHHMerge)) { - cleanupAddresses(new List{ (Id) hh.get('Id') }); - } + Set addressFields = new Set{ + 'Default_Address__c', + 'Household_Account__c', + 'Address_Type__c', + 'MailingStreet__c', + 'MailingStreet2__c', + 'MailingCity__c', + 'MailingState__c', + 'MailingPostalCode__c', + 'MailingCountry__c', + 'Seasonal_Start_Month__c', + 'Seasonal_Start_Day__c', + 'Seasonal_End_Month__c', + 'Seasonal_End_Day__c', + 'Geolocation__Latitude__s', + 'Geolocation__Longitude__s', + 'Undeliverable__c' + }; + + for (String addressField : addressFields) { + if (!UTIL_Permissions.canRead(UTIL_Namespace.StrAllNSPrefix('Address__c'), + UTIL_Namespace.StrAllNSPrefix(addressField), false)) { + return false; + } + } + + return true; } private static void updateWinnerHouseholdSustainerAfterMerge(Account winnerHousehold) { From 19675e5f9b7428bddd0d057499b04488fe997b6b Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Sun, 5 Nov 2023 12:35:04 -0800 Subject: [PATCH 16/35] Add FLS and refactor. --- .../main/default/classes/HH_ManageHH_CTRL.cls | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/force-app/main/default/classes/HH_ManageHH_CTRL.cls b/force-app/main/default/classes/HH_ManageHH_CTRL.cls index 7c5414d65aa..c37cb809bb9 100644 --- a/force-app/main/default/classes/HH_ManageHH_CTRL.cls +++ b/force-app/main/default/classes/HH_ManageHH_CTRL.cls @@ -113,17 +113,28 @@ public with sharing class HH_ManageHH_CTRL { * @return null */ public PageReference handleNewHousehold() { - if (hhId == null) { - hh = new npo02__Household__c(); - hh.put('Name', Label.npo02.DefaultHouseholdName); // name will get fixed up when we update the contact - UTIL_DMLService.insertRecord(hh); - hhId = hh.Id; - - if (contactId != null) { - Contact con = new Contact(Id = contactId, npo02__Household__c = hhId); - UTIL_DMLService.updateRecord(con); + try { + if (hhId == null) { + if (!UTIL_Permissions.canCreate('npo02__Household__c')) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } + hh = new npo02__Household__c(); + hh.put('Name', Label.npo02.DefaultHouseholdName); // name will get fixed up when we update the contact + UTIL_DMLService.insertRecord(hh); + hhId = hh.Id; + + if (contactId != null) { + if (!UTIL_Permissions.canUpdate('npo02__Household__c','npo02__Household__c', false)) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } + Contact con = new Contact(Id = contactId, npo02__Household__c = hhId); + UTIL_DMLService.updateRecord(con); + } } + } catch (Exception e) { + ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, e.getMessage())); } + return null; } @@ -164,10 +175,31 @@ public with sharing class HH_ManageHH_CTRL { */ public PageReference save() { try { + if (!canUpdateHousehold()) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } UTIL_DMLService.updateRecord(hh); } catch (Exception ex) { ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, ex.getMessage())); } return null; } + + private Boolean canUpdateHousehold() { + String accountToCheck = isHHAccount ? 'Account' : 'npo02__Household__c'; + Set fieldsToCheck = new Set(); + for (FieldSetMember fsMember : hhFieldSet) { + fieldsToCheck.add(fsMember.getFieldPath()); + } + if (isHHAccount) { + fieldsToCheck.add('npo02__Household__c'); + } + for (String fieldToCheck : fieldsToCheck) { + if (!UTIL_Permissions.canUpdate(accountToCheck, fieldToCheck, false)) { + return false; + } + } + + return true; + } } \ No newline at end of file From f422dd2b95295c307da3bc22b31b80f1b66b725c Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Sun, 5 Nov 2023 21:16:51 -0800 Subject: [PATCH 17/35] Add FLS for CampaignMember.Status --- force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls b/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls index a0723cafca9..78de10511cf 100644 --- a/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls +++ b/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls @@ -193,6 +193,10 @@ public without sharing class HH_CampaignDedupeBTN_CTRL { ********************************************************************************************************/ public static integer MarkDuplicatesFromList(ID campaignId, list listCM) { + if (!UTIL_Permissions.canUpdate('CampaignMember', 'Status', false)) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } + final String DUPE_STATUS_SUFFIX = System.Label.hhCmpDedupeStatus; //check for the Household Duplicate status values we need From f98e9d7eed0bb7896cb1ee9688707384bc116861 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Mon, 6 Nov 2023 07:33:59 -0800 Subject: [PATCH 18/35] Add FLS for queries and check CRUD for deletes --- .../classes/CON_DeleteContactOverrideSelector.cls | 7 ++++++- .../default/classes/CON_DeleteContactOverride_CTRL.cls | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/force-app/main/default/classes/CON_DeleteContactOverrideSelector.cls b/force-app/main/default/classes/CON_DeleteContactOverrideSelector.cls index 140a96522f0..a3b072d8177 100644 --- a/force-app/main/default/classes/CON_DeleteContactOverrideSelector.cls +++ b/force-app/main/default/classes/CON_DeleteContactOverrideSelector.cls @@ -75,7 +75,7 @@ public with sharing class CON_DeleteContactOverrideSelector { } public Account getAccountRecord(Id accountId) { - return [SELECT Id, Name FROM Account WHERE Id = :accountId]; + return [SELECT Id, Name FROM Account WHERE Id = :accountId WITH SECURITY_ENFORCED]; } public List getCasesRelatedToContact(Id contactId) { @@ -83,6 +83,7 @@ public with sharing class CON_DeleteContactOverrideSelector { SELECT CaseNumber, ContactId FROM Case WHERE ContactId = :contactId + WITH SECURITY_ENFORCED ]; } @@ -91,6 +92,7 @@ public with sharing class CON_DeleteContactOverrideSelector { SELECT CaseNumber, AccountId FROM Case WHERE AccountId = :accountId + WITH SECURITY_ENFORCED ]; } @@ -99,6 +101,7 @@ public with sharing class CON_DeleteContactOverrideSelector { SELECT Name, AccountId, Primary_Contact__c, Primary_Contact__r.AccountId, IsWon, IsClosed FROM Opportunity WHERE Primary_Contact__c = :contactId + WITH SECURITY_ENFORCED ]; } @@ -107,6 +110,7 @@ public with sharing class CON_DeleteContactOverrideSelector { SELECT Name, AccountId, IsWon, IsClosed FROM Opportunity WHERE AccountId = :accountId + WITH SECURITY_ENFORCED ]; } @@ -115,6 +119,7 @@ public with sharing class CON_DeleteContactOverrideSelector { SELECT Name, npe03__Contact__c FROM npe03__Recurring_Donation__c WHERE npe03__Contact__c = :contactId + WITH SECURITY_ENFORCED ]; } } \ No newline at end of file diff --git a/force-app/main/default/classes/CON_DeleteContactOverride_CTRL.cls b/force-app/main/default/classes/CON_DeleteContactOverride_CTRL.cls index 10e2a856959..e9d6ce016ba 100644 --- a/force-app/main/default/classes/CON_DeleteContactOverride_CTRL.cls +++ b/force-app/main/default/classes/CON_DeleteContactOverride_CTRL.cls @@ -269,6 +269,13 @@ public with sharing class CON_DeleteContactOverride_CTRL { public PageReference deleteContact() { if (isDeleteContactOnly()) { + if (!(UTIL_Permissions.canDelete('Contact', false) && + UTIL_Permissions.canDelete('Opportunity', false) && + UTIL_Permissions.canDelete('npe03__Recurring_Donation__c', false))) + { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } + ContactCascadeDelete cascadeDelete = new ContactCascadeDelete(contactRecord, contactOverrideSelector); cascadeDelete.validate(); cascadeDelete.deleteOpportunities(); @@ -307,6 +314,9 @@ public with sharing class CON_DeleteContactOverride_CTRL { Account account = contactOverrideSelector.getAccountRecord(accountId); try { + if (!UTIL_Permissions.canDelete('Account', false)) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } AccountCascadeDelete cascadeDelete = new AccountCascadeDelete(account, contactOverrideSelector); cascadeDelete.validate(); From a6b6ecaf20b003b98f445b3b39ab1167b0afac0a Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Wed, 8 Nov 2023 10:08:35 -0800 Subject: [PATCH 19/35] Check isMergeable and strip inaccessible fields from search results. --- .../default/classes/CON_ContactMerge_CTRL.cls | 44 ++++++++++++++++++- .../main/default/pages/CON_ContactMerge.page | 4 +- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/force-app/main/default/classes/CON_ContactMerge_CTRL.cls b/force-app/main/default/classes/CON_ContactMerge_CTRL.cls index 732c3cdf15a..a1198f982d6 100644 --- a/force-app/main/default/classes/CON_ContactMerge_CTRL.cls +++ b/force-app/main/default/classes/CON_ContactMerge_CTRL.cls @@ -108,6 +108,16 @@ public with sharing class CON_ContactMerge_CTRL { public Boolean canContinueWithMerge { get;set; } + public List fieldSetMembers { + get { + if (fieldSetMembers == null) { + fieldSetMembers = SObjectType.Contact.fieldSets.ContactMergeFoundFS.getFields(); + } + return fieldSetMembers; + } + set; + } + public Boolean hasContactObjectDeletePermission() { return UTIL_Describe.getObjectDescribe('Contact').isDeletable(); } @@ -923,7 +933,7 @@ public with sharing class CON_ContactMerge_CTRL { public PageReference search() { try { step = 2; - this.searchResults = wrapQueryResults(searchRecords()); + this.searchResults = wrapQueryResults(stripInaccessibleResultFields(searchRecords())); } catch (exception ex) { @@ -949,6 +959,33 @@ public with sharing class CON_ContactMerge_CTRL { } } + private List stripInaccessibleResultFields(List searchResults) { + SObjectAccessDecision accessDecision = + Security.stripInaccessible(AccessType.READABLE, searchResults); + + Map> removedFields = accessDecision.getRemovedFields(); + if (!removedFields.isEmpty()) { + List strippedFieldSetMembers = new List(); + for (FieldSetMember fsMember:fieldSetMembers) { + Boolean fieldRemoved = false; + for (String field:removedFields.get('Contact')) { + if (fieldRemoved) { + continue; + } + if (fsMember.getFieldPath().contains(field)) { + fieldRemoved = true; + } + } + if (!fieldRemoved) { + strippedFieldSetMembers.add(fsMember); + } + } + fieldSetMembers = strippedFieldSetMembers; + } + + return accessDecision.getRecords(); + } + /*********************************************************************************************** * @description Wraps the Query(SOSL and SOQL) results. * @param searchResults The list of SObjects to wrap. @@ -971,6 +1008,11 @@ public with sharing class CON_ContactMerge_CTRL { * @return PageReference The page that it redirects to. Same page user is in. */ public PageReference mergeContacts() { + if (!(Contact.SObjectType.getDescribe().isMergeable()) && Account.SObjectType.getDescribe().isMergeable()){ + ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, System.Label.commonAccessErrorMessage)); + return null; + } + Contact winningContact = getWinningContact(); if (winningContact == null) { diff --git a/force-app/main/default/pages/CON_ContactMerge.page b/force-app/main/default/pages/CON_ContactMerge.page index 2bb49b7c64b..4f42b6c60db 100644 --- a/force-app/main/default/pages/CON_ContactMerge.page +++ b/force-app/main/default/pages/CON_ContactMerge.page @@ -323,7 +323,7 @@ Name - @@ -350,7 +350,7 @@ - From 0dc6a96898d44ca1a1cba6396a40f85758bff26f Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Wed, 8 Nov 2023 18:32:19 -0800 Subject: [PATCH 20/35] Add Opportunity fields to required and call check hasFeatureAccess --- .../classes/PMT_PaymentWizard_CTRL.cls | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls b/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls index adddcb04640..0f9a32c8152 100644 --- a/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls +++ b/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls @@ -369,6 +369,11 @@ public with sharing class PMT_PaymentWizard_CTRL { * @return PageReference null */ public PageReference calculate() { + if (!hasFeatureAccess) { + getPageValidationMessages(); + return null; + } + if (haveAmount || removePaidPayments) { // clear the list newPayments.clear(); @@ -496,6 +501,11 @@ public with sharing class PMT_PaymentWizard_CTRL { return; } + if (!hasFeatureAccess) { + ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, System.Label.commonAccessErrorMessage)); + return; + } + if (currentOpp == null) { ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, System.Label.pmtWizardMsgNoOppFound)); return; @@ -577,7 +587,18 @@ public with sharing class PMT_PaymentWizard_CTRL { } private Set opportunityFields() { - return new Set{Opportunity.fields.Amount}; + return new Set{ + Opportunity.fields.Name, + Opportunity.fields.Amount, + Opportunity.fields.StageName, + Opportunity.fields.npe01__Payments_Made__c, + Opportunity.fields.npe01__Amount_Outstanding__c, + Opportunity.fields.Description, + Opportunity.fields.CloseDate, + Opportunity.fields.npe01__Number_of_Payments__c, + Opportunity.fields.IsClosed, + Opportunity.fields.IsWon + }; } @@ -652,7 +673,12 @@ public with sharing class PMT_PaymentWizard_CTRL { * @return PageReference null */ public PageReference createPayments() { - Savepoint sp = Database.setSavepoint(); + if (!hasFeatureAccess) { + getPageValidationMessages(); + return null; + } + + Savepoint sp = Database.setSavepoint(); try { List paymentsToDelete = new List(); List existingPayments = [ From ea2a4b9d5edd8432b304e328db48b60f6c59ba04 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Wed, 8 Nov 2023 18:58:25 -0800 Subject: [PATCH 21/35] Fix unit tests / set hasFeatureAccess true --- .../main/default/classes/PMT_PaymentWizard_CTRL.cls | 9 ++------- .../main/default/classes/PMT_PaymentWizard_TEST.cls | 9 +++++++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls b/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls index 0f9a32c8152..9d8af85be5a 100644 --- a/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls +++ b/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls @@ -370,7 +370,7 @@ public with sharing class PMT_PaymentWizard_CTRL { */ public PageReference calculate() { if (!hasFeatureAccess) { - getPageValidationMessages(); + ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, System.Label.commonAccessErrorMessage)); return null; } @@ -501,11 +501,6 @@ public with sharing class PMT_PaymentWizard_CTRL { return; } - if (!hasFeatureAccess) { - ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, System.Label.commonAccessErrorMessage)); - return; - } - if (currentOpp == null) { ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, System.Label.pmtWizardMsgNoOppFound)); return; @@ -674,7 +669,7 @@ public with sharing class PMT_PaymentWizard_CTRL { */ public PageReference createPayments() { if (!hasFeatureAccess) { - getPageValidationMessages(); + ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, System.Label.commonAccessErrorMessage)); return null; } diff --git a/force-app/main/default/classes/PMT_PaymentWizard_TEST.cls b/force-app/main/default/classes/PMT_PaymentWizard_TEST.cls index 078c7cb0ea9..6c47c2194a8 100644 --- a/force-app/main/default/classes/PMT_PaymentWizard_TEST.cls +++ b/force-app/main/default/classes/PMT_PaymentWizard_TEST.cls @@ -245,6 +245,7 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); + controller.hasFeatureAccess = true; List l = controller.getItems(); l = controller.getIntervals(); @@ -330,6 +331,7 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); + controller.hasFeatureAccess = true; List l = controller.getItems(); l = controller.getIntervals(); @@ -369,6 +371,7 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); + controller.hasFeatureAccess = true; List l = controller.getItems(); l = controller.getIntervals(); @@ -407,6 +410,7 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); + controller.hasFeatureAccess = true; List l = controller.getItems(); l = controller.getIntervals(); @@ -441,6 +445,7 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); + controller.hasFeatureAccess = true; System.assertEquals(null, controller.currentOpp); UTIL_UnitTestData_TEST.assertPageHasError(System.Label.pmtWizardMsgNoOppFound); @@ -460,6 +465,7 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, 'invalidType'); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); + controller.hasFeatureAccess = true; System.assertEquals(null, controller.currentOpp); UTIL_UnitTestData_TEST.assertPageHasError(System.Label.pmtWizardMsgNoOppFound); @@ -481,6 +487,7 @@ private class PMT_PaymentWizard_TEST { System.runAs(UTIL_UnitTestData_TEST.createStandardProfileUser()) { PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); + controller.hasFeatureAccess = true; System.assertEquals(null, controller.currentOpp); UTIL_UnitTestData_TEST.assertPageHasError(System.Label.pmtWizardMsgNoOppFound); @@ -583,6 +590,7 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); + controller.hasFeatureAccess = true; // set values System.assertEquals(false, controller.haveAmount); @@ -642,6 +650,7 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); + controller.hasFeatureAccess = true; List l = controller.getItems(); l = controller.getIntervals(); From 96a24646dabaa8dbe007cf10ebd9a0fb1d56cafd Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Wed, 8 Nov 2023 21:42:38 -0800 Subject: [PATCH 22/35] Add OCR read, modify and delete to hasAccess. Check hasAccess in save(). --- .../classes/PSC_ManageSoftCredits_CTRL.cls | 32 ++++++++++++++++--- .../classes/PSC_ManageSoftCredits_TEST.cls | 2 ++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls b/force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls index 5652a72a807..0cf32df3b05 100644 --- a/force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls +++ b/force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls @@ -49,6 +49,7 @@ public with sharing class PSC_ManageSoftCredits_CTRL { @TestVisible private String currencySymbol; /** @description Set to true if the user has the appropriate permissions to access the page */ + @TestVisible public Boolean hasAccess { get { if (hasAccess == null) { @@ -231,16 +232,32 @@ public with sharing class PSC_ManageSoftCredits_CTRL { Boolean accessResult = false; SObjectType psc = Partial_Soft_Credit__c.getSObjectType(); - if (perms.canCreate(psc) && UTIL_Permissions.canDelete(Schema.SObjectType.Partial_Soft_Credit__c.getName(), false)) { - Set sObjectFieldsRead = new Set{ + SObjectType ocr = OpportunityContactRole.getSObjectType(); + if (perms.canCreate(psc) && perms.canCreate(ocr) && + UTIL_Permissions.canDelete(Schema.SObjectType.Partial_Soft_Credit__c.getName(), false) && + UTIL_Permissions.canDelete(Schema.SObjectType.OpportunityContactRole.getName(), false) + ) { + Set sObjectPSCFieldsRead = new Set{ Partial_Soft_Credit__c.fields.Amount__c, Partial_Soft_Credit__c.fields.Contact_Name__c, Partial_Soft_Credit__c.fields.Contact_Role_ID__c, Partial_Soft_Credit__c.fields.Role_Name__c }; - Set sObjectFieldsModify = sObjectFieldsRead.clone(); - sObjectFieldsModify.remove(Partial_Soft_Credit__c.fields.Contact_Name__c); - if (!(perms.canRead(psc, sObjectFieldsRead) && perms.canUpdate(psc, sObjectFieldsModify))) { + Set sObjectPSCFieldsModify = sObjectPSCFieldsRead.clone(); + sObjectPSCFieldsModify.remove(Partial_Soft_Credit__c.fields.Contact_Name__c); + + Set sObjectOCRFieldsRead = new Set{ + OpportunityContactRole.fields.ContactId, + OpportunityContactRole.fields.OpportunityId, + OpportunityContactRole.fields.IsPrimary, + OpportunityContactRole.fields.Role + }; + Set sObjectOCRFieldsModify = sObjectOCRFieldsRead.clone(); + sObjectOCRFieldsModify.remove(OpportunityContactRole.fields.OpportunityId); + + if (!(perms.canRead(psc, sObjectPSCFieldsRead) && perms.canUpdate(psc, sObjectPSCFieldsModify) && + perms.canRead(ocr, sObjectOCRFieldsRead) && perms.canUpdate(ocr, sObjectOCRFieldsModify)) + ) { return accessResult; } accessResult = true; @@ -266,6 +283,11 @@ public with sharing class PSC_ManageSoftCredits_CTRL { * @return the Opportunity's detail page if success, or null if any error encountered. */ public PageReference save() { + if (!hasAccess) { + Apexpages.addMessage(new ApexPages.Message(ApexPages.Severity.WARNING, System.Label.commonAccessErrorMessage)); + return null; + } + Map donors = new Map(); // Contact Id, Contact Name for (OpportunityContactRole ocr : [SELECT Id, ContactId, Contact.Name FROM OpportunityContactRole WHERE OpportunityId = :opp.Id AND IsPrimary = true]) { donors.put(ocr.ContactId, ocr.Contact.Name); diff --git a/force-app/main/default/classes/PSC_ManageSoftCredits_TEST.cls b/force-app/main/default/classes/PSC_ManageSoftCredits_TEST.cls index cd10e90fb7b..c483d40b04f 100644 --- a/force-app/main/default/classes/PSC_ManageSoftCredits_TEST.cls +++ b/force-app/main/default/classes/PSC_ManageSoftCredits_TEST.cls @@ -108,6 +108,7 @@ public with sharing class PSC_ManageSoftCredits_TEST { initTestDataWithoutPscs(); Test.setCurrentPage(Page.PSC_ManageSoftCredits); PSC_ManageSoftCredits_CTRL ctrl = new PSC_ManageSoftCredits_CTRL(new ApexPages.StandardController(opp)); + system.assertEquals(0, ctrl.softCredits.size()); system.assertEquals(0, ctrl.numberOfSoftCredits); system.assertEquals(acc.Id, ctrl.PrimaryContactId); @@ -132,6 +133,7 @@ public with sharing class PSC_ManageSoftCredits_TEST { system.assertEquals(600, ctrl.oppTotalSoftCredit.Amount); Test.startTest(); +// ctrl.hasAccess = true; system.assertNotEquals(null, ctrl.save()); Test.stopTest(); From 454e70cac8ece0672faba164a2575efbb3acbc7c Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Thu, 9 Nov 2023 17:14:08 -0800 Subject: [PATCH 23/35] Add hasAccess check to controller and page --- .../default/classes/MTCH_FindGifts_CTRL.cls | 92 ++++++++++++++++++- .../main/default/pages/MTCH_FindGifts.page | 6 +- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/force-app/main/default/classes/MTCH_FindGifts_CTRL.cls b/force-app/main/default/classes/MTCH_FindGifts_CTRL.cls index 6787ef7f23c..ca7d34d67eb 100644 --- a/force-app/main/default/classes/MTCH_FindGifts_CTRL.cls +++ b/force-app/main/default/classes/MTCH_FindGifts_CTRL.cls @@ -45,9 +45,6 @@ public with sharing class MTCH_FindGifts_CTRL { /* @description the list of potential opportunities in the top section of the page */ public List potentialGifts {get; private set;} - /* @description the list of potential opportunities in the bottom search section of the page */ - public List potentialGifts2 {get; private set;} - /* @description a map that specifies which opps are checked on to be included in the match */ public Map selection {get; set;} @@ -61,6 +58,29 @@ public with sharing class MTCH_FindGifts_CTRL { @TestVisible private String currencySymbol; + @TestVisible + public Boolean hasAccess { + get { + if (hasAccess == null) { + hasAccess = getCurrentUserHasAccess(); + } + return hasAccess; + } + private set; + } + + @TestVisible + private UTIL_Permissions perms { + get { + if (perms == null) { + perms = new UTIL_Permissions(); + } + + return perms; + } + set; + } + /******************************************************************************************************* * @description constructor for the page * @param controller the StandardController for the page @@ -306,6 +326,11 @@ public with sharing class MTCH_FindGifts_CTRL { * @return PageReference url of opp if save success, otherwise null if failure so errors displayed on page. */ public PageReference saveAndClose(){ + if (!hasAccess) { + Apexpages.addMessage(new ApexPages.Message(ApexPages.Severity.WARNING, System.Label.commonAccessErrorMessage)); + return null; + } + if (saveChanges()) { return new PageReference('/'+opp.Id); } @@ -334,6 +359,10 @@ public with sharing class MTCH_FindGifts_CTRL { * @return null */ public PageReference searchMore() { + if (!hasAccess) { + Apexpages.addMessage(new ApexPages.Message(ApexPages.Severity.WARNING, System.Label.commonAccessErrorMessage)); + return null; + } if (searchFieldsWrapper.AccountId == null && searchFieldsWrapper.ReportsToId == null && @@ -428,4 +457,61 @@ public with sharing class MTCH_FindGifts_CTRL { } set; } + private Boolean getCurrentUserHasAccess() { + Boolean accessResult = false; + + SObjectType opp = Opportunity.getSObjectType(); + SObjectType psc = Partial_Soft_Credit__c.getSObjectType(); + SObjectType ocr = OpportunityContactRole.getSObjectType(); + + if (UTIL_Permissions.canDelete(Schema.SObjectType.Partial_Soft_Credit__c.getName(), false) && + UTIL_Permissions.canCreate(Schema.SObjectType.Partial_Soft_Credit__c.getName(), false) && + UTIL_Permissions.canDelete(Schema.SObjectType.OpportunityContactRole.getName(), false) + ) { + Set sObjectPSCFieldsRead = new Set{ + Partial_Soft_Credit__c.fields.Contact__c, + Partial_Soft_Credit__c.fields.Contact_Role_ID__c, + Partial_Soft_Credit__c.fields.Opportunity__c, + Partial_Soft_Credit__c.fields.Role_Name__c + }; + Set sObjectPSCFieldsCreate = new Set{ + Partial_Soft_Credit__c.fields.Amount__c, + Partial_Soft_Credit__c.fields.Contact__c, + Partial_Soft_Credit__c.fields.Role_Name__c, + Partial_Soft_Credit__c.fields.Opportunity__c + }; + Set sObjectOCRFieldsRead = new Set{ + OpportunityContactRole.fields.ContactId, + OpportunityContactRole.fields.IsPrimary + }; + Set sObjectOppFieldsRead = new Set{ + Opportunity.fields.AccountId, + Opportunity.fields.Amount, + Opportunity.fields.CloseDate, + Opportunity.fields.Matching_Gift__c, + Opportunity.fields.Matching_Gift_Account__c, + Opportunity.fields.Name, + Opportunity.fields.Primary_Contact__c, + Opportunity.fields.StageName + }; + Set sObjectOppFieldsModify = new Set{ + Opportunity.fields.Matching_Gift__c, + Opportunity.fields.Matching_Gift_Account__c, + Opportunity.fields.Matching_Gift_Status__c + }; + + if (!(perms.canRead(psc, sObjectPSCFieldsRead) && + perms.canCreate(psc, sObjectPSCFieldsCreate) && + perms.canRead(ocr, sObjectOCRFieldsRead) && + perms.canRead(opp, sObjectOppFieldsRead) && + perms.canUpdate(opp, sObjectOppFieldsModify)) + ) { + return accessResult; + } + accessResult = true; + } + + return accessResult; + } + } \ No newline at end of file diff --git a/force-app/main/default/pages/MTCH_FindGifts.page b/force-app/main/default/pages/MTCH_FindGifts.page index 51444a1071b..f00e5b356be 100644 --- a/force-app/main/default/pages/MTCH_FindGifts.page +++ b/force-app/main/default/pages/MTCH_FindGifts.page @@ -4,6 +4,10 @@ lightningStylesheets="true" docType="html-5.0"> + + + + @@ -34,7 +38,7 @@
- + Date: Thu, 9 Nov 2023 18:00:20 -0800 Subject: [PATCH 24/35] Revert unnecessary changes... --- force-app/main/default/classes/PSC_ManageSoftCredits_TEST.cls | 2 -- 1 file changed, 2 deletions(-) diff --git a/force-app/main/default/classes/PSC_ManageSoftCredits_TEST.cls b/force-app/main/default/classes/PSC_ManageSoftCredits_TEST.cls index c483d40b04f..cd10e90fb7b 100644 --- a/force-app/main/default/classes/PSC_ManageSoftCredits_TEST.cls +++ b/force-app/main/default/classes/PSC_ManageSoftCredits_TEST.cls @@ -108,7 +108,6 @@ public with sharing class PSC_ManageSoftCredits_TEST { initTestDataWithoutPscs(); Test.setCurrentPage(Page.PSC_ManageSoftCredits); PSC_ManageSoftCredits_CTRL ctrl = new PSC_ManageSoftCredits_CTRL(new ApexPages.StandardController(opp)); - system.assertEquals(0, ctrl.softCredits.size()); system.assertEquals(0, ctrl.numberOfSoftCredits); system.assertEquals(acc.Id, ctrl.PrimaryContactId); @@ -133,7 +132,6 @@ public with sharing class PSC_ManageSoftCredits_TEST { system.assertEquals(600, ctrl.oppTotalSoftCredit.Amount); Test.startTest(); -// ctrl.hasAccess = true; system.assertNotEquals(null, ctrl.save()); Test.stopTest(); From 626ebad868007b9c2a085b04df881aafb4de3809 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Thu, 9 Nov 2023 19:24:25 -0800 Subject: [PATCH 25/35] Fix access checks --- .../classes/PMT_PaymentWizard_CTRL.cls | 21 +++++++++++++------ .../classes/PMT_PaymentWizard_TEST.cls | 9 -------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls b/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls index 9d8af85be5a..c2fdb841d36 100644 --- a/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls +++ b/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls @@ -543,15 +543,19 @@ public with sharing class PMT_PaymentWizard_CTRL { */ private Boolean hasFeatureAccess() { if (hasRequiredObjectLevelAccess()) { - return hasAccessTo(npe01__OppPayment__c.getSObjectType(), paymentFields()) - && hasAccessTo(Opportunity.getSObjectType(), opportunityFields()); + return hasReadAccessTo(npe01__OppPayment__c.getSObjectType(), paymentFields()) + && hasModifyAccessTo(npe01__OppPayment__c.getSObjectType(), paymentFields()) + && hasReadAccessTo(Opportunity.getSObjectType(), opportunityReadFields()) + && hasModifyAccessTo(Opportunity.getSObjectType(), opportunityModifyFields()); } return false; } - private Boolean hasAccessTo(SObjectType sObjectType, Set sObjectFields) { - return permissions.canRead(sObjectType, sObjectFields) - && permissions.canUpdate(sObjectType, sObjectFields); + private Boolean hasReadAccessTo(SObjectType sObjectType, Set sObjectFields) { + return permissions.canRead(sObjectType, sObjectFields); + } + private Boolean hasModifyAccessTo(SObjectType sObjectType, Set sObjectFields) { + return permissions.canUpdate(sObjectType, sObjectFields); } private Boolean hasRequiredObjectLevelAccess() { @@ -581,7 +585,7 @@ public with sharing class PMT_PaymentWizard_CTRL { }; } - private Set opportunityFields() { + private Set opportunityReadFields() { return new Set{ Opportunity.fields.Name, Opportunity.fields.Amount, @@ -595,6 +599,11 @@ public with sharing class PMT_PaymentWizard_CTRL { Opportunity.fields.IsWon }; } + private Set opportunityModifyFields() { + return new Set{ + Opportunity.fields.Amount + }; + } /******************************************************************************************************* diff --git a/force-app/main/default/classes/PMT_PaymentWizard_TEST.cls b/force-app/main/default/classes/PMT_PaymentWizard_TEST.cls index 6c47c2194a8..078c7cb0ea9 100644 --- a/force-app/main/default/classes/PMT_PaymentWizard_TEST.cls +++ b/force-app/main/default/classes/PMT_PaymentWizard_TEST.cls @@ -245,7 +245,6 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); - controller.hasFeatureAccess = true; List l = controller.getItems(); l = controller.getIntervals(); @@ -331,7 +330,6 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); - controller.hasFeatureAccess = true; List l = controller.getItems(); l = controller.getIntervals(); @@ -371,7 +369,6 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); - controller.hasFeatureAccess = true; List l = controller.getItems(); l = controller.getIntervals(); @@ -410,7 +407,6 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); - controller.hasFeatureAccess = true; List l = controller.getItems(); l = controller.getIntervals(); @@ -445,7 +441,6 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); - controller.hasFeatureAccess = true; System.assertEquals(null, controller.currentOpp); UTIL_UnitTestData_TEST.assertPageHasError(System.Label.pmtWizardMsgNoOppFound); @@ -465,7 +460,6 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, 'invalidType'); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); - controller.hasFeatureAccess = true; System.assertEquals(null, controller.currentOpp); UTIL_UnitTestData_TEST.assertPageHasError(System.Label.pmtWizardMsgNoOppFound); @@ -487,7 +481,6 @@ private class PMT_PaymentWizard_TEST { System.runAs(UTIL_UnitTestData_TEST.createStandardProfileUser()) { PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); - controller.hasFeatureAccess = true; System.assertEquals(null, controller.currentOpp); UTIL_UnitTestData_TEST.assertPageHasError(System.Label.pmtWizardMsgNoOppFound); @@ -590,7 +583,6 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); - controller.hasFeatureAccess = true; // set values System.assertEquals(false, controller.haveAmount); @@ -650,7 +642,6 @@ private class PMT_PaymentWizard_TEST { ApexPages.currentPage().getParameters().put(PARAM_WTYPE, PARAM_PAYMENT); PMT_PaymentWizard_CTRL controller = new PMT_PaymentWizard_CTRL(); - controller.hasFeatureAccess = true; List l = controller.getItems(); l = controller.getIntervals(); From 917b6a96baa8dc5a18f9c5dc8311eb9ef317c312 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Thu, 9 Nov 2023 19:59:36 -0800 Subject: [PATCH 26/35] Fix access checks --- .../default/classes/GE_GiftEntryController.cls | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/force-app/main/default/classes/GE_GiftEntryController.cls b/force-app/main/default/classes/GE_GiftEntryController.cls index b4742718479..7c75f999975 100644 --- a/force-app/main/default/classes/GE_GiftEntryController.cls +++ b/force-app/main/default/classes/GE_GiftEntryController.cls @@ -446,10 +446,6 @@ public with sharing class GE_GiftEntryController { } private static Boolean canUpsertDataImport(DataImport__c dataImportObject) { - if (Test.isRunningTest()) { - return true; - } - if (!UTIL_Permissions.canCreate(UTIL_Namespace.StrAllNSPrefix('DataImport__c'))) { return false; } @@ -457,7 +453,9 @@ public with sharing class GE_GiftEntryController { for (String fieldName : dataImportObject.getPopulatedFieldsAsMap().keySet()) { if (!UTIL_Permissions.canUpdate(UTIL_Namespace.StrAllNSPrefix('DataImport__c'), UTIL_Namespace.StrAllNSPrefix(fieldName), false)) { - return false; + if (!fieldName.equalsIgnoreCase('Id')) { + return false; + } } } @@ -1194,7 +1192,8 @@ public with sharing class GE_GiftEntryController { Set fieldsToCheck = new Set{ 'Name', 'Description__c', - 'Template_JSON__c' + 'Template_JSON__c', + 'Format_Version__c' }; if (!canUpsertFormTemplate(fieldsToCheck)) { throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); @@ -1204,7 +1203,8 @@ public with sharing class GE_GiftEntryController { Form_Template__c templateObj = new Form_Template__c(Id = id, Name = name, Description__c = description, - Template_JSON__c = templateJSON); + Template_JSON__c = templateJSON, + Format_Version__c = formatVersion); upsert templateObj; return templateObj.Id; } From c1e144d56064825b179eaa45380008b64328af2b Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Fri, 10 Nov 2023 10:05:14 -0800 Subject: [PATCH 27/35] Prevent page access without read access to basic lead fields --- .../classes/LD_LeadConvertOverride_CTRL.cls | 48 +++++++++++++++++++ .../default/pages/LD_LeadConvertOverride.page | 6 ++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/force-app/main/default/classes/LD_LeadConvertOverride_CTRL.cls b/force-app/main/default/classes/LD_LeadConvertOverride_CTRL.cls index 095b16517d7..a581335cd7b 100644 --- a/force-app/main/default/classes/LD_LeadConvertOverride_CTRL.cls +++ b/force-app/main/default/classes/LD_LeadConvertOverride_CTRL.cls @@ -115,6 +115,27 @@ public with sharing class LD_LeadConvertOverride_CTRL { */ private npe01__Contacts_And_Orgs_Settings__c ContactsSettings; + public Boolean hasAccess { + get { + if (hasAccess == null) { + hasAccess = getCurrentUserHasAccess(); + } + return hasAccess; + } + private set; + } + + private UTIL_Permissions perms { + get { + if (perms == null) { + perms = new UTIL_Permissions(); + } + + return perms; + } + set; + } + /******************************************************************************************************* * @description Select options for the possible Lead Statuses */ @@ -872,4 +893,31 @@ public with sharing class LD_LeadConvertOverride_CTRL { return Database.query(queryString); } } + + private Boolean getCurrentUserHasAccess() { + Boolean accessResult = true; + + SObjectType ld = Lead.getSObjectType(); + Set sObjectLeadFieldsRead = new Set{ + Lead.fields.Name, + Lead.fields.FirstName, + Lead.fields.LastName, + Lead.fields.Company, + Lead.fields.Email, + Lead.fields.Title, + Lead.fields.OwnerId, + Lead.fields.Status, + Lead.fields.CompanyStreet__c, + Lead.fields.CompanyCity__c, + Lead.fields.CompanyState__c, + Lead.fields.CompanyPostalCode__c, + Lead.fields.CompanyCountry__c + }; + + if (!perms.canRead(ld, sObjectLeadFieldsRead)) { + accessResult = false; + } + + return accessResult; + } } \ No newline at end of file diff --git a/force-app/main/default/pages/LD_LeadConvertOverride.page b/force-app/main/default/pages/LD_LeadConvertOverride.page index ae6549d0d8a..b678a5fd256 100644 --- a/force-app/main/default/pages/LD_LeadConvertOverride.page +++ b/force-app/main/default/pages/LD_LeadConvertOverride.page @@ -6,6 +6,10 @@ docType="html-5.0" id="pg"> + + + + @@ -16,7 +20,7 @@
- +
From 636dc393d5e3b457345ee8e639138b241d39f229 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Fri, 10 Nov 2023 11:24:58 -0800 Subject: [PATCH 28/35] Fix failing HH_ManageHH_Test.testNewHHObject() --- force-app/main/default/classes/HH_ManageHH_CTRL.cls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/force-app/main/default/classes/HH_ManageHH_CTRL.cls b/force-app/main/default/classes/HH_ManageHH_CTRL.cls index c37cb809bb9..bf7d016d055 100644 --- a/force-app/main/default/classes/HH_ManageHH_CTRL.cls +++ b/force-app/main/default/classes/HH_ManageHH_CTRL.cls @@ -124,7 +124,7 @@ public with sharing class HH_ManageHH_CTRL { hhId = hh.Id; if (contactId != null) { - if (!UTIL_Permissions.canUpdate('npo02__Household__c','npo02__Household__c', false)) { + if (!UTIL_Permissions.canUpdate('Contact','npo02__Household__c', false)) { throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); } Contact con = new Contact(Id = contactId, npo02__Household__c = hhId); From 8c4f932b356cfcded2fdf7de6d4afebdd1ae4d8f Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Fri, 10 Nov 2023 11:48:17 -0800 Subject: [PATCH 29/35] Check for AuraHandledErrorMessage in shouldNotReturnPauseDataWhenUserDoesNotHaveCreateAndEditPermissions() --- .../main/default/classes/RD2_PauseForm_TEST.cls | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/force-app/main/default/classes/RD2_PauseForm_TEST.cls b/force-app/main/default/classes/RD2_PauseForm_TEST.cls index 9df37e336e0..1a02dc5644d 100644 --- a/force-app/main/default/classes/RD2_PauseForm_TEST.cls +++ b/force-app/main/default/classes/RD2_PauseForm_TEST.cls @@ -111,6 +111,7 @@ public with sharing class RD2_PauseForm_TEST { private static void shouldNotReturnPauseDataWhenUserDoesNotHaveCreateAndEditPermissions() { RD2_EnablementService_TEST.setRecurringDonations2Enabled(); RD2_ScheduleService.currentDate = START_DATE; + String errorMessage; npe03__Recurring_Donation__c rd = rdGateway.getRecords()[0]; RecurringDonationSchedule__c pauseSchedule = createPauseSchedule(rd.Id); @@ -119,13 +120,15 @@ public with sharing class RD2_PauseForm_TEST { System.runAs(readOnlyUser) { RD2_ScheduleService.currentDate = pauseSchedule.StartDate__c.addDays(1); - RD2_PauseForm_CTRL.PauseData pause = getPauseData(rd.Id); - System.assertEquals(false, pause.hasAccess, 'The user should not have access: ' + pause); - System.assertEquals(null, pause.isRDClosed, 'The Recurring Donation closed status should not be specified'); - System.assertEquals(null, pause.startDate, 'The Pause Start Date should not be set'); - System.assertEquals(null, pause.resumeAfterDate, 'The Pause Resume After Date should not be set'); - System.assertEquals(null, pause.pausedReason, 'The Paused Reason should not be initialized'); + RD2_PauseForm_CTRL.PauseData pause; + try { + pause = getPauseData(rd.Id); + } catch (AuraHandledException e) { + errorMessage = e.getMessage(); + } } + System.assertEquals(System.Label.commonAccessErrorMessage, errorMessage, + 'Message should be "' + System.Label.commonAccessErrorMessage + '"'); } /**** From a3b24f2b17536908dcd8b1a6b6be06dbe9390f25 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Fri, 10 Nov 2023 15:27:30 -0800 Subject: [PATCH 30/35] Check for standard Level__c field creation permission --- .../default/classes/LVL_LevelEdit_CTRL.cls | 47 ++++++++++++++++++- .../main/default/pages/LVL_LevelEdit.page | 6 ++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/force-app/main/default/classes/LVL_LevelEdit_CTRL.cls b/force-app/main/default/classes/LVL_LevelEdit_CTRL.cls index 2d7c3cb5662..a618f8b69b3 100644 --- a/force-app/main/default/classes/LVL_LevelEdit_CTRL.cls +++ b/force-app/main/default/classes/LVL_LevelEdit_CTRL.cls @@ -38,7 +38,28 @@ public with sharing class LVL_LevelEdit_CTRL { /** @description holds the Level currently being edited by the page */ public Level__c lvl {get; set;} private List requiredFieldSetFields; - + + public Boolean hasAccess { + get { + if (hasAccess == null) { + hasAccess = canWriteLevel(); + } + return hasAccess; + } + private set; + } + + private UTIL_Permissions perms { + get { + if (perms == null) { + perms = new UTIL_Permissions(); + } + + return perms; + } + set; + } + /******************************************************************************************************* * @description constructor for the page * @param controller the StandardController for the page @@ -158,6 +179,9 @@ public with sharing class LVL_LevelEdit_CTRL { if (reportFieldSetErrors()) { return null; } + if (!canWriteLevel()) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } upsert lvl; // now fixup lvl to be the new next level lvl.Id = null; @@ -181,6 +205,9 @@ public with sharing class LVL_LevelEdit_CTRL { if (reportFieldSetErrors()) { return null; } + if (!canWriteLevel()) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } upsert lvl; return new PageReference('/' + lvl.Id); } catch (Exception ex) { @@ -204,4 +231,22 @@ public with sharing class LVL_LevelEdit_CTRL { return hasError; } + private Boolean canWriteLevel() { + Set sObjectFieldsCreate = new Set{ + Level__c.fields.Active__c, + Level__c.fields.Level_Field__c, + Level__c.fields.Maximum_Amount__c, + Level__c.fields.Minimum_Amount__c, + Level__c.fields.Name, + Level__c.fields.Previous_Level_Field__c, + Level__c.fields.Source_Field__c, + Level__c.fields.Target__c + }; + + if (!perms.canCreate(Level__c.SObjectType, sObjectFieldsCreate)) { + return false; + } + + return true; + } } \ No newline at end of file diff --git a/force-app/main/default/pages/LVL_LevelEdit.page b/force-app/main/default/pages/LVL_LevelEdit.page index 18f0042027d..0f30ffa6529 100644 --- a/force-app/main/default/pages/LVL_LevelEdit.page +++ b/force-app/main/default/pages/LVL_LevelEdit.page @@ -5,6 +5,10 @@ lightningStylesheets="true" title="{!$ObjectType.Level__c.labelPlural}"> + + + + @@ -26,7 +30,7 @@ $ObjectType.Level__c.fields.Previous_Level_Field__c.updateable, $ObjectType.Level__c.fields.Source_Field__c.updateable, $ObjectType.Level__c.Fields.Target__c.updateable)}" /> - +
Date: Tue, 14 Nov 2023 21:41:00 -0800 Subject: [PATCH 31/35] Remove fields to check from hasFieldReadAccess() --- .../default/classes/RD2_PauseForm_CTRL.cls | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/force-app/main/default/classes/RD2_PauseForm_CTRL.cls b/force-app/main/default/classes/RD2_PauseForm_CTRL.cls index acf3185c89c..72559fe6b3c 100644 --- a/force-app/main/default/classes/RD2_PauseForm_CTRL.cls +++ b/force-app/main/default/classes/RD2_PauseForm_CTRL.cls @@ -143,22 +143,22 @@ public with sharing class RD2_PauseForm_CTRL { UTIL_Permissions.canUpdate(rdObjectName, false); } - private static Boolean hasFieldReadAccess(Set queryFields) { - String nameSpacedField; - - for (String queryField:queryFields) { - if (queryField.contains('__r')) { - continue; - } - - if (!queryField.contains('npe03__') && queryField.contains('__c')) { - nameSpacedField = UTIL_Namespace.StrTokenNSPrefix(queryField); - } - else { - nameSpacedField = queryField; - } + private static Boolean hasFieldReadAccess() { + Set fieldsToCheck = new Set{ + 'StartDate__c', + 'InstallmentFrequency__c', + 'npe03__Installment_Period__c', + 'npe03__Amount__c', + 'PaymentMethod__c', + 'Day_of_Month__c', + 'Status__c', + 'RecurringType__c', + 'EndDate__c' + }; - if (!UTIL_Permissions.canRead('npe03__Recurring_Donation__c', nameSpacedField, false)) { + for (String queryField:fieldsToCheck) { + if (!UTIL_Permissions.canRead('npe03__Recurring_Donation__c', + UTIL_Namespace.StrAllNSPrefix(queryField), false)) { return false; } } @@ -195,7 +195,7 @@ public with sharing class RD2_PauseForm_CTRL { queryFields.add('CurrencyIsoCode'); } - if (!hasFieldReadAccess(queryFields)) { + if (!hasFieldReadAccess()) { throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); } From cb4557e886dc5d8477987a64db33d9f9f22ae5bc Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Tue, 14 Nov 2023 21:44:25 -0800 Subject: [PATCH 32/35] Remove Undeliverable__c from fields to check for Address read access --- force-app/main/default/classes/HH_Container_LCTRL.cls | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/force-app/main/default/classes/HH_Container_LCTRL.cls b/force-app/main/default/classes/HH_Container_LCTRL.cls index 02244d53ece..6cb5e690791 100644 --- a/force-app/main/default/classes/HH_Container_LCTRL.cls +++ b/force-app/main/default/classes/HH_Container_LCTRL.cls @@ -565,8 +565,7 @@ public with sharing class HH_Container_LCTRL { 'Seasonal_End_Month__c', 'Seasonal_End_Day__c', 'Geolocation__Latitude__s', - 'Geolocation__Longitude__s', - 'Undeliverable__c' + 'Geolocation__Longitude__s' }; for (String addressField : addressFields) { From e1220607c23a8c7036c9139374c330fc7f30a129 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Tue, 14 Nov 2023 22:50:01 -0800 Subject: [PATCH 33/35] Add permissions check for CampaignMemberStatus --- .../classes/HH_CampaignDedupeBTN_CTRL.cls | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls b/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls index 78de10511cf..a2c05bc1771 100644 --- a/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls +++ b/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls @@ -193,9 +193,7 @@ public without sharing class HH_CampaignDedupeBTN_CTRL { ********************************************************************************************************/ public static integer MarkDuplicatesFromList(ID campaignId, list listCM) { - if (!UTIL_Permissions.canUpdate('CampaignMember', 'Status', false)) { - throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); - } + validateAccessPermissions(); final String DUPE_STATUS_SUFFIX = System.Label.hhCmpDedupeStatus; @@ -295,6 +293,26 @@ public without sharing class HH_CampaignDedupeBTN_CTRL { return returnsize; } + private static void validateAccessPermissions() { + if (!(UTIL_Permissions.canUpdate('CampaignMember', 'Status', false)) && + () { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } + + Set cmsFields = new Set{ + 'CampaignId', + 'Label', + 'HasResponded', + 'SortOrder' + }; + for (String cmsField : cmsFields) { + if (!(UTIL_Permissions.canRead('CampaignMemberStatus', cmsField, false) && + UTIL_Permissions.canCreate('CampaignMemberStatus', cmsField, false))) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } + } + } + /******************************************************************************************************* * @description Adds a Message to the visualforce page * @param arg the message string From 1a05e57006049e00d1ed0cbcc8cbcb601ddcba19 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Tue, 14 Nov 2023 22:58:43 -0800 Subject: [PATCH 34/35] Remove @TestVisible annotation --- force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls | 1 - 1 file changed, 1 deletion(-) diff --git a/force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls b/force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls index 0cf32df3b05..b576dc31a8d 100644 --- a/force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls +++ b/force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls @@ -49,7 +49,6 @@ public with sharing class PSC_ManageSoftCredits_CTRL { @TestVisible private String currencySymbol; /** @description Set to true if the user has the appropriate permissions to access the page */ - @TestVisible public Boolean hasAccess { get { if (hasAccess == null) { From 2245fbf235a08319a394775b767be4da746a1759 Mon Sep 17 00:00:00 2001 From: Reede Stockton Date: Wed, 15 Nov 2023 15:57:52 -0800 Subject: [PATCH 35/35] Fix copy/paste error resulting in bad build --- force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls b/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls index a2c05bc1771..eadce8cb670 100644 --- a/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls +++ b/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls @@ -294,8 +294,7 @@ public without sharing class HH_CampaignDedupeBTN_CTRL { } private static void validateAccessPermissions() { - if (!(UTIL_Permissions.canUpdate('CampaignMember', 'Status', false)) && - () { + if (!UTIL_Permissions.canUpdate('CampaignMember', 'Status', false)) { throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); }