Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#419 - implements a proposal for supporting native User Mode Database Operations introduced in Summer '22 #420

Merged
merged 13 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 62 additions & 24 deletions sfdx-source/apex-common/main/classes/fflib_QueryFactory.cls
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
**/
public class fflib_QueryFactory { //No explicit sharing declaration - inherit from caller
public enum SortOrder {ASCENDING, DESCENDING}
public enum FLSEnforcement{NONE, LEGACY, USER_MODE, SYSTEM_MODE}

/**
* This property is read-only and may not be set after instantiation.
Expand All @@ -71,8 +72,8 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
* This can optionally be enforced (or not) by calling the setEnforceFLS method prior to calling
* one of the selectField or selectFieldset methods.
**/
private Boolean enforceFLS;
private FLSEnforcement mFlsEnforcement;

private Boolean sortSelectFields = true;
private Boolean allRows = false;

Expand All @@ -86,12 +87,26 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
private Map<Schema.ChildRelationship, fflib_QueryFactory> subselectQueryMap;

private String getFieldPath(String fieldName, Schema.sObjectType relatedSObjectType){

//Enforcing FLS using the legacy heuristic requires resolving the full field path to its respective
//Describe result to test for isAccessible on the DescribeFieldResult
//This is computationally expensive and should be bypassed if the QueryFactory instance is not
//enforcing FLS
//Starting in Summer '22, Apex can natively enforce CRUD and FLS with User Mode Operations
//Someday, the LEGACY FLSEnforcement heuristic will be removed
if(mFlsEnforcement == FLSEnforcement.USER_MODE || mFlsEnforcement == FLSEnforcement.SYSTEM_MODE){
return fieldName.toLowerCase();
}

if(!fieldName.contains('.')){ //single field
Schema.SObjectField token = fflib_SObjectDescribe.getDescribe(table).getField(fieldName.toLowerCase());
if(token == null)
throw new InvalidFieldException(fieldName,this.table);
if (enforceFLS)
fflib_SecurityUtils.checkFieldIsReadable(this.table, token);
if(token == null) {
throw new InvalidFieldException(fieldName, this.table);
}
if(mFlsEnforcement == FLSEnforcement.LEGACY) {
fflib_SecurityUtils.checkFieldIsReadable(this.table, token);
}

return token.getDescribe().getName();
}

Expand All @@ -104,7 +119,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
Schema.SObjectField token = fflib_SObjectDescribe.getDescribe(lastSObjectType).getField(field.toLowerCase());
DescribeFieldResult tokenDescribe = token != null ? token.getDescribe() : null;

if (token != null && enforceFLS) {
if (token != null && mFlsEnforcement == FLSEnforcement.LEGACY) {
fflib_SecurityUtils.checkFieldIsReadable(lastSObjectType, token);
}

Expand Down Expand Up @@ -170,7 +185,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
this.table = table;
fields = new Set<String>();
order = new List<Ordering>();
enforceFLS = false;
mFlsEnforcement = FLSEnforcement.NONE;
}

/**
Expand Down Expand Up @@ -199,12 +214,18 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
* permission enforced. If this method is not called, the default behavior
* is that FLS read permission will not be checked.
* @param enforce whether to enforce field level security (read)
* @deprecated - use the setEnforceFLS overload that specifies Legacy or Native FLS enforcement
**/
public fflib_QueryFactory setEnforceFLS(Boolean enforce){
this.enforceFLS = enforce;
return setEnforceFLS(enforce ? FLSEnforcement.LEGACY : FLSEnforcement.NONE);
}

public fflib_QueryFactory setEnforceFLS(FLSEnforcement enforcement){
this.mFlsEnforcement = enforcement;
return this;
}


/**
* Sets a flag to indicate that this query should have ordered
* query fields in the select statement (this at a small cost to performance).
Expand Down Expand Up @@ -243,10 +264,12 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
* @exception InvalidFieldException If the field is null {@code field}.
**/
public fflib_QueryFactory selectField(Schema.SObjectField field){
if(field == null)
throw new InvalidFieldException(null,this.table);
if (enforceFLS)
if(field == null) {
throw new InvalidFieldException(null, this.table);
}
if (mFlsEnforcement == FLSEnforcement.LEGACY) {
fflib_SecurityUtils.checkFieldIsReadable(table, field);
}
fields.add( getFieldTokenPath(field) );
return this;
}
Expand Down Expand Up @@ -276,10 +299,12 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
**/
public fflib_QueryFactory selectFields(Set<Schema.SObjectField> fields){
for(Schema.SObjectField token:fields){
if(token == null)
throw new InvalidFieldException();
if (enforceFLS)
fflib_SecurityUtils.checkFieldIsReadable(table, token);
if(token == null) {
throw new InvalidFieldException();
}
if (mFlsEnforcement == FLSEnforcement.LEGACY) {
fflib_SecurityUtils.checkFieldIsReadable(table, token);
}
this.fields.add( getFieldTokenPath(token) );
}
return this;
Expand All @@ -290,11 +315,13 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
* @exception InvalidFieldException if the fields are null {@code fields}.
**/
public fflib_QueryFactory selectFields(List<Schema.SObjectField> fields){
for(Schema.SObjectField token:fields){
if(token == null)
for(Schema.SObjectField token : fields){
if(token == null) {
throw new InvalidFieldException();
if (enforceFLS)
fflib_SecurityUtils.checkFieldIsReadable(table, token);
}
if (mFlsEnforcement == FLSEnforcement.LEGACY) {
fflib_SecurityUtils.checkFieldIsReadable(table, token);
}
this.fields.add( getFieldTokenPath(token) );
}
return this;
Expand Down Expand Up @@ -679,7 +706,9 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
String result = 'SELECT ';
//if no fields have been added, just add the Id field so that the query or subquery will not just fail
if (fields.size() == 0){
if (enforceFLS) fflib_SecurityUtils.checkFieldIsReadable(table, 'Id');
if (mFlsEnforcement == FLSEnforcement.LEGACY){
fflib_SecurityUtils.checkFieldIsReadable(table, 'Id');
}
result += 'Id';
}else {
List<String> fieldsToQuery = new List<String>(fields);
Expand All @@ -697,8 +726,17 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
}
}
result += ' FROM ' + (relationship != null ? relationship.getRelationshipName() : table.getDescribe().getName());
if(conditionExpression != null)
result += ' WHERE '+conditionExpression;

if(conditionExpression != null) {
result += ' WHERE ' + conditionExpression;
}

if(mFlsEnforcement == FLSEnforcement.USER_MODE){
result += ' WITH USER_MODE';
}
else if(mFlsEnforcement == FLSEnforcement.SYSTEM_MODE){
result += ' WITH SYSTEM_MODE';
}

if(order.size() > 0){
result += ' ORDER BY ';
Expand Down Expand Up @@ -730,7 +768,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
.setLimit(this.limitCount)
.setOffset(this.offsetCount)
.setCondition(this.conditionExpression)
.setEnforceFLS(this.enforceFLS);
.setEnforceFLS(this.mFlsEnforcement);

Map<Schema.ChildRelationship, fflib_QueryFactory> subqueries = this.subselectQueryMap;
if(subqueries != null) {
Expand Down
89 changes: 77 additions & 12 deletions sfdx-source/apex-common/main/classes/fflib_SObjectSelector.cls
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
public abstract with sharing class fflib_SObjectSelector
implements fflib_ISObjectSelector
{
public enum DataAccess{LEGACY, USER_MODE, SYSTEM_MODE}

/**
* Indicates whether the sObject has the currency ISO code field for organisations which have multi-currency
* enabled.
Expand Down Expand Up @@ -64,6 +66,8 @@ public abstract with sharing class fflib_SObjectSelector
**/
private String m_orderBy;

private DataAccess m_dataAccess = DataAccess.LEGACY;

/**
* Sort the query fields in the select statement (defaults to true, at the expense of performance).
* Switch this off if you need more performant queries.
Expand Down Expand Up @@ -111,11 +115,17 @@ public abstract with sharing class fflib_SObjectSelector
{
this(includeFieldSetFields, true, false);
}


public fflib_SObjectSelector(Boolean includeFieldSetFields, DataAccess dataAccess)
{
this(includeFieldSetFields, false, false, false, dataAccess);
}

/**
* Constructs the Selector
*
* @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well
* @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well
* @deprecated - consider using userMode for native platform enforcement of CRUD and FLS
**/
public fflib_SObjectSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS)
{
Expand All @@ -129,15 +139,23 @@ public abstract with sharing class fflib_SObjectSelector
* @param enforceCRUD Enforce CRUD security
* @param enforceFLS Enforce Field Level Security
* @param sortSelectFields Set to false if selecting many columns to skip sorting select fields and improve performance
* @deprecated - consider using dataAccess for native platform enforcement of CRUD and FLS
**/
public fflib_SObjectSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS, Boolean sortSelectFields)
{
this(includeFieldSetFields,enforceCRUD,enforceFLS,sortSelectFields,DataAccess.LEGACY);
}

private fflib_SObjectSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS, Boolean sortSelectFields, DataAccess dataAccess)
{
m_includeFieldSetFields = includeFieldSetFields;
m_enforceCRUD = enforceCRUD;
m_enforceFLS = enforceFLS;
m_sortSelectFields = sortSelectFields;
m_DataAccess = dataAccess;
}


/**
* Override this method to provide a list of Fieldsets that can optionally drive inclusion of additional fields in the base queries
**/
Expand Down Expand Up @@ -179,6 +197,7 @@ public abstract with sharing class fflib_SObjectSelector

/**
* @description Set the selector to enforce FLS Security
* @deprecated -- consider using setDataAccess to enforce native Apex User Mode Operations instead
**/
public fflib_SObjectSelector enforceFLS()
{
Expand Down Expand Up @@ -211,6 +230,18 @@ public abstract with sharing class fflib_SObjectSelector
return this;
}

public fflib_SObjectSelector setDataAccess(DataAccess access){
this.m_dataAccess = access;

//You can't mix and match the legacy enforceFls and assertCRUD with the SYSTEM_MODE or USER_MODE
if(this.m_dataAccess != DataAccess.LEGACY){
ignoreCRUD();
m_enforceFLS = false;
}

return this;
}

/**
* Returns True if this Selector instance has been instructed by the caller to include Field Set fields
**/
Expand All @@ -235,6 +266,10 @@ public abstract with sharing class fflib_SObjectSelector
return m_enforceCRUD;
}

public DataAccess getDataAccess(){
return m_dataAccess;
}

/**
* Provides access to the builder containing the list of fields base queries are using, this is demand
* created if one has not already been defined via setFieldListBuilder
Expand Down Expand Up @@ -349,15 +384,15 @@ public abstract with sharing class fflib_SObjectSelector
**/
public fflib_QueryFactory newQueryFactory()
{
return newQueryFactory(m_enforceCRUD, m_enforceFLS, true);
return newQueryFactory(m_enforceCRUD, m_enforceFLS, true, m_DataAccess);
}

/**
* Returns a QueryFactory configured with the Selectors object, fields, fieldsets and default order by
**/
public fflib_QueryFactory newQueryFactory(Boolean includeSelectorFields)
{
return newQueryFactory(m_enforceCRUD, m_enforceFLS, includeSelectorFields);
return newQueryFactory(m_enforceCRUD, m_enforceFLS, includeSelectorFields, m_DataAccess);
}

/**
Expand All @@ -367,9 +402,22 @@ public abstract with sharing class fflib_SObjectSelector
public fflib_QueryFactory newQueryFactory(Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Admittedly, indent characters are not standard throughout the framework, but since we're touching this function, let's ensure the indent characters are the same for these related functions.

{
// Construct QueryFactory around the given SObject
return newQueryFactory(
assertCRUD,
enforceFLS,
includeSelectorFields,
DataAccess.LEGACY);
}

private fflib_QueryFactory newQueryFactory(Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields, DataAccess dataAccess)
{
// Construct QueryFactory around the given SObject
return configureQueryFactory(
new fflib_QueryFactory(getSObjectType2()),
assertCRUD, enforceFLS, includeSelectorFields);
new fflib_QueryFactory(getSObjectType2()),
assertCRUD,
enforceFLS,
includeSelectorFields,
dataAccess);
}

/**
Expand Down Expand Up @@ -408,7 +456,8 @@ public abstract with sharing class fflib_SObjectSelector
subSelectQueryFactory,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sync indent character.

m_enforceCRUD,
m_enforceFLS,
includeSelectorFields);
includeSelectorFields,
m_dataAccess);
}

/**
Expand All @@ -424,8 +473,8 @@ public abstract with sharing class fflib_SObjectSelector
**/
public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory, String relationshipName, Boolean includeSelectorFields)
{
fflib_QueryFactory subSelectQueryFactory = parentQueryFactory.subselectQuery(relationshipName);
return configureQueryFactory(subSelectQueryFactory, m_enforceCRUD, m_enforceFLS, includeSelectorFields);
fflib_QueryFactory subSelectQueryFactory = parentQueryFactory.subselectQuery(relationshipName);
return configureQueryFactory(subSelectQueryFactory, m_enforceCRUD, m_enforceFLS, includeSelectorFields, m_dataAccess);
}

/**
Expand All @@ -439,9 +488,13 @@ public abstract with sharing class fflib_SObjectSelector
/**
* Configures a QueryFactory instance according to the configuration of this selector
**/
private fflib_QueryFactory configureQueryFactory(fflib_QueryFactory queryFactory, Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields)
private fflib_QueryFactory configureQueryFactory(fflib_QueryFactory queryFactory,
Boolean assertCRUD,
Boolean enforceFLS,
Boolean includeSelectorFields,
DataAccess access)
{
// CRUD and FLS security required?
// Legacy CRUD enforcement required?
if (assertCRUD)
{
try {
Expand All @@ -453,7 +506,19 @@ public abstract with sharing class fflib_SObjectSelector
'Permission to access an ' + getSObjectType().getDescribe().getName() + ' denied.');
}
}
queryFactory.setEnforceFLS(enforceFLS);

fflib_QueryFactory.FLSEnforcement fls = fflib_QueryFactory.FLSEnforcement.NONE;
if(access == DataAccess.USER_MODE){
fls = fflib_QueryFactory.FLSEnforcement.USER_MODE;
}
else if(access == DataAccess.SYSTEM_MODE){
fls = fflib_QueryFactory.FLSEnforcement.SYSTEM_MODE;
}
else if(enforceFLS){
fls = fflib_QueryFactory.FLSEnforcement.LEGACY;
}

queryFactory.setEnforceFLS(fls);

// Configure the QueryFactory with the Selector fields?
if(includeSelectorFields)
Expand Down
Loading