Remote Actions: Leveraging class hierarchies to safely pass data

Sometimes, we need our @RemoteAction to be very flexible. This can lead to the necessity of allowing the caller to pass in some arbitrary information. But by doing so, we could be opening a security breach. In this post, I will show an example of this, as well as a possible solution to the security problem. Some knowledge about VisualForce components and remote actions will be needed to follow this.

Say, we want to implement a VF component that takes some data from the org and shows it in a custom picklist. We’d like this component to retrieve data very quickly as the user interacts with it, so we will use a remote action for this.

 

<apex:component controller="CustomPicklistController">
<apex:attribute name="objectName" description="Object to query from" type="string" required="true"/>

    <script type="text/javascript">
        function getRemoteData() {
            Visualforce.remoting.Manager.invokeAction(
                '{!$RemoteAction.CustomPicklistController.getData}',
                '{!objectName}',
                function(result, event){
                    ...
                },
                {escape: true}
            );
        }
    </script>
    
</apex:component>

 
This is the controller:

public with sharing class CustomPicklistController {

    @RemoteAction
    public static CustomPicklistData getData(String objectName) {
        List<SObject> records = retrieveData(objectName);
        CustomPicklistData data = convertData(records);
        return data;
    }

    private static List<SObject> retrieveData(
        String objectName
    ) {
        String safeObjectName = getSafeSOQLString(objectName);
        String dbQuery = 'SELECT Id, Name FROM ' + safeObjectName;
        return Database.query(dbQuery);
    }
}

 
… where getSafeSOQLString() makes sure no malicious string breaks our query by injecting SOQL code. In the example, there is no filter for the query. We are retrieving all records for the given object.

We want this component to be flexible enough to allow us to set custom specific filters wherever we instantiate it. A first approach would consist of passing the filter text as attribute to the component. This is the definition:

<apex:attribute name="filterText" type="String" required="false"
    description="Filter to append to the query"/>

 
The controller would be updated as follows:

public with sharing class CustomPicklistController {

    @RemoteAction
    public static CustomPicklistData getData(String objectName, String filterText) {
        List<SObject> records = retrieveData(objectName, filterText);
        CustomPicklistData data = convertData(records);
        return data;
    }

    private static List<SObject> retrieveData(
        String objectName,
        String filterText
    ) {
        String safeObjectName = getSafeSOQLString(objectName);
        String whereClause = String.isBlank(filterText) ? '' : ' WHERE ' + filterText;
        String dbQuery = 'SELECT Id, Name FROM ' + safeObjectName + whereClause;
        return Database.query(dbQuery);
    }
}

 
An example of instantiation for this component can look like:

<CustomPicklistController objectName="Account" filterText="{!queryCondition}"/>

 
… where queryCondition would be something like:

public String getQueryCondition() {
    return 'Name LIKE \'A%\'';
}

 
Unfortunately, we cannot apply getSafeSOQLString to filterText, as it is a piece of SOQL code. If we escaped it in order to prevent SOQL injection, it would stop working as well. We control front-end too in this case, so we could think that taking care of making front-end secure (so the user is not able to provide a fully free input for this attribute) is enough. But it isn’t: data is passed to back-end as a JSON string, which is not secure. We need an alternative way of passing the filter information from the front-end to the back-end.

 

Using precompiled, back-end data

 
So we want a component that gives us flexibility to pass whatever filter we need, but also, we don’t want to pass that filter in plain SOQL. We could think of creating a class which instances store the filter somehow codified, and then allows us to retrieve the SOQL string for that filter. A specific instance of that class, with the correct information, would be created from the VF page’s controller and passed to the component via attribute, which, in turn, would then pass the info to the @RemoteAction method. But in the end, it would not be secure either as the filter information is travelling from front-end to back-end, one way or another.

Instead, the flexibility will be moved to the back-end, while the front-end will be restricted to the options implemented in back-end. Let me explain this.

The component will now expect an instance of a class that implements this interface:

public interface ICondition {
    String getCondition();
}

 
Such instance will not contain any data. It will only give us the information about which specific class implements the condition, so we can call the getCondition() method and get the information we need. This way the query condition is not passed through front end.

For each specific filter, a custom class will be implemented, defining the condition for each component instance:

public class SpecificCondition implements ICondition {
    public String getCondition() {
        return 'Name LIKE \'A%\'';
    }
}

 
The component will now receive the name of the class that implements the condition:

<apex:component controller="CustomPicklistController">
<apex:attribute name="objectName" type="String" required="true" description="Object to query from"/>
<apex:attribute name="conditionClass" type="String" required="false" default="CustomPicklistController.NoCondition" description="Class implementing ICondition"/>
...
    <script type="text/javascript">
        function getRemoteData() {
            Visualforce.remoting.Manager.invokeAction(
                '{!$RemoteAction.CustomPicklistController.getData}',
                '{!objectName}',
                {'apexType': 'c.'+'{!conditionClass}'},
                function(result, event){
                    ...
                },
                {escape: true}
            );
        }
    </script>
...
</apex:component>

 
What happened here? We are basically applying what this article from Salesforce’s official documentation says about how to pass interface instances to a @RemoteAction method: the instance itself is not passed. What we pass, is a string with the class name (and nothing else in our example given the instance contains no data). The system will then, automatically, take care of instantiating the class.

As you can see, the default c namespace is being concatenated here:

    <script type="javascript">
        ...
        {'apexType': 'c.'+'{!conditionClass}'}
        ...
    </script>

 
This is to simplify the caller so it does not need to know about the namespace thing. Of course, if we wanted to make this component available from outside our package, we would need to tweak this.

Another interesting detail is the default value given to the attribute: CustomPicklistController.NoCondition. This references a class that we will include with the component’s controller:

public class NoCondition implements ICondition {
    public String getCondition() {
        return '';
    }
}

 
The reason why we need this, is that we cannot pass null or inexistent classes to a @RemoteAction, so if we don’t want to apply any filter, we need a default class that actually implements no filter.

With all of this settled, the @RemoteAction method now looks like this:

public with sharing class CustomPicklistController {

    @RemoteAction
    public static CustomPicklistData getData(String objectName, ICondition condition) {
        List<SObject> records = retrieveData(objectName, condition);
        CustomPicklistData data = convertData(records);
        return data;
    }

    private static List<SObject> retrieveData(
        String objectName,
        ICondition condition
    ) {
        String safeObjectName = getSafeSOQLString(objectName);
        String filter = condition.getCondition();
        String whereClause = filter.isEmpty() ? '' : ' WHERE ' + filter;
        String dbQuery = 'SELECT Id, Name FROM ' + safeObjectName + whereClause;
        return Database.query(dbQuery);
    }
}

 
So, the Visualforce page instantiates the component this way:

<CustomPicklistController objectName="Account" conditionClass="{!conditionClass}"/>

 
… and the condition class is returned from the VF page’s controller:

public String getConditionClass() {
    return 'WhateverPageController.SpecificCondition';
}

 
(… where WhateverPageController is the VF page’s controller, and SpecificCondition is the specific condition class we saw before, nested within the controller class).

 

Conclusions

With the proposed solution, we have avoided having to open a door to potential SOQL injection attacks by not allowing to pass any data, but only information to access the code that actually generates the data, and all of this while leveraging @RemoteAction’s speed and keeping a great flexibility for the component’s user.

Thanks to my teammates at FinancialForce Ana Cristina López, Abel Martos and Shaun Doyle for their great contribution while looking for a solution to the problem.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s