Newer Version Available
PolicyCondition Interface
Namespace
Usage
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.
We recommend having test classes for the policy condition interface to ensure it works correctly. Testing is required regardless of whether the policy is moved from a sandbox to production, with a change set, or some other way. For example, test your policies in your development environment before moving the policies to production.
Don’t include DML statements in your custom policies because they can cause errors. When you send a custom email via Apex during transaction policy evaluation, you get an error, even if the record is not explicitly related to another record. For more information, see Apex DML Operations in the Apex Developer Guide.
PolicyCondition Methods
The following is the method for PolicyCondition.
evaluate(event)
Signature
public Boolean evaluate(TxnSecurity.Event event)
Parameters
- event
- Type: TxnSecurity.Event
- The event to check against the transaction security policy.
Return Value
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.
Apex Policies for Transaction Security
| Available in: Salesforce Classic and Lightning Experience |
| Available in: Enterprise, Performance,
Unlimited, and Developer Editions Requires purchasing Salesforce Shield or Salesforce Event Monitoring add-on subscriptions. |
If you didn’t specify a condition value before you generated the Apex interface for a policy, you can add the condition later. To change the condition, you can edit the Apex code to include a condition before you activate your policy. If you don’t include a condition, your policy isn’t triggered.
Don’t include DML statements in your custom policies because they can cause errors. When you send a custom email via Apex during transaction policy evaluation, you get an error, even if the record is not explicitly related to another record. For more information, see Apex DML Operations in the Apex Developer Guide.
When you delete a transaction security policy, your TxnSecurity.PolicyCondition implementation isn’t deleted. You can reuse your Apex code in other policies.
If you use an API callout in the Apex class that implements TxnSecurity.PolicyCondition, you must select an action when you create the Transaction Security policy in Setup. If you select None as the action, the policy can’t execute. For more information, see Invoking Callouts Using Apex in the Apex Developer Guide.
Apex Transaction Security Implementation Examples
| Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience |
| Available in: Enterprise, Performance,
Unlimited, and Developer Editions Requires purchasing Salesforce Shield or Salesforce Event Monitoring add-on subscriptions. |
Example
1global class LoginPolicyCondition implements TxnSecurity.PolicyCondition {
2 public boolean evaluate(TxnSecurity.Event e) {
3 AggregateResult[] results = [SELECT SourceIp
4 FROM LoginHistory
5 WHERE UserId = :e.userId
6 AND LoginTime = LAST_N_DAYS:1
7 GROUP BY SourceIp];
8 if(!results.isEmpty() && results.size() > 1) {
9 return true;
10 }
11 return false;
12 }
13}Example
1global class SessionPolicyCondition implements TxnSecurity.PolicyCondition {
2 public boolean evaluate(TxnSecurity.Event e) {
3
4 LoginHistory eObj = [SELECT SourceIp FROM LoginHistory WHERE Id = :e.data.get('LoginHistoryId')];
5 if (eObj.SourceIp == '1.1.1.1') {
6 return true;
7 }
8 return false;
9 }
10}Example
1global class LeadExportPolicyCondition implements TxnSecurity.PolicyCondition {
2 public boolean evaluate(TxnSecurity.Event e) {
3
4 Integer numberOfRecords = Integer.valueOf(e.data.get('NumberOfRecords'));
5 String entityName = e.data.get('EntityName');
6
7 if ('Lead'.equals(entityName) && numberOfRecords > 1000) {
8 return true;
9 }
10
11 return false;
12 }
13}Example
1global class ReportsPolicyCondition implements TxnSecurity.PolicyCondition {
2 public boolean evaluate(TxnSecurity.Event e) {
3 if(e.data.get('SessionLevel') == 'STANDARD' ){
4 return true;
5 }
6 return false;
7 }
8}Example
From the Transaction Security Policies page, create a policy to block localhost logins. Here is the generated Apex policy:
1global class BlockLocalhostLoginPolicyCondition implements TxnSecurity.PolicyCondition {
2 public boolean evaluate(TxnSecurity.Event e) {
3 // Get the LoginHistoryId to in turn select the SourceIp address.
4 String loginHistoryId = e.data.get('LoginHistoryId');
5 // Retrieve SourceIp from LoginHistory.
6 LoginHistory eObj =
7 [SELECT SourceIp FROM LoginHistory WHERE id = :e.data.get('LoginHistoryId')];
8 // If the Source IP is localhost (127.0.0.1), trigger the policy and return true.
9 if(eObj.SourceIp == '127.0.0.1') {
10 return true;
11 }
12 return false;
13 }
14}Example
An admin or other customer with API privileges can download all customer data in bulk using SOAP API, REST API, or 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.
1global class DataLoaderExportPolicyCondition implements TxnSecurity.PolicyCondition {
2 public boolean evaluate(TxnSecurity.Event e) {
3 Boolean isApi = Boolean.valueOf(e.data.get('IsApi')) { // For any API request...
4 Integer numberOfRecords = Integer.valueOf(e.data.get('NumberOfRecords'));
5 if (isApi && numberOfRecords >= 2000) {
6 return true;
7 }
8 return false;
9 }
10 }
11}Example
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.
1global class ConfidentialDataPolicyCondition implements TxnSecurity.PolicyCondition {
2 public boolean evaluate(TxnSecurity.Event e) {
3 if (e.resourceType == 'Dashboard') { // If the event is about Dashboards...
4 Dashboard dashboard =
5 [SELECT DeveloperName FROM Dashboard WHERE id = :e.entityId];
6 String name = String.valueOf(dashboard.DeveloperName);
7 // Check if this is a quarterly report.
8 if (name.containsIgnoreCase('Quarterly Report')) {
9 return true;
10 }
11 }
12 return false;
13 }
14}Example
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. Because 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.
1global class CeoBrowserAccessPolicyCondition implements TxnSecurity.PolicyCondition {
2 public boolean evaluate(TxnSecurity.Event e) {
3 // If it's a Login attempt from our CEO's user account.
4 if (e.action == 'Login' && e.userId == '005x0000005VmCu') {
5 // Get the platform & browser from LoginHistory for this login attempt.
6 LoginHistory loginAttempt =
7 [SELECT Platform, Browser FROM LoginHistory
8 WHERE Id = :e.data.get('LoginHistoryId')];
9 String platform = loginAttempt.Platform;
10 String browser = loginAttempt.Browser;
11 // The policy is triggered when the CEO isn’t using Safari on Mac OSX.
12 if (!platform.equals('Mac OSX') || !browser.startsWith('Safari')) {
13 return true;
14 }
15 }
16 return false;
17 }
18}Example
Your organization could have remote offices and a global presence but, due to international law, wants to restrict access to its Salesforce org.
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 U.S.-based company IP address.
1global class BlockAccessFromNKPolicyCondition implements TxnSecurity.PolicyCondition {
2 public boolean evaluate(TxnSecurity.Event e) {
3 // Get the login history.
4 LoginHistory loginAttempt =
5 [SELECT LoginGeoId FROM LoginHistory WHERE Id = :e.data.get('LoginHistoryId')];
6 // Get the login's geographical info.
7 String loginGeoId = String.valueOf(loginAttempt.LoginGeoId);
8 LoginGeo loginGeo = [SELECT Country FROM LoginGeo WHERE Id = :loginGeoId];
9 // Get the country at that location.
10 String country = String.valueOf(loginGeo.Country);
11 // Trigger policy and block access for any user trying to log in from North Korea.
12 if(country.equals('North Korea')) {
13 return true;
14 }
15 return false;
16 }
17}You can also restrict access to other values, like postal code or city.
Example
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. This policy blocks devices using Android 5.0 or earlier.
1global class BlockOldAndroidDevicesPolicyCondition implements TxnSecurity.PolicyCondition {
2 public boolean evaluate(TxnSecurity.Event e) {
3 LoginHistory loginAttempt =
4 [SELECT Platform FROM LoginHistory WHERE Id = :e.data.get('LoginHistoryId')];
5 if (loginAttempt != null) {
6 String platform = loginHistory.Platform;
7 if (platform.contains('Android') && platform.compareTo('Android 5') < 0) {
8 return true;
9 }
10 }
11 return false; // Allow access from Android versions greater than 5.
12 }
13}Example
You can scan or filter for specific words in posts. This example looks for a post containing the 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.
1global class ChatterMessageWordFilterPolicyCondition implements TxnSecurity.PolicyCondition {
2 public boolean evaluate(TxnSecurity.Event event) {
3 String body = event.data.get('Body');
4
5 if(body.containsIgnoreCase('Salesforce')) {
6 return true;
7 }
8 return false;
9 }
10}Example
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 dataset. 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.
1global class DataLoaderLeadExportPolicyCondition implements TxnSecurity.PolicyCondition {
2 public boolean evaluate(TxnSecurity.Event e) {
3 if (Boolean.valueOf(e.data.get('IsApi'))) {
4
5 // The event data is a Map<String, String>. We need to call the
6 // valueOf() method on appropriate data types to use them here.
7 String resourceType = e.data.get('resourceType');
8 String connectedAppId = e.data.get('ConnectedAppId');
9 Integer numberOfRecords = Integer.valueOf(e.data.get('NumberOfRecords'));
10 Integer executionTimeMillis = Integer.valueOf(e.data.get('ExecutionTime'));
11
12 // We're looking for leads accessed by a specific connected app that is
13 // transferring more than 2,000 records a second - a large transfer.
14 if ('Lead'.equals(resourceType) &&
15 '0CiD00000004Cce'.equals(connectedAppId) &&
16 numberOfRecords > 2000 &&
17 executionTimeMillis > 1000) {
18 return true;
19 }
20 }
21 return false;
22 }
23}