Start tracking your progress
Trailhead Home
Trailhead Home

Explore Custom Transaction Security Policies

Learning Objectives

Warning

Warning

Be careful—Transaction Security is a powerful feature. An incorrect Login Event policy that uses Block as its real-time action locks you out of your org. To prevent this from happening in an org you care about, create a new Trailhead Playground for this module. Yes, we really mean a brand new Trailhead Playground.

After completing this unit, you’ll be able to:
  • Modify the generated Apex to implement your specific needs.
  • Write tests for your Apex classes.

Swimming in the Sea of Apex

At this point, you’ve delved into the depths of Transaction Security and seen how Apex makes it work. Now it’s time to pump up that Apex so that it does exactly what you want it to do. We’ll dive into the code and make changes to fine-tune a policy condition.

Customize the Apex for the Policy

You can customize policies before or after you enable them. In both scenarios, the process is the same. Let’s look at a common scenario: You want to update a policy and you don’t want it to get in the way of your company’s work.

Note

Note

One note about creating new Apex transaction security policies: You can’t create new Apex policies in production orgs. You can create them in trial or sandbox orgs, however.

In this example, we’re looking at policy called Lead Data Export Policy. Let’s take a look at it in your org.
  1. From Setup, in the Quick Find box, enter Transaction Security, and select Transaction Security Policies.
  2. To see the Apex class for the LeadDataExportPolicy, in the Apex Condition column, click DataLoaderLeadExportCondition.

The DataLoaderLeadExportCondition blocks users from downloading more than 2,000 leads at a time. You probably recognize this class from the previous unit about Apex. If you like, you can go back to it for a refresher. We’re going to modify our new policy’s code and add some error checking to ensure that we’re looking at the data export event.

A screenshot of the DataLoaderLeadExportCondition transaction security policy. The comments in the code say, "Trigger the policy only for an export on Leads, where we are downloading more than 2000 records."

Let’s assume your sales managers are still concerned about users downloading too many leads. Your employees need to generate large reports, but your sales managers want to block users if they try to download more than 500, instead of 2,000.

Begin by editing the Apex policy condition for the Lead Data Export Policy.
  1. From the DataLoaderLeadExportCondition Apex class detail page, click Edit.
  2. In the code, replace if(numberOfRecords > 2000 || executionTimeMillis > 1000) with if(numberOfRecords > 500 || executionTimeMillis > 1000). Don’t forget to update your code comments to reflect the change, too.
  3. Click Save.
Your DataLoaderLeadExportCondition should look like this.
global class DataLoaderLeadExportCondition implements TxnSecurity.PolicyCondition {
    public boolean evaluate(TxnSecurity.Event e) {
        // The event data is a Map<String, String>.
        // We need to call the valueOf() method on appropriate data types to use them in our logic.
        Integer numberOfRecords = Integer.valueOf(e.data.get('NumberOfRecords'));
        Long executionTimeMillis = Long.valueOf(e.data.get('ExecutionTime'));
        String entityName = e.data.get('EntityName');

        // Trigger the policy when a user tries to download more than 
        // 500 lead records or the download time exceeds 1 second (1000ms).
        if('Lead'.equals(entityName)){
            if(numberOfRecords > 500 || executionTimeMillis > 1000){
                return true;
            }
        }
        // For everything else, don't trigger the policy.
        return false;
    }
}

Great! Your LeadDataExportPolicy now blocks users from downloading reports that contain more than 500 leads.

Test the Apex for the Policy

You’ve learned about Apex programs and customized a class. The last step to becoming an ace Apex programmer is to test your code. When you start writing your own code, you should also write tests to make sure that your code works as you expect. You do that with test classes.

Begin by editing the Lead Data Export Policy’s code that you customized. You don’t change the original DataLoaderLeadExportCondition class. Instead, you add test code to a new test class in Apex. Test classes aren’t specific to Transaction Security. All good Apex programmers use them to make sure that their code does what they expect it to, and to find errors before their users and customers find them. We’re going to jump right to the end and show you what test code looks like, then go through it so you understand what it does.

Here’s the DataLoaderLeadExportCondition that you’ve already created.
global class DataLoaderLeadExportCondition implements TxnSecurity.PolicyCondition {
    public boolean evaluate(TxnSecurity.Event e) {
        // The event data is a Map<String, String>.
        // We need to call the valueOf() method on appropriate data types to use them in our logic.
        Integer numberOfRecords = Integer.valueOf(e.data.get('NumberOfRecords'));
        Long executionTimeMillis = Long.valueOf(e.data.get('ExecutionTime'));
        String entityName = e.data.get('EntityName');

        // Trigger the policy when a user tries to download more than 
        // 500 lead records or the download time exceeds 1 second (1000ms).
        if('Lead'.equals(entityName)){
            if(numberOfRecords > 500 || executionTimeMillis > 1000){
                return true;
            }
        }
        // For everything else, don't trigger the policy.
        return false;
    }
}
Let’s take a look at the test class code.
/**
 * Test for default DataLoaderLeadExportCondition provided by Salesforce
 * 
*/
@isTest
public class DataLoaderLeadExportConditionTest {

    /*
     * Test to check if policy gets triggered when user exports 750 Leads
    */
  public static testMethod void testLeadExportTriggered() {
    Map<String, String> eventMap = getEventMap(String.valueOf(750));
    Organization org = getOrganization();
    User user = createUser();
      
    TxnSecurity.Event e = createTransactionSecurityEvent(org, user, eventMap) ;

    /* We are unit testing a PolicyCondition that triggers
       when an event is generated due to high NumberOfRecords */
    DataLoaderLeadExportCondition dataLoaderLeadExportCondition = new DataLoaderLeadExportCondition();
    /* Assert that the condition is triggered */
    System.assertEquals(true, dataLoaderLeadExportCondition.evaluate(e));
   }
    /*
     * Test to check if policy doesn't get triggered when user exports 200 Leads
    */
  public static testMethod void testLeadExportNotTriggered() {
    Map<String, String> eventMap = getEventMap(String.valueOf(200));
    Organization org = getOrganization();
    User user = createUser();
       
    TxnSecurity.Event e = createTransactionSecurityEvent(org, user, eventMap) ;
    /* We are unit testing a PolicyCondition that does not trigger
       an event due to low NumberOfRecords  */
    DataLoaderLeadExportCondition dataLoaderLeadExportCondition = new DataLoaderLeadExportCondition();
      
    /* Assert that the condition is NOT triggered */
    System.assertEquals(false, dataLoaderLeadExportCondition.evaluate(e));
  }
    /*
     * Create an example user
     */
    private static User createUser(){
        Profile p = [select id from profile where name='System Administrator'];
        String ourSysAdminString = p.Id; 
        
        User u = new User(alias = 'test', email='user@salesforce.com',
            emailencodingkey='UTF-8', lastname='TestLastname', languagelocalekey='en_US',
            localesidkey='en_US', profileid = p.Id, country='United States',
            timezonesidkey='America/Los_Angeles', username=generateRandomUsername(7));
        insert u;
        return u;
    }

    /**
     * Generates a random username of the form “[random]@[random].com”, where the 
     * length of the random string is the given Integer argument. For example, with 
     * an argument of 3, the following random username may be produced: 
     * “ajZ@ajZ.com”.
     */ 
    private static String generateRandomUsername(Integer len) {
        final String chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
        String randStr = '';
        while (randStr.length() < len) {
           Integer idx = Math.mod(Math.abs(Crypto.getRandomInteger()), chars.length());
           randStr += chars.substring(idx, idx+1);
        }
        return randStr + '@' + randStr + '.com'; 
    }


    /*
     * Returns current organization
     */
    private static Organization getOrganization(){
        return [SELECT id, Name FROM Organization];
    }
      
    /*
     * Adds NumberOfRecords and example ExecutionTime
     */ 
    private static Map<String, String> getEventMap(String numberOfRecords){
        Map<String, String> eventMap = new Map<String, String>();
        eventMap.put('NumberOfRecords', numberOfRecords);
        eventMap.put('ExecutionTime', '30');
        eventMap.put('EntityName', 'Lead');
        return eventMap;
    }
    
    /*
     * Create a TxnSecurity.Event instance
     */ 
    private static TxnSecurity.Event createTransactionSecurityEvent(Organization org, User user, Map<String, String> eventMap){
        TxnSecurity.Event e = new TxnSecurity.Event(
             org.Id /* organizationId */,
             user.Id /* userId */,
             'Lead' /* entityName */ ,
             'DataExport' /* action */,
             'Lead' /* resourceType */,
             '000000000000000' /* entityId */,
              Datetime.newInstance(2016, 9, 22) /* timeStamp */,
              eventMap /* data - Map containing information about the event */ );
        return e;
    }
}
Let’s break it down. First, you’ll notice that we start this class with the @isTest keyword, which lets the compiler know that this is test code.
Before you can run tests, you need to create some objects that contain information our tests need to run.
  • An example user
  • Information about your org
  • An Apex map that contains a number of records, sample execution times, and the entity name Lead
Let’s take a look at the first test, testLeadExportTriggered.
public static testMethod void testLeadExportTriggered() {

    /* Create a map for the event we’re going to build. */
    Map<String, String> eventMap = getEventMap(String.valueOf(750));
    Organization org = getOrganization();
    User user = createUser();
      
    TxnSecurity.Event e = createTransactionSecurityEvent(org, user, eventMap) ;

    /* We are unit testing a PolicyCondition that triggers
       when an event is generated due to high NumberOfRecords */
    DataLoaderLeadExportCondition dataLoaderLeadExportCondition = new DataLoaderLeadExportCondition();

    /* Assert that the condition is triggered */
    System.assertEquals(true, dataLoaderLeadExportCondition.evaluate(e));
   }

First, you create a map that contains 750 sample leads. You also get information about our current org and create a sample user. (You can’t export a report without org and user information.) You pass all of that information to an event, e. Next, you call your DataLoaderLeadExportCondition method. If you recall from the last section, DataLoaderLeadExportCondition blocks any user from exporting more than 500 leads at a time. Finally, if your condition is triggered, your code returns a value of true. In this case, because you’re attempting to export 750 leads at a time, you should receive a value of true.

Of course you want to test to make sure our condition is triggered if someone tries to export too many leads. But you also want to test your code to make sure it doesn’t trigger when a user tries to export 500 or fewer leads. After all, you don’t want to block users who aren’t doing anything wrong. Your next test, testLeadExportNotTriggered, covers that.
public static testMethod void testLeadExportNotTriggered() {
    Map<String, String> eventMap = getEventMap(String.valueOf(200));
    Organization org = getOrganization();
    User user = createUser();
       
    TxnSecurity.Event e = createTransactionSecurityEvent(org, user, eventMap) ;
    /* We are unit testing a PolicyCondition that does not trigger
       an event due to low NumberOfRecords  */
    DataLoaderLeadExportCondition dataLoaderLeadExportCondition = new DataLoaderLeadExportCondition();
      
    /* Assert that the condition is NOT triggered */
    System.assertEquals(false, dataLoaderLeadExportCondition.evaluate(e));
  }

Here, you create a map that contains information about 200 sample leads. You gather the same information about your current org, and you create another sample user. You call the DataLoaderLeadExportCondition method. The code then returns false if the DataLoaderLeadExportCondition isn’t triggered. In this case, your code should return false because you’re attempting to export a report with only 200 leads.

Create and run the test class to see if your code is working the way you expect.

  1. From Setup, in the Quick Find box, type Apex, and click Apex Classes.
  2. To create a new test class, click New.
  3. Copy and paste the test code into your class.
  4. Click Save | Run Test.

    A screenshot that shows the Run Test button on the Apex class page

Success! Your tests pass, confirming that your code is working properly and ready to rock.

retargeting