The evaluate method is called upon the occurrence of an event monitored by a transaction security policy. A typical implementation first selects the item of interest from the event. Then the item is tested to see if it meets the condition being monitored. If the condition is met, the method returns true.
For example, imagine a transaction security policy that checks for the same user logging in more than once. For each login event, the method would check if the user logging in already has a login session in progress, and if so, true is returned.
If you’re using the policy condition interface in the org where the policy was implemented, test classes for the policy are not required. If you move the policy to another org, you must have test classes for the Apex policy in the new org. Testing is required whether the policy is moved from a sandbox to production, with a change set, or some other way. Why? If you’re making a policy available outside of its development environment, it needs testing to make sure it works correctly.
To avoid errors, don't include DML statements in your custom policies. Also, if you send custom emails via Apex during transaction policy evaluation, you’ll get an error when the policy is evaluated, even if the record is not explicitly related to another record. For more information, see Apex DML Operations in the Apex Developer Guide.
The following is the method for PolicyCondition.
public Boolean evaluate(TxnSecurity.Event event)
Type: Boolean
When the policy is triggered, True is returned. For example, let’s suppose the policy is to limit users to a single login session. If anyone tries to log in a second time, the policy’s action requires that they end their current session. The policy also sends an email notification to the Salesforce admin. The evaluate() method only checks the login event, and returns True if it’s the user’s second login. The Transaction Security system performs the action and notification, and not the evaluate() method.
First, on the Transaction Security setup page, create a policy to block localhost
logins:
The following example is the generated Apex policy. Indenting and comments have been added to improve legibility.
global class BlockLocalhostLoginPolicyCondition implements TxnSecurity.PolicyCondition { public boolean evaluate(TxnSecurity.Event e) { // Get the LoginHistoryId to in turn select the SourceIp address. String loginHistoryId = e.data.get('LoginHistoryId'); // Retrieve SourceIp from LoginHistory. LoginHistory eObj = [SELECT SourceIp FROM LoginHistory WHERE id = :e.data.get('LoginHistoryId')]; // If the Source IP is localhost (127.0.0.1), trigger the policy and return true. if(eObj.SourceIp == '127.0.0.1') { return true; } return false; } }
An admin or other customer with API privileges can download all customer data in bulk using SOAP API, REST API, or the Bulk API. This security policy restricts API-based data downloads to 2,000 records and alerts the admin with a real-time notification if the policy is triggered.
global class DataLoaderExportPolicyCondition implements TxnSecurity.PolicyCondition { public boolean evaluate(TxnSecurity.Event e) { Boolean isApi = Boolean.valueOf(e.data.get('IsApi')) { // For any API request... Integer numberOfRecords = Integer.valueOf(e.data.get('NumberOfRecords')); if (isApi && numberOfRecords >= 2000) { return true; } return false; } } }
You can have sensitive, confidential data in your quarterly Salesforce reports. You also want to ensure that teams accessing those reports use two-factor authentication (2FA) for high assurance before viewing this data. The policy makes 2FA a requirement, but you can’t provide high-assurance sessions until your teams have a way to meet the 2FA requirements. As a prerequisite, first set up 2FA in your Salesforce environment.
This example highlights the capability of a policy to enforce 2FA for a specific report. The report defined here is any report with “Quarterly Report” in its name. Anyone accessing the report is required to have a high-assurance session using 2FA.
global class ConfidentialDataPolicyCondition implements TxnSecurity.PolicyCondition { public boolean evaluate(TxnSecurity.Event e) { if (e.resourceType == 'Dashboard') { // If the event is about Dashboards... Dashboard dashboard = [SELECT DeveloperName FROM Dashboard WHERE id = :e.entityId]; String name = String.valueOf(dashboard.DeveloperName); // Check if this is a quarterly report. if (name.containsIgnoreCase('Quarterly Report')) { return true; } } return false; } }
Here’s a policy example for restricting access. Many organizations have standard hardware and support specific versions of different browsers. You can use this standard to reduce the security risk for high impact individuals by acting when logins take place from unusual devices. For example, your CEO typically logs in from San Francisco using a Macbook or Salesforce mobile application on an iPhone to Salesforce. When a login occurs from elsewhere using a Chromebook, it’s highly suspicious. Since hackers do not necessarily know which platforms corporate executives use, this policy makes a security breach less likely.
In this example, the customer organization knows that their CEO is using a Macbook running OS X with the Safari browser. Any attempt to log in using the CEO’s credentials with anything else is automatically blocked.
global class CeoBrowserAccessPolicyCondition implements TxnSecurity.PolicyCondition { public boolean evaluate(TxnSecurity.Event e) { // If it's a Login attempt from our CEO's user account. if (e.action == 'Login' && e.userId == '005x0000005VmCu') { // Get the platform & browser from LoginHistory for this login attempt. LoginHistory loginAttempt = [SELECT Platform, Browser FROM LoginHistory WHERE Id = :e.data.get('LoginHistoryId')]; String platform = loginAttempt.Platform; String browser = loginAttempt.Browser; // The policy is triggered when the CEO isn’t using Safari on Mac OSX. if (!platform.equals('Mac OSX') || !browser.startsWith('Safari')) { return true; } } return false; } }
Your organization could have remote offices and a global presence but, due to international law, wants to restrict access to their Salesforce org. The restrictions would be from specific countries, or obtain alerts when unusual login activity occurs.
This example builds a policy that blocks users logging in from North Korea. If users are in North Korea but using a corporate VPN, their VPN gateway would be in Singapore or the United States. The VPN gateway would make their login successful because Salesforce would see the internal US-based company IP address.
global class BlockAccessFromNKPolicyCondition implements TxnSecurity.PolicyCondition { public boolean evaluate(TxnSecurity.Event e) { // Get the login history. LoginHistory loginAttempt = [SELECT LoginGeoId FROM LoginHistory WHERE Id = :e.data.get('LoginHistoryId')]; // Get the login's geographical info. String loginGeoId = String.valueOf(loginAttempt.LoginGeoId); LoginGeo loginGeo = [SELECT Country FROM LoginGeo WHERE Id = :loginGeoId]; // Get the country at that location. String country = String.valueOf(loginGeo.Country); // Trigger policy and block access for any user trying to log in from North Korea. if(country.equals('North Korea')) { return true; } return false; } }
You can also restrict access to other specific values, like postal code or city.
You’re concerned with a specific mobile platform’s vulnerabilities and its ability to capture screen shots and read data while accessing Salesforce. If the device is not running a security client, you could restrict access from device platforms using operating systems with known and well-identified vulnerabilities. In this example, we create a policy to block devices using Android 5.0 or earlier.
global class BlockOldAndroidDevicesPolicyCondition implements TxnSecurity.PolicyCondition { public boolean evaluate(TxnSecurity.Event e) { LoginHistory loginAttempt = [SELECT Platform FROM LoginHistory WHERE Id = :e.data.get('LoginHistoryId')]; if (loginAttempt != null) { String platform = loginHistory.Platform; if (platform.contains('Android') && platform.compareTo('Android 5') < 0) { return true; } } return false; // Allow access from Android versions greater than 5. } }
You can scan or filter for specific words in posts. This example looks for a post containing the specific word “Salesforce” and blocks those posts. You can also write conditions that loop through a list of words or keep a running total of the occurrence of the words.
global class ChatterMessageWordFilterPolicyCondition implements TxnSecurity.PolicyCondition { public boolean evaluate(TxnSecurity.Event event) { String body = event.data.get('Body'); if(body.containsIgnoreCase('Salesforce')) { return true; } return false; } }
If you’re comparing the contents of the entire post, don’t use the equals string method—use contains, or containsIgnoreCase as shown here. If the Chatter settings are on to show emoticons or to allow rich text, those items are included in the Chatter post’s body. For example, with rich text, the post “This is text.” could be stored as “<p>This is text.</p>”. If you use the equals method, the embedded tags prevent your otherwise identical comparison text from matching the post body.
Advertisers and spammers often post messages to successful communities at high rates to increase their chances of people clicking their links. The links can include unwanted content. You can use technologies outside of Salesforce to scan or filter content based on these different services.
In this example, we have unwanted text in communities posts and the policy executes an API callout to see if the content is compliant. This example uses a service that blocks commonly-accepted English profanity as specified at www.purgomalum.com/profanitylist.
global class ChatterMessageProfanityFilterPolicyCondition implements TxnSecurity.PolicyCondition { public boolean evaluate(TxnSecurity.Event e) { String body = e.data.get('Body'); //Create HTTPRequest and specify its type and properties. HttpRequest request = new HttpRequest(); request.setMethod('GET'); request.setHeader('content-type', 'text/plain'); request.setHeader('Connection', 'keep-alive'); request.setEndpoint('http://www.purgomalum.com/service/containsprofanity?text=' + EncodingUtil.urlEncode(body,'UTF-8')); Http http = new Http(); HTTPResponse response = http.send(request); if (response.getStatusCode() == 200 && response.getBody().equals('true')) { return true; // Callout succeeded and found profanity in the message. } return false; // Callout failed or no profanity was found. } }
If an API callout’s elapsed execution time exceeds 3 seconds, the user is denied access to the resource or entity. For more information, see Transaction Security Metering.
Sometimes connected apps have API privileges to access data org-wide due to sharing or account access settings definitions. However, the end user of the connected app is restricted to only a specific data set. This conflict can result in an increased security risk by identifying the API key and performing command-line searches directly in the database to look for leads. The following policy avoids this situation and data loss around your company’s lead information.
global class DataLoaderLeadExportPolicyCondition implements TxnSecurity.PolicyCondition { public boolean evaluate(TxnSecurity.Event e) { if (Boolean.valueOf(e.data.get('IsApi'))) { // The event data is a Map<String, String>. We need to call the // valueOf() method on appropriate data types to use them here. String resourceType = e.data.get('resourceType'); String connectedAppId = e.data.get('ConnectedAppId'); Integer numberOfRecords = Integer.valueOf(e.data.get('NumberOfRecords')); Integer executionTimeMillis = Integer.valueOf(e.data.get('ExecutionTime')); // We're looking for leads accessed by a specific connected app that is // transferring more than 2,000 records a second - a large transfer. if ('Lead'.equals(resourceType) && '0CiD00000004Cce'.equals(connectedAppId) && numberOfRecords > 2000 && executionTimeMillis > 1000) { return true; } } return false; } }