apex-enterprise-patterns / fflib-apex-common

Common Apex Library supporting Apex Enterprise Patterns and much more!
BSD 3-Clause "New" or "Revised" License
906 stars 515 forks source link

fflib_QueryFactory ListIndex out of bounds #309

Closed cropredyHelix closed 3 years ago

cropredyHelix commented 3 years ago
Message:List index out of bounds: 0 
Stacktrace:Class.fflib_QueryFactory.getFieldPath: line 111, column 1
Class.fflib_QueryFactory.selectFields: line 234, column 1
Class.PermissionSetAssignmentsSelector.selectByUserIdAndPermissions: line 55, column 1

The relevant method where this fails is getFieldPath(..) shown below and line 111 is:

lastSObjectType = tokenDescribe.getReferenceTo()[0]; //if it's polymorphic doesn't matter which one we get

The use case where this happens is

Analysis

private String getFieldPath(String fieldName){
    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);    
        return token.getDescribe().getName();
    }

    //traversing FK relationship(s)
    List<String> fieldPath = new List<String>();
    Schema.SObjectType lastSObjectType = table;
    Iterator<String> i = fieldName.split('\\.').iterator();
    while(i.hasNext()){
        String field = i.next();
        Schema.SObjectField token = fflib_SObjectDescribe.getDescribe(lastSObjectType).getField(field.toLowerCase());
        DescribeFieldResult tokenDescribe = token != null ? token.getDescribe() : null;

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

        if(token != null && i.hasNext() && tokenDescribe.getSoapType() == Schema.SoapType.ID){
            lastSObjectType = tokenDescribe.getReferenceTo()[0]; //if it's polymorphic doesn't matter which one we get
            fieldPath.add(tokenDescribe.getRelationshipName());
        }else if(token != null && !i.hasNext()){
            fieldPath.add(tokenDescribe.getName());
        }else{
            if(token == null)
                throw new InvalidFieldException(field,lastSObjectType);
            else
                throw new NonReferenceFieldException(lastSObjectType+'.'+field+' is not a lookup or master-detail field but is used in a cross-object query field.');
        }
    }

        return String.join(fieldPath,'.');
    }
wimvelzeboer commented 3 years ago

Hi @cropredyHelix I think this issue was already discussed in this issue: https://github.com/apex-enterprise-patterns/fflib-apex-common/issues/233 And there is already an PR open with a fix: https://github.com/apex-enterprise-patterns/fflib-apex-common/pull/251

cropredyHelix commented 3 years ago

@wimvelzeboer - ah - so the fix is basically -- if there's no FLS to enforce, just bail out of the method right away. I should have thought of that as we don't enforce FLS in our enterprise org's selectors. Thanks !

cropredyHelix commented 3 years ago

hooray - V51.0 solves this @wimvelzeboer

VERSIONED BEHAVIOR CHANGE: From version 51.0 onwards, the getReferenceTo() method in the Schema.DescribeFieldResult class returns referenced objects that aren’t accessible to the context user. If the context user has access to an object’s field that references another object, irrespective of the context user’s access to the cross-referenced object, the DescribeFieldResult.getReferenceTo() method returns references. In version 50.0 and earlier, if the context user doesn’t have access to the cross-referenced object, the method returns an empty list.

daveespo commented 3 years ago

Per the previous comment on this ticket, we believe this is likely fixed with Spring '21 and #317 -- please reopen the issue if you disagree