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/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(); diff --git a/force-app/main/default/classes/GE_GiftEntryController.cls b/force-app/main/default/classes/GE_GiftEntryController.cls index 04cb1f04beb..7c75f999975 100644 --- a/force-app/main/default/classes/GE_GiftEntryController.cls +++ b/force-app/main/default/classes/GE_GiftEntryController.cls @@ -424,10 +424,20 @@ 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 { + // 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); + } upsert dataImportObject Id; return dataImportObject; + } catch (UTIL_Permissions.InsufficientPermissionException e) { + throw new AuraHandledException(e.getMessage()); } catch (Exception e) { String JSONExceptionData = ERR_ExceptionData.createExceptionWrapperJSONString(e); @@ -435,6 +445,23 @@ 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)) { + if (!fieldName.equalsIgnoreCase('Id')) { + return false; + } + } + } + + return true; + } + /******************************************************************************************************* * @description Run the DataImport process on a single gift * @param dataImport DataImport record to be processed @@ -1062,7 +1089,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[] {}; Form_Template__c[] templates = [ @@ -1162,6 +1188,17 @@ public with sharing class GE_GiftEntryController { String description, String formatVersion, String templateJSON) { + + Set fieldsToCheck = new Set{ + 'Name', + 'Description__c', + 'Template_JSON__c', + 'Format_Version__c' + }; + if (!canUpsertFormTemplate(fieldsToCheck)) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } + if (templateJSON != null) { Form_Template__c templateObj = new Form_Template__c(Id = id, Name = name, @@ -1175,6 +1212,22 @@ public with sharing class GE_GiftEntryController { return null; } + + private static Boolean canUpsertFormTemplate(Set fieldsToCheck) { + if (!UTIL_Permissions.canCreate(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'))) { + return false; + } + + for (String fieldName : fieldsToCheck) { + if (!UTIL_Permissions.canUpdate(UTIL_Namespace.StrAllNSPrefix('Form_Template__c'), + UTIL_Namespace.StrAllNSPrefix(fieldName), false)) { + return false; + } + } + + return true; + } + /******************************************************************************************************* * @description Method checks if the provided name is in use by another existing Form Template. * @@ -1345,6 +1398,14 @@ 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)) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } + String query = new UTIL_Query() .withSelectFields(new Set{UTIL_Currency.CURRENCY_ISO_CODE_FIELD}) .withFrom(DataImportBatch__c.SObjectType) diff --git a/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls b/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls index a0723cafca9..eadce8cb670 100644 --- a/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls +++ b/force-app/main/default/classes/HH_CampaignDedupeBTN_CTRL.cls @@ -193,6 +193,8 @@ public without sharing class HH_CampaignDedupeBTN_CTRL { ********************************************************************************************************/ public static integer MarkDuplicatesFromList(ID campaignId, list listCM) { + validateAccessPermissions(); + final String DUPE_STATUS_SUFFIX = System.Label.hhCmpDedupeStatus; //check for the Household Duplicate status values we need @@ -291,6 +293,25 @@ 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 diff --git a/force-app/main/default/classes/HH_Container_LCTRL.cls b/force-app/main/default/classes/HH_Container_LCTRL.cls index 252a2e0119c..6cb5e690791 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,75 @@ 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' + }; + + 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) { diff --git a/force-app/main/default/classes/HH_ManageHH_CTRL.cls b/force-app/main/default/classes/HH_ManageHH_CTRL.cls index 7c5414d65aa..bf7d016d055 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('Contact','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 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/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/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/classes/OPP_OpportunityNamingBTN_CTRL.cls b/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls index 0b608f46831..da58db0b016 100644 --- a/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls +++ b/force-app/main/default/classes/OPP_OpportunityNamingBTN_CTRL.cls @@ -53,8 +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', false)) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } + OPP_OpportunityNaming.refreshOppNames(thisOpp); update thisOpp; redirect = true; } catch (Exception ex) { diff --git a/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls b/force-app/main/default/classes/PMT_PaymentWizard_CTRL.cls index adddcb04640..c2fdb841d36 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) { + ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, System.Label.commonAccessErrorMessage)); + return null; + } + if (haveAmount || removePaidPayments) { // clear the list newPayments.clear(); @@ -538,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() { @@ -576,8 +585,24 @@ public with sharing class PMT_PaymentWizard_CTRL { }; } - private Set opportunityFields() { - return new Set{Opportunity.fields.Amount}; + private Set opportunityReadFields() { + 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 + }; + } + private Set opportunityModifyFields() { + return new Set{ + Opportunity.fields.Amount + }; } @@ -652,7 +677,12 @@ public with sharing class PMT_PaymentWizard_CTRL { * @return PageReference null */ public PageReference createPayments() { - Savepoint sp = Database.setSavepoint(); + if (!hasFeatureAccess) { + ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, System.Label.commonAccessErrorMessage)); + return null; + } + + Savepoint sp = Database.setSavepoint(); try { List paymentsToDelete = new List(); List existingPayments = [ diff --git a/force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls b/force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls index 5652a72a807..b576dc31a8d 100644 --- a/force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls +++ b/force-app/main/default/classes/PSC_ManageSoftCredits_CTRL.cls @@ -231,16 +231,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 +282,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/RD2_PauseForm_CTRL.cls b/force-app/main/default/classes/RD2_PauseForm_CTRL.cls index 945a55d219d..72559fe6b3c 100644 --- a/force-app/main/default/classes/RD2_PauseForm_CTRL.cls +++ b/force-app/main/default/classes/RD2_PauseForm_CTRL.cls @@ -143,6 +143,29 @@ public with sharing class RD2_PauseForm_CTRL { UTIL_Permissions.canUpdate(rdObjectName, false); } + 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' + }; + + for (String queryField:fieldsToCheck) { + if (!UTIL_Permissions.canRead('npe03__Recurring_Donation__c', + UTIL_Namespace.StrAllNSPrefix(queryField), false)) { + return false; + } + } + + return true; + } + /** * @description Query and Construct Recurring Donation Record * @param rdId Recurring Donation Id @@ -172,6 +195,10 @@ public with sharing class RD2_PauseForm_CTRL { queryFields.add('CurrencyIsoCode'); } + if (!hasFieldReadAccess()) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } + RD2_QueryService queryService = new RD2_QueryService(); queryFields.add(queryService.getScheduleSubQuery()); @@ -256,6 +283,10 @@ public with sharing class RD2_PauseForm_CTRL { public static void savePause(String jsonPauseData) { Boolean hasScheduleChanged = false; try { + if (!hasAccess()) { + throw new UTIL_Permissions.InsufficientPermissionException(System.Label.commonAccessErrorMessage); + } + PauseData pause = (PauseData) JSON.deserialize(jsonPauseData, PauseData.class); RD2_RecurringDonation rd = getRecurringDonation(pause.rdId); @@ -296,9 +327,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); 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 + '"'); } /**** 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 @@ - 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 @@
- +
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)}" /> - +
+ + + + @@ -34,7 +38,7 @@
- +