mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-09 00:42:46 +08:00
replace SECURITY_ENFORCED with USER_MODE across SOQL/Apex skill examples
This commit is contained in:
parent
fe3e2edad0
commit
d4bfd686c2
@ -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) {
|
||||||
|
|||||||
@ -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
|
||||||
];
|
];
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -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 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user