1

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.

2

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.