Created
November 1, 2019 09:49
-
-
Save FishOfPrey/bb8a2781adff8eecc60c23bcd6d21cac to your computer and use it in GitHub Desktop.
Differences between baseline ESAPI insertAsUser and stripping version
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public SFDCAccessControlResults.InsertResults insertAsUser(SObject [] devObjs, List<String> fieldsToSet) { | |
if (devObjs == null || devObjs.size() == 0 || fieldsToSet == null || fieldsToSet.size() == 0) | |
throw new AccessControlDmlException('null or empty parameter'); | |
Schema.DescribeSObjectResult d = devObjs.getSObjectType().getDescribe(); | |
if (d.isCreateable() == false) | |
throw new SFDCAccessControlException('Access Violation', | |
SFDCAccessControlException.ExceptionType.OBJECT_ACCESS_VIOLATION, | |
SFDCAccessControlException.ExceptionReason.NO_CREATE, | |
d.getName(), | |
null); | |
Map<String, Schema.SObjectField> fieldMap = SFDCPlugins.SFDC_DescribeInfoCache.fieldMapFor(devObjs.getSObjectType()); | |
Set<String> creatableFields = fieldsToStringSet(getCreatableFields(fieldMap)); | |
/* | |
create a clean array by cloning the array, empty it, and refill the member objects by creating clean ones. | |
We can't just create an array of sObjects and add the objects using devObj.getSObjectType().newSObject(); | |
because it will fail on insert. The error will be: System.TypeException: DML not allowed on abstract class SObject | |
*/ | |
sObject [] cleanObjs = devObjs.clone(); | |
cleanObjs.clear(); | |
// for each object in the array set only creatable fields | |
for (sObject devObj: devObjs) { | |
// start from a fresh sObject of same type and only set fields the user is allowed to set | |
sObject cleanObj = devObj.getSObjectType().newSObject(); | |
// set all fields that were requested and the user has permission to set - throw an exception if a field was requested and user can't set and in ALL_OR_NONE mode | |
for (String fieldName : fieldsToSet) { | |
fieldName = fieldName.toLowerCase(); | |
if (creatableFields == null || creatableFields.contains(fieldName) == false) { | |
// creatableFields is either null which means no fields are allowed to be set by user, or is not null but does not contain the current fieldName | |
if (omode == OperationMode.ALL_OR_NONE) | |
// if operation mode == ALL_OR_NONE - throw exception because user does not have permission to set fieldName | |
throw new SFDCAccessControlException('Access Violation', | |
SFDCAccessControlException.ExceptionType.FIELD_ACCESS_VIOLATION, | |
SFDCAccessControlException.ExceptionReason.NO_CREATE, | |
d.getName(), | |
fieldName); | |
} | |
else { | |
// user has permission to set fieldName and it was request by the developer - so set it | |
// if the developer did not set this field and it is required, we should get an exception | |
// when we set it here, or when we perform the actual insert. | |
cleanObj.put(fieldName, devObj.get(fieldName)); | |
} | |
} | |
cleanObjs.add(cleanObj); | |
} | |
Database.SaveResult [] results = null; | |
try { | |
// call dbInsert() to enforce sharing rules if required | |
results = dbInsert(cleanObjs); | |
} catch (Exception e) { | |
throw new AccessControlDmlException('Failed to insert objects'); | |
} | |
return new SFDCAccessControlResults.InsertResults(cleanObjs, results); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public SFDCAccessControlResults.InsertResults insertAsUser(SObject [] devObjs, List<String> fieldsToSet) { | |
if (devObjs == null || devObjs.size() == 0 || fieldsToSet == null || fieldsToSet.size() == 0) | |
throw new AccessControlDmlException('null or empty parameter'); | |
System.SObjectAccessDecision decision = null; | |
try { | |
boolean enforceRootObjectCRUD = true; | |
decision = Security.stripInaccessible( | |
AccessType.CREATABLE, | |
devObjs, | |
enforceRootObjectCRUD); | |
} catch (System.NoAccessException ex) { | |
string sObjectName = ex.getMessage().substring(ex.getMessage().indexOf(':')); | |
throw new SFDCAccessControlException('Access Violation', | |
SFDCAccessControlException.ExceptionType.OBJECT_ACCESS_VIOLATION, | |
SFDCAccessControlException.ExceptionReason.NO_CREATE, | |
sObjectName, | |
null); | |
} | |
// Removed fields | |
Map<string, Set<string>> removedFields = decision.getRemovedFields(); | |
if(omode == OperationMode.ALL_OR_NONE && !removedFields.isEmpty()) { | |
for(string firstSObjectType : removedFields.keySet()) { | |
for(string firstSObjectField : removedFields.get(firstSObjectType)) { | |
// if operation mode == ALL_OR_NONE - throw exception because user does not have permission to set fieldName | |
// TODO: Check case sensitivity on the contains method call | |
if(fieldsToSet != null && !fieldsToSet.contains(firstSObjectField)) { | |
// The field that has been removed isn't one of the required fields to set. | |
// No need to error, just ignore it. | |
// This occurs when securityStripping is called before cleanObjToGivenFields | |
continue; | |
} | |
// If the fields are defined as strings the exception expects the field name lower case | |
// For a SObjectField, unchanged | |
string fieldName = (lowerCaseFieldName) ? firstSObjectField.toLowerCase() : firstSObjectField; | |
throw new SFDCAccessControlException('Access Violation', | |
SFDCAccessControlException.ExceptionType.FIELD_ACCESS_VIOLATION, | |
SFDCAccessControlException.ExceptionReason.NO_CREATE, | |
firstSObjectType, | |
fieldName); | |
} | |
} | |
} | |
// Stripped records - these are safe to insert/create/update | |
// They may still need to be checked so that only the requested fields are set. | |
List<SObject> strippedRecords = decision.getRecords(); | |
// for each object in the array ensure only requested fields are set | |
for (sObject devObj : strippedRecords) { | |
sObject cleanObj = cleanObjToGivenFields(devObj, fieldsToSet, null, SFDCAccessControlException.ExceptionReason.NO_CREATE); | |
if(cleanObj !== devObj) { | |
// Fields have been removed. Replace the stripped sObject with the cleaned one | |
strippedRecords.set(strippedRecords.indexOf(devObj), cleanObj); | |
} | |
} | |
Database.SaveResult [] results = null; | |
try { | |
// call dbInsert() to enforce sharing rules if required | |
results = dbInsert(strippedRecords); | |
} catch (Exception e) { | |
// Note, source exception is currently lost. | |
throw new AccessControlDmlException('Failed to insert objects'); | |
} | |
return new SFDCAccessControlResults.InsertResults(strippedRecords, results); | |
} | |
/* | |
* Populate the cleanObj with only the fields defined in fieldsToSet from devObj. Create an empty cleanObj if required | |
* devObj - the source sObject | |
* fieldsToSet - the API names for the fields to set. | |
* cleanObj - OPTIONAL - an empty sObject of the correct type that the fields can be populated into. It should only have the ID field set. | |
* reason - If there is an SObjectException when setting the requested fields, this is the reason to include in the resulting exception. | |
*/ | |
private sObject cleanObjToGivenFields(sObject devObj, List<string> fieldsToSet, sObject cleanObj, SFDCAccessControlException.ExceptionReason reason) { | |
Map<String, Object> fieldsMap = devObj.getPopulatedFieldsAsMap(); | |
if(new Set<string>(fieldsToSet).containsAll(fieldsMap.keySet())) { | |
// The fieldsToSet are an exact match or a superset for the fields populated on the sObject. | |
// Just use the source sObject. It doesn't have any unrequested fields set. | |
return devObj; | |
} | |
if(cleanObj == null) { | |
// start from a fresh sObject of same type and only set fields requested | |
cleanObj = devObj.getSObjectType().newSObject(); | |
} | |
// set all fields that were requested | |
for (String fieldName : fieldsToSet) { | |
try { | |
if(devObj.isSet(fieldName)) { | |
cleanObj.put(fieldName, devObj.get(fieldName)); | |
} | |
} catch (System.SObjectException ex) { | |
if (omode == OperationMode.ALL_OR_NONE) { | |
// if operation mode == ALL_OR_NONE - throw exception because user does not have permission to set fieldName | |
Schema.DescribeSObjectResult d = devObj.getSObjectType().getDescribe(); | |
fieldName = fieldName.toLowerCase(); | |
throw new SFDCAccessControlException('Access Violation', | |
SFDCAccessControlException.ExceptionType.FIELD_ACCESS_VIOLATION, | |
reason, | |
d.getName(), | |
fieldName); | |
} | |
} | |
} | |
return cleanObj; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment