[SalesForce] How to check field accessiblity of a field that is related to the object being checked

While developing secure controller actions for a Lightning Component, I ran across an issue checking accessibility for an Account level attribute related a Contact.

For example:

Schema.SObjectType.Contact.fields.getMap().get('Account.My_custom_field__c').getDescribe().isAccessible()

The piece of code, get('Account.My_custom_field__c') returns null, and the code throws an exception. I understand why it is happening — the field it's trying to check is technically on the Account object, not the Contact object.

The way I have written my code is, I have an array of fields, which I later use to piece together a query. I loop over each field, check field accessibility, then if the field is accessible, I add to the queryable fields list.

Here's a full code example, and my current work-around:

// this list of fields are these that will be returned to the component
String[] selectableFields = new String[] { 'Id', 'Name', 'Title', 'Email', 'Fax', 'Phone', 
    'HomePhone', 'MobilePhone', 'Account.My_custom_field__c', 'MailingStreet',
    'MailingCity', 'MailingState', 'MailingPostalCode' };        

Map<String, Schema.SObjectField> fieldMap = Schema.SObjectType.Contact.fields.getMap();
List<String> columns = new List<String>();
for(String field : selectableFields) {
    if(DEBUG) System.debug('Selectable field: ' + field);
    // it's not possible to check accessiblilty on second-level attributes, so just add it to the columns list
    if(field.contains('.')) {
        columns.add(field.trim());
        if(DEBUG) System.debug(' - unknown accessiblity due to second level attribute');                
    }
    else if(fieldMap.get(field).getDescribe().isAccessible()) {
        columns.add(field.trim());
        if(DEBUG) System.debug(' - accessible');
    }
    else {
        if(DEBUG) System.debug(' - inaccessible');
    }
}
// id will also be accessible if has access to object, so check if more than 1 column is accessible
if(columns.size() > 1) {
    // get the contact id from the the user's record
    String contactId = [select ContactId from user where id = :UserInfo.getUserId() limit 1][0].ContactId;  
    return Database.query('select ' + String.join(columns, ',') + ' from Contact where id = :contactId');
}
else {
    throw new AuraHandledException(NO_ACCESS_MESSAGE);
}   

I'd like to avoid having to write if this else that style code to handle what I'm calling second-level attributes in the code above, but I don't see any way around it.

I realize I can just hard-code the Account.My_custom_field__c accessibility check, but I wanted to first see if there are any patterns or best practices to check for field accessibility that take multi-level attributes into account before I do.

Best Answer

I built out a system to do just that for a FluentQuery tool I was working on. Here's the relevant bit of the DescribeCache:

global class DescribeCache
{
    static final Map<SObjectType, Map<String, SObjectField>> fields =
        new Map<SObjectType, Map<String, SObjectField>>();

    global static SObjectField getField(SObjectType sObjectType, String fieldPath)
    {
        if (sObjectType == null || fieldPath == null) return null;
        if (!fields.containsKey(sObjectType))
            fields.put(sObjectType, sObjectType.getDescribe().fields.getMap());
        if (!fieldPath.contains('.')) return fields.get(sObjectType).get(fieldPath);

        Relationship relation = new Relationship(fieldPath.substringBefore('.'));
        SObjectField field = fields.get(sObjectType).get(relation.getFieldPath());
        if (field == null) return null;

        SObjectType parentType = field.getDescribe().getReferenceTo()[0];
        return getField(parentType, fieldPath.substringAfter('.'));
    }
    class Relationship
    {
        final String name;
        public Relationship(String name) { this.name = name; }
        public String getFieldPath()
        {
            if (name == null) return null;
            return name.endsWith('__r') ?
                name.replace('__r', '__c') : name + 'Id';
        }
    }
}

And the relevant bit of my Permissions class minus all the mocking:

// change to public static if this part is all you need
protected virtual Boolean canAccess(SObjectType sObjectType, String fieldPath)
{
    SObjectField field = DescribeCache.getField(sObjectType, fieldPath);
    return (field == null) ? false : field.getDescribe().isAccessible();
}

The library is open source. Feel free to pull whatever you need out and adjust for your own use.

Related Topic