Getting Started with Enhanced Transaction Security Policies

What if I told you that you could prevent your users from adding fields containing sensitive data to their reports as they build and edit in report builder? Or that you could require two factor authentication when a user logs in from an outdated browser version? Well, you’re in luck, transaction security policies are one of the coolest features in Salesforce you might not have known existed! They give you the ability to block the transaction or alert an admin when a user does something that might be suspicious. By bringing together transaction security policies and Apex conditions, we can take our org’s security to the next level.

Transaction Security is POWERFUL
Be careful—Transaction Security is a powerful feature. An incorrect Login Event policy that uses Block as its real-time action can lock you out of your org.

Before you dig into transaction security policies, make sure you check out the first blog in this series on Shield Event Monitoring, an Introduction to Real-Time Event Monitoring. Real-time events are a recent addition to Event Monitoring and enable you to implement a number of new security features as part of Salesforce Shield.

What makes a transaction security policy?

A transaction security policy consists of an event, condition, and action that has been defined.

  1. Each transaction security policy starts with a Real Time Event. This event is fired each time that a user has an interaction with Salesforce that is covered under the available events. For example, when a user logs in and triggers LoginEvent through real-time events.
  2. The event is then evaluated by a condition. The condition allows you to evaluate the body of the event for security risks and vulnerability. Continuing the example with the LoginEvent, we can access the login characteristics like the user, their location, and their session details.
  1. If the condition evaluates to be true, you can kick off an action. Each action can either block, require two-factor authentication, allow the transaction, or take a custom action with synchronous or asynchronous Apex. On top of that, you can send an in-app notification or email to an admin user. Using our LoginEvent example, if a user logs in with an outdated browser, we can trigger an email the Salesforce Admin that they need to update their browser.

Which events are available to track?

As of Summer ‘20, there are four events that initiate a transaction security policy.

API Event – Evaluated each time a query is made via the REST, SOAP or Bulk APIs.

List View Event – Evaluated each time a user access or exports a list view.

Login Event – Evaluated each time a user logs in.

Report Event – Evaluated each time a user interacts with a report or dashboard.

If you would like to learn more, check out the enhanced transaction security policies Trailhead badge. But for now, we are going to examine some more complex use cases using Apex.

Getting started with Apex conditions

As mentioned earlier, the condition evaluation phase of the policy can be expressed in an Apex class. You would use an Apex condition when the logic needed in the policy exceeds the capability of the condition builder — for example if you would like to query and filter data related to the policy. The condition implements TxnSecurity.EventCondition interface which accepts and input from the policy.

When evaluated, the events relevant SObject is passed into the evaluate method where you can then perform logic that determines the outcome of the policy. The response must always be a boolean — a true response will trigger an action while a false response will let the user proceed.

global class apexEventCondition implements TxnSecurity.EventCondition {
  public boolean evaluate(SObject event) {
    // A false response will allow the transaction to proceed  
    return false;
  }
}

Evaluating event conditions

Let’s take a look at a scenario that would require the use of an Apex condition. A company adds users who are currently under performance review into a public group called ‘watch list’. The company would like to be discretely alerted every time one of these users downloads a report or list view containing opportunity data. In order to achieve this, we will need two events, the ReportEvent and the ListViewEvent.

global class BlockWatchListAccountExports implements TxnSecurity.EventCondition {
    public boolean evaluate(SObject event) {
        switch on event {
            when ReportEvent reportEvent {
                // We will pass in the user's Id and a string of 
                // queried entities(objects) to the evaluate method
                return evaluate(
                    reportEvent.UserId,
                    reportEvent.QueriedEntities,
                    reportEvent.Operation
                );
            }
            when else {
                return false;
            }
        }
    }

    private boolean evaluate(
        Id userId,
        String queriedEntities,
        String operation ) {
        // Check if the user belongs to the Watch List public group
        List<GroupMember> groupMember = [
            SELECT Id
            FROM GroupMember
            WHERE
                GroupId IN (SELECT Id FROM Group WHERE Name = 'Watch List')
                AND UserOrGroupId = :userId
            LIMIT 1
        ];
        if (
            groupMember.size() == 1 &&
            queriedEntities.contains('Opportunity') &&
            operation == 'ReportExported'
        ) {
            return true;
        }
        return false;
    }
}

In this example, you can see that we are taking our evaluate method and extending it by passing in the User Id of the user that viewed the report, the objects that were queried, and the action that was being taken on the report. We can then query to see if the user that initiated the transaction is part of the ‘watch list’ public group. The last part of our evaluation checks if the user was a member of the public group, the report contains the opportunity object, and the action taken was exporting. If this condition evaluates to be true, we will then trigger an action on the transaction security policy.

Using Apex conditions in the policy builder

Once your Apex condition has been saved, you can access it within the New Policy wizard. Here you’re given the opportunity to select from a list of Apex classes that implement the TxnSecurity.EventCondition interface. If the condition returns true, we then have the opportunity to take action.

Initially we can block the transaction and not allow the user to download the reports, we can require two-factor authentication and let the user continue, or we can allow them to continue the transaction. On top of that functionally, we can also send an email or in-app notification to a designated system administrator. In our example use case, we will send both notifications to the admin.

Screenshot 2020-04-27 at 08.55.32.png

Testing transaction security policies

The real-time events that are used to initiate transaction security policies are stored as SObjects, making the approach to unit testing very similar to the test classes you may have previously written. As you can see in this example, we can setup our ReportEvent object with the default values needed to evaluate our Apex condition. It’s very important that you run your conditions through a number of positive and negative tests as an unhandled transaction security policy can cause major issues with the way your users interact with Salesforce. It’s also important to note that you must run the test as the user that was created during the @testSetup otherwise they will not be included in the public group that is in the test criteria.

 @isTest
public with sharing class BlockWatchlistOpportunityExports_Test {

    @testSetup
    public void createTestData(){...}

    @isTest
    static void testApiEventPositiveTestCase() {

        // get the user created from the @testSetup
        List<User> users = [SELECT Id FROM User LIMIT 1];
        User user = (users.size() == 1) ? users.get(0) : null;

        // set up our event and its field values
        ReportEvent testReportEvent = new ReportEvent();
        testReportEvent.UserId = user.Id;
        testReportEvent.QueriedEntities = 'Opportunity';
        testReportEvent.Operation = 'Operation';

        // create a boolean to store our result
        Boolean result;
        
        Test.startTest();
        // The test must be ran as the user that is assigned to the correct group.
        // When the transaction security policy is fired, the event will be in the
        // context of the running user. 
        System.runAs(user.Id) {
            // test that the Apex returns true for this event
            BlockWatchListAccountExports eventCondition = new BlockWatchListAccountExports();
            result = eventCondition.evaluate(testReportEvent);

        }
        Test.stopTest();
        // check that the user policy evaluated to true
        System.assert(result, 'User was not part of public group');
    }
}

Asynchronous conditions

If synchronous condition processing wasn’t enough, we can take this a step further by diving into the TxnSecurity.AsyncCondition event condition interface. This makes it possible to perform DML and Apex callouts within your condition. A great example of this would be calling out to an external data source to verify that a user has the ability to view a record based on their security classification. We will take a deep dive into this topic in the next blog of this series.

Conclusion

Transaction security policies can really help you enhance the security standards of your Salesforce environments. They can help you act on potential security vulnerabilities and with the help of Apex, even complex scenarios can be tackled. Stay tuned as we look into using Async SOQL to manage and search through real-time event data! If you would like to learn more about transaction security & real-time events, check out the listed resources and Trailhead modules below.

Trailhead
Enhanced Transaction Security
Event Monitoring

Documentation
Enhanced Transaction Security Policies
Enhanced Transaction Security Examples

About the Author

Stephan Chandler-Garcia is a Senior Developer Evangelist at Salesforce. He focuses on Application development, Security and Communities. You can follow him on Twitter @stephanwcg