Skip to content

Instantly share code, notes, and snippets.

@FishOfPrey
Created November 1, 2019 09:49
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save FishOfPrey/bb8a2781adff8eecc60c23bcd6d21cac to your computer and use it in GitHub Desktop.
Differences between baseline ESAPI insertAsUser and stripping version
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);
}
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