replace SECURITY_ENFORCED with USER_MODE across SOQL/Apex skill examples

This commit is contained in:
anatolii 2026-08-01 16:23:35 +03:00
parent fe3e2edad0
commit d4bfd686c2
No known key found for this signature in database
11 changed files with 52 additions and 52 deletions

View File

@ -37,7 +37,7 @@ public with sharing class LwcController {
SELECT Id, Name, Industry, AnnualRevenue, Phone, CreatedDate SELECT Id, Name, Industry, AnnualRevenue, Phone, CreatedDate
FROM Account FROM Account
WHERE Name LIKE :searchKey WHERE Name LIKE :searchKey
WITH SECURITY_ENFORCED WITH USER_MODE
ORDER BY Name ORDER BY Name
LIMIT :recordLimit LIMIT :recordLimit
]; ];
@ -57,7 +57,7 @@ public with sharing class LwcController {
(SELECT Id, FirstName, LastName, Email FROM Contacts LIMIT 5) (SELECT Id, FirstName, LastName, Email FROM Contacts LIMIT 5)
FROM Account FROM Account
WHERE Id = :accountId WHERE Id = :accountId
WITH SECURITY_ENFORCED WITH USER_MODE
LIMIT 1 LIMIT 1
]; ];
@ -146,7 +146,7 @@ public with sharing class LwcController {
List<Account> existing = [ List<Account> existing = [
SELECT Id FROM Account SELECT Id FROM Account
WHERE Id = :accountId WHERE Id = :accountId
WITH SECURITY_ENFORCED WITH USER_MODE
LIMIT 1 LIMIT 1
]; ];
@ -201,7 +201,7 @@ public with sharing class LwcController {
List<Account> accountsToDelete = [ List<Account> accountsToDelete = [
SELECT Id FROM Account SELECT Id FROM Account
WHERE Id IN :recordIds WHERE Id IN :recordIds
WITH SECURITY_ENFORCED WITH USER_MODE
]; ];
if (accountsToDelete.size() != recordIds.size()) { if (accountsToDelete.size() != recordIds.size()) {
@ -280,14 +280,14 @@ public with sharing class LwcController {
// Build dynamic query // Build dynamic query
String query = 'SELECT Id, Name, Industry, AnnualRevenue, Phone, CreatedDate ' + String query = 'SELECT Id, Name, Industry, AnnualRevenue, Phone, CreatedDate ' +
'FROM Account ' + 'FROM Account ' +
'WITH SECURITY_ENFORCED ' + 'WITH USER_MODE ' +
'ORDER BY ' + sortField + ' ' + sortDir + ' NULLS LAST ' + 'ORDER BY ' + sortField + ' ' + sortDir + ' NULLS LAST ' +
'LIMIT :recordLimit OFFSET :recordOffset'; 'LIMIT :recordLimit OFFSET :recordOffset';
List<Account> records = Database.query(query); List<Account> records = Database.query(query);
// Get total count // Get total count
Integer totalCount = [SELECT COUNT() FROM Account WITH SECURITY_ENFORCED]; Integer totalCount = [SELECT COUNT() FROM Account WITH USER_MODE];
return new PagedResult(records, totalCount); return new PagedResult(records, totalCount);
} catch (Exception e) { } catch (Exception e) {

View File

@ -138,7 +138,7 @@ public static List<Account> getAccounts(String searchTerm) {
SELECT Id, Name, Industry SELECT Id, Name, Industry
FROM Account FROM Account
WHERE Name LIKE :searchKey WHERE Name LIKE :searchKey
WITH SECURITY_ENFORCED WITH USER_MODE
LIMIT 50 LIMIT 50
]; ];
} }
@ -1368,7 +1368,7 @@ public with sharing class LwcController {
SELECT Id, Name, Industry, AnnualRevenue SELECT Id, Name, Industry, AnnualRevenue
FROM Account FROM Account
WHERE Name LIKE :searchKey WHERE Name LIKE :searchKey
WITH SECURITY_ENFORCED WITH USER_MODE
ORDER BY Name ORDER BY Name
LIMIT 50 LIMIT 50
]; ];
@ -1425,7 +1425,7 @@ public static void deleteAccounts(List<Id> accountIds) {
List<Account> toDelete = [ List<Account> toDelete = [
SELECT Id FROM Account SELECT Id FROM Account
WHERE Id IN :accountIds WHERE Id IN :accountIds
WITH SECURITY_ENFORCED WITH USER_MODE
]; ];
delete toDelete; delete toDelete;
@ -1446,7 +1446,7 @@ public static List<Contact> getContactsWithErrorHandling(Id accountId) {
SELECT Id, Name, Email, Phone SELECT Id, Name, Email, Phone
FROM Contact FROM Contact
WHERE AccountId = :accountId WHERE AccountId = :accountId
WITH SECURITY_ENFORCED WITH USER_MODE
ORDER BY Name ORDER BY Name
LIMIT 100 LIMIT 100
]; ];

View File

@ -980,10 +980,10 @@ it('displays data', async () => {
### FLS Enforcement ### FLS Enforcement
```apex ```apex
// Always use SECURITY_ENFORCED or stripInaccessible // Always use USER_MODE or stripInaccessible
@AuraEnabled(cacheable=true) @AuraEnabled(cacheable=true)
public static List<Account> getAccounts() { public static List<Account> getAccounts() {
return [SELECT Id, Name FROM Account WITH SECURITY_ENFORCED]; return [SELECT Id, Name FROM Account WITH USER_MODE];
} }
// For DML operations // For DML operations

View File

@ -435,7 +435,7 @@ public class AccountSelector {
SELECT Id, Name, Industry SELECT Id, Name, Industry
FROM Account FROM Account
WHERE IsActive__c = true WHERE IsActive__c = true
WITH SECURITY_ENFORCED WITH USER_MODE
]; ];
} }

View File

@ -7,7 +7,7 @@ Salesforce SOQL query generation, optimization, and analysis skill with 100-poin
- **Natural Language to SOQL**: Convert requests into executable queries - **Natural Language to SOQL**: Convert requests into executable queries
- **Query Optimization**: Improve selectivity, LIMIT usage, and field selection - **Query Optimization**: Improve selectivity, LIMIT usage, and field selection
- **Relationship Queries**: Parent-child, child-parent, and polymorphic patterns - **Relationship Queries**: Parent-child, child-parent, and polymorphic patterns
- **Security Guidance**: `WITH USER_MODE`, `WITH SECURITY_ENFORCED`, and Apex-safe usage - **Security Guidance**: `WITH USER_MODE`, and Apex-safe usage
- **100-Point Scoring**: Performance, correctness, security, and readability checks - **100-Point Scoring**: Performance, correctness, security, and readability checks
## Quick Start ## Quick Start

View File

@ -47,7 +47,7 @@ public class BulkPatternExample {
SELECT Id, Name SELECT Id, Name
FROM Account FROM Account
WHERE Id IN :accountIds WHERE Id IN :accountIds
WITH SECURITY_ENFORCED WITH USER_MODE
]); ]);
// Step 3: O(1) Map lookups // Step 3: O(1) Map lookups
@ -86,7 +86,7 @@ public class GroupedChildPattern {
SELECT Id, FirstName, LastName, Email, AccountId SELECT Id, FirstName, LastName, Email, AccountId
FROM Contact FROM Contact
WHERE AccountId IN :accountIds WHERE AccountId IN :accountIds
WITH SECURITY_ENFORCED WITH USER_MODE
]) { ]) {
contactsByAccount.get(c.AccountId).add(c); contactsByAccount.get(c.AccountId).add(c);
} }
@ -137,7 +137,7 @@ public class MultiLevelLookupPattern {
SELECT Id, Name, OwnerId, Owner.Name, Owner.Email SELECT Id, Name, OwnerId, Owner.Name, Owner.Email
FROM Account FROM Account
WHERE Id IN :accountIds WHERE Id IN :accountIds
WITH SECURITY_ENFORCED WITH USER_MODE
]); ]);
// Step 3: Use the data // Step 3: Use the data
@ -177,7 +177,7 @@ public class ConditionalQueryPattern {
SELECT Id, Name, AnnualRevenue, Industry SELECT Id, Name, AnnualRevenue, Industry
FROM Account FROM Account
WHERE Id IN :highValueAccountIds WHERE Id IN :highValueAccountIds
WITH SECURITY_ENFORCED WITH USER_MODE
]); ]);
// Step 3: Process only the high-value opportunities // Step 3: Process only the high-value opportunities
@ -212,7 +212,7 @@ public class SharedQueryPattern {
SELECT Id, Name, Industry, AnnualRevenue, OwnerId SELECT Id, Name, Industry, AnnualRevenue, OwnerId
FROM Account FROM Account
WHERE Id IN :accountIds WHERE Id IN :accountIds
WITH SECURITY_ENFORCED WITH USER_MODE
]); ]);
} }

View File

@ -184,19 +184,19 @@ SELECT Id, Status__c FROM Account WHERE Id = :accountId FOR UPDATE
-- Adds overhead and can cause lock contention -- Adds overhead and can cause lock contention
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
// SECURITY ENFORCEMENT // SECURITY
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
-- ✅ Recommended: WITH SECURITY_ENFORCED -- ✅ Recommended: WITH USER_MODE (API 54.0+)
SELECT Id, Name, Phone FROM Account SELECT Id, Name, Phone FROM Account
WITH SECURITY_ENFORCED
-- Throws exception if user lacks field access
-- Alternative: USER_MODE (API 54.0+)
SELECT Id, Name FROM Account
WITH USER_MODE WITH USER_MODE
-- Respects sharing rules and FLS -- Respects sharing rules and FLS
-- Alternative: WITH SECURITY_ENFORCED (Removed in API version 67.0+, use USER_MODE instead)
SELECT Id, Name FROM Account
WITH SECURITY_ENFORCED
-- Throws exception if user lacks field access
-- For admin operations: SYSTEM_MODE -- For admin operations: SYSTEM_MODE
SELECT Id, Name FROM Account SELECT Id, Name FROM Account
WITH SYSTEM_MODE WITH SYSTEM_MODE

View File

@ -83,7 +83,7 @@ public inherited sharing class ${OBJECT_NAME}Selector {
SELECT Id, Name, OwnerId, CreatedDate, LastModifiedDate SELECT Id, Name, OwnerId, CreatedDate, LastModifiedDate
FROM ${OBJECT_NAME} FROM ${OBJECT_NAME}
WHERE Id IN :recordIds WHERE Id IN :recordIds
WITH SECURITY_ENFORCED WITH USER_MODE
]; ];
} }
@ -114,7 +114,7 @@ public inherited sharing class ${OBJECT_NAME}Selector {
SELECT Id, Name, OwnerId, CreatedDate, LastModifiedDate SELECT Id, Name, OwnerId, CreatedDate, LastModifiedDate
FROM ${OBJECT_NAME} FROM ${OBJECT_NAME}
WHERE OwnerId = :ownerId WHERE OwnerId = :ownerId
WITH SECURITY_ENFORCED WITH USER_MODE
ORDER BY LastModifiedDate DESC ORDER BY LastModifiedDate DESC
LIMIT 1000 LIMIT 1000
]; ];
@ -139,7 +139,7 @@ public inherited sharing class ${OBJECT_NAME}Selector {
FROM ${OBJECT_NAME} FROM ${OBJECT_NAME}
WHERE CreatedDate >= :startDate WHERE CreatedDate >= :startDate
AND CreatedDate <= :endDate AND CreatedDate <= :endDate
WITH SECURITY_ENFORCED WITH USER_MODE
ORDER BY CreatedDate DESC ORDER BY CreatedDate DESC
LIMIT 10000 LIMIT 10000
]; ];
@ -171,7 +171,7 @@ public inherited sharing class ${OBJECT_NAME}Selector {
LIMIT 50) LIMIT 50)
FROM ${OBJECT_NAME} FROM ${OBJECT_NAME}
WHERE Id IN :recordIds WHERE Id IN :recordIds
WITH SECURITY_ENFORCED WITH USER_MODE
]; ];
} }
@ -211,7 +211,7 @@ public inherited sharing class ${OBJECT_NAME}Selector {
SELECT Id SELECT Id
FROM ${OBJECT_NAME} FROM ${OBJECT_NAME}
WHERE Id = :recordId WHERE Id = :recordId
WITH SECURITY_ENFORCED WITH USER_MODE
LIMIT 1 LIMIT 1
]; ];
return !results.isEmpty(); return !results.isEmpty();

View File

@ -111,7 +111,7 @@ for (List<Account> accs : [SELECT Id, Name FROM Account]) {
## Security Patterns ## Security Patterns
### WITH SECURITY_ENFORCED ### WITH SECURITY_ENFORCED (Removed in API version 67.0+, use USER_MODE instead)
```sql ```sql
-- Throws exception if user lacks FLS -- Throws exception if user lacks FLS

View File

@ -41,7 +41,7 @@ public class AccountSelector {
SELECT Id, Name, Industry SELECT Id, Name, Industry
FROM Account FROM Account
WHERE Id IN :accountIds WHERE Id IN :accountIds
WITH SECURITY_ENFORCED WITH USER_MODE
]; ];
} }
} }
@ -89,7 +89,7 @@ public inherited sharing class AccountSelector {
SELECT Id, Name, Industry, AnnualRevenue, OwnerId SELECT Id, Name, Industry, AnnualRevenue, OwnerId
FROM Account FROM Account
WHERE Id IN :accountIds WHERE Id IN :accountIds
WITH SECURITY_ENFORCED WITH USER_MODE
]; ];
} }
@ -101,7 +101,7 @@ public inherited sharing class AccountSelector {
SELECT Id, Name, Industry, AnnualRevenue, OwnerId SELECT Id, Name, Industry, AnnualRevenue, OwnerId
FROM Account FROM Account
WHERE OwnerId = :ownerId WHERE OwnerId = :ownerId
WITH SECURITY_ENFORCED WITH USER_MODE
LIMIT 1000 LIMIT 1000
]; ];
} }
@ -118,7 +118,7 @@ public inherited sharing class AccountSelector {
LIMIT 50) LIMIT 50)
FROM Account FROM Account
WHERE Id IN :accountIds WHERE Id IN :accountIds
WITH SECURITY_ENFORCED WITH USER_MODE
]; ];
} }
} }
@ -207,7 +207,7 @@ public inherited sharing class OpportunitySelector {
SELECT Id, Name, StageName, Amount, CloseDate, AccountId SELECT Id, Name, StageName, Amount, CloseDate, AccountId
FROM Opportunity FROM Opportunity
WHERE AccountId IN :accountIds WHERE AccountId IN :accountIds
WITH SECURITY_ENFORCED WITH USER_MODE
]; ];
} }
@ -402,7 +402,7 @@ public inherited sharing class BulkQueryHelper {
SELECT Id, Name, Industry SELECT Id, Name, Industry
FROM Account FROM Account
WHERE Id IN :accountIds WHERE Id IN :accountIds
WITH SECURITY_ENFORCED WITH USER_MODE
]); ]);
} }
@ -416,7 +416,7 @@ public inherited sharing class BulkQueryHelper {
SELECT Id, FirstName, LastName, Email, AccountId SELECT Id, FirstName, LastName, Email, AccountId
FROM Contact FROM Contact
WHERE AccountId IN :accountIds WHERE AccountId IN :accountIds
WITH SECURITY_ENFORCED WITH USER_MODE
]) { ]) {
if (!contactsByAccount.containsKey(c.AccountId)) { if (!contactsByAccount.containsKey(c.AccountId)) {
contactsByAccount.put(c.AccountId, new List<Contact>()); contactsByAccount.put(c.AccountId, new List<Contact>());
@ -456,15 +456,15 @@ for (Opportunity opp : Trigger.new) {
## Best Practices Summary ## Best Practices Summary
| Practice | Benefit | | Practice | Benefit |
|----------|---------| |--------------------------------|---------|
| Centralize in Selector classes | One place to update field lists | | Centralize in Selector classes | One place to update field lists |
| Use `WITH SECURITY_ENFORCED` | Automatic FLS enforcement | | Use `WITH USER_MODE` | Respects sharing rules and FLS |
| Return empty List, not null | Prevents NullPointerException | | Return empty List, not null | Prevents NullPointerException |
| Use `inherited sharing` | Respects caller's sharing context | | Use `inherited sharing` | Respects caller's sharing context |
| Make fields list a constant | Easy to update across queries | | Make fields list a constant | Easy to update across queries |
| Add null/empty checks | Prevent unnecessary queries | | Add null/empty checks | Prevent unnecessary queries |
| Support mocking in tests | Faster tests, no database dependencies | | Support mocking in tests | Faster tests, no database dependencies |
--- ---

View File

@ -165,11 +165,11 @@ SELECT Id, (SELECT Id FROM Children__r) FROM Parent__c
## WITH Clauses ## WITH Clauses
| Clause | Description | | Clause | Description |
|--------|-------------| |--------|-----------------------------------------------------------------------------|
| `WITH SECURITY_ENFORCED` | Enforce FLS (throws exception if no access) | | `WITH SECURITY_ENFORCED` | Enforce FLS (throws exception if no access; removed in API version 67.0+) |
| `WITH USER_MODE` | Respect sharing and FLS | | `WITH USER_MODE` | Respect sharing and FLS |
| `WITH SYSTEM_MODE` | Bypass sharing rules | | `WITH SYSTEM_MODE` | Bypass sharing rules |
--- ---