mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-08 16:25:58 +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
|
||||
FROM Account
|
||||
WHERE Name LIKE :searchKey
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
ORDER BY Name
|
||||
LIMIT :recordLimit
|
||||
];
|
||||
@ -57,7 +57,7 @@ public with sharing class LwcController {
|
||||
(SELECT Id, FirstName, LastName, Email FROM Contacts LIMIT 5)
|
||||
FROM Account
|
||||
WHERE Id = :accountId
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
LIMIT 1
|
||||
];
|
||||
|
||||
@ -146,7 +146,7 @@ public with sharing class LwcController {
|
||||
List<Account> existing = [
|
||||
SELECT Id FROM Account
|
||||
WHERE Id = :accountId
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
LIMIT 1
|
||||
];
|
||||
|
||||
@ -201,7 +201,7 @@ public with sharing class LwcController {
|
||||
List<Account> accountsToDelete = [
|
||||
SELECT Id FROM Account
|
||||
WHERE Id IN :recordIds
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
];
|
||||
|
||||
if (accountsToDelete.size() != recordIds.size()) {
|
||||
@ -280,14 +280,14 @@ public with sharing class LwcController {
|
||||
// Build dynamic query
|
||||
String query = 'SELECT Id, Name, Industry, AnnualRevenue, Phone, CreatedDate ' +
|
||||
'FROM Account ' +
|
||||
'WITH SECURITY_ENFORCED ' +
|
||||
'WITH USER_MODE ' +
|
||||
'ORDER BY ' + sortField + ' ' + sortDir + ' NULLS LAST ' +
|
||||
'LIMIT :recordLimit OFFSET :recordOffset';
|
||||
|
||||
List<Account> records = Database.query(query);
|
||||
|
||||
// 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);
|
||||
} catch (Exception e) {
|
||||
|
||||
@ -138,7 +138,7 @@ public static List<Account> getAccounts(String searchTerm) {
|
||||
SELECT Id, Name, Industry
|
||||
FROM Account
|
||||
WHERE Name LIKE :searchKey
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
LIMIT 50
|
||||
];
|
||||
}
|
||||
@ -1368,7 +1368,7 @@ public with sharing class LwcController {
|
||||
SELECT Id, Name, Industry, AnnualRevenue
|
||||
FROM Account
|
||||
WHERE Name LIKE :searchKey
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
ORDER BY Name
|
||||
LIMIT 50
|
||||
];
|
||||
@ -1425,7 +1425,7 @@ public static void deleteAccounts(List<Id> accountIds) {
|
||||
List<Account> toDelete = [
|
||||
SELECT Id FROM Account
|
||||
WHERE Id IN :accountIds
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
];
|
||||
|
||||
delete toDelete;
|
||||
@ -1446,7 +1446,7 @@ public static List<Contact> getContactsWithErrorHandling(Id accountId) {
|
||||
SELECT Id, Name, Email, Phone
|
||||
FROM Contact
|
||||
WHERE AccountId = :accountId
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
ORDER BY Name
|
||||
LIMIT 100
|
||||
];
|
||||
|
||||
@ -980,10 +980,10 @@ it('displays data', async () => {
|
||||
### FLS Enforcement
|
||||
|
||||
```apex
|
||||
// Always use SECURITY_ENFORCED or stripInaccessible
|
||||
// Always use USER_MODE or stripInaccessible
|
||||
@AuraEnabled(cacheable=true)
|
||||
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
|
||||
|
||||
@ -435,7 +435,7 @@ public class AccountSelector {
|
||||
SELECT Id, Name, Industry
|
||||
FROM Account
|
||||
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
|
||||
- **Query Optimization**: Improve selectivity, LIMIT usage, and field selection
|
||||
- **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
|
||||
|
||||
## Quick Start
|
||||
|
||||
@ -47,7 +47,7 @@ public class BulkPatternExample {
|
||||
SELECT Id, Name
|
||||
FROM Account
|
||||
WHERE Id IN :accountIds
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
]);
|
||||
|
||||
// Step 3: O(1) Map lookups
|
||||
@ -86,7 +86,7 @@ public class GroupedChildPattern {
|
||||
SELECT Id, FirstName, LastName, Email, AccountId
|
||||
FROM Contact
|
||||
WHERE AccountId IN :accountIds
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
]) {
|
||||
contactsByAccount.get(c.AccountId).add(c);
|
||||
}
|
||||
@ -137,7 +137,7 @@ public class MultiLevelLookupPattern {
|
||||
SELECT Id, Name, OwnerId, Owner.Name, Owner.Email
|
||||
FROM Account
|
||||
WHERE Id IN :accountIds
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
]);
|
||||
|
||||
// Step 3: Use the data
|
||||
@ -177,7 +177,7 @@ public class ConditionalQueryPattern {
|
||||
SELECT Id, Name, AnnualRevenue, Industry
|
||||
FROM Account
|
||||
WHERE Id IN :highValueAccountIds
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
]);
|
||||
|
||||
// Step 3: Process only the high-value opportunities
|
||||
@ -212,7 +212,7 @@ public class SharedQueryPattern {
|
||||
SELECT Id, Name, Industry, AnnualRevenue, OwnerId
|
||||
FROM Account
|
||||
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
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// SECURITY ENFORCEMENT
|
||||
// SECURITY
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- ✅ Recommended: WITH SECURITY_ENFORCED
|
||||
-- ✅ Recommended: WITH USER_MODE (API 54.0+)
|
||||
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
|
||||
-- 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
|
||||
SELECT Id, Name FROM Account
|
||||
WITH SYSTEM_MODE
|
||||
|
||||
@ -83,7 +83,7 @@ public inherited sharing class ${OBJECT_NAME}Selector {
|
||||
SELECT Id, Name, OwnerId, CreatedDate, LastModifiedDate
|
||||
FROM ${OBJECT_NAME}
|
||||
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
|
||||
FROM ${OBJECT_NAME}
|
||||
WHERE OwnerId = :ownerId
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
ORDER BY LastModifiedDate DESC
|
||||
LIMIT 1000
|
||||
];
|
||||
@ -139,7 +139,7 @@ public inherited sharing class ${OBJECT_NAME}Selector {
|
||||
FROM ${OBJECT_NAME}
|
||||
WHERE CreatedDate >= :startDate
|
||||
AND CreatedDate <= :endDate
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
ORDER BY CreatedDate DESC
|
||||
LIMIT 10000
|
||||
];
|
||||
@ -171,7 +171,7 @@ public inherited sharing class ${OBJECT_NAME}Selector {
|
||||
LIMIT 50)
|
||||
FROM ${OBJECT_NAME}
|
||||
WHERE Id IN :recordIds
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
];
|
||||
}
|
||||
|
||||
@ -211,7 +211,7 @@ public inherited sharing class ${OBJECT_NAME}Selector {
|
||||
SELECT Id
|
||||
FROM ${OBJECT_NAME}
|
||||
WHERE Id = :recordId
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
LIMIT 1
|
||||
];
|
||||
return !results.isEmpty();
|
||||
|
||||
@ -111,7 +111,7 @@ for (List<Account> accs : [SELECT Id, Name FROM Account]) {
|
||||
|
||||
## Security Patterns
|
||||
|
||||
### WITH SECURITY_ENFORCED
|
||||
### WITH SECURITY_ENFORCED (Removed in API version 67.0+, use USER_MODE instead)
|
||||
|
||||
```sql
|
||||
-- Throws exception if user lacks FLS
|
||||
|
||||
@ -41,7 +41,7 @@ public class AccountSelector {
|
||||
SELECT Id, Name, Industry
|
||||
FROM Account
|
||||
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
|
||||
FROM Account
|
||||
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
|
||||
FROM Account
|
||||
WHERE OwnerId = :ownerId
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
LIMIT 1000
|
||||
];
|
||||
}
|
||||
@ -118,7 +118,7 @@ public inherited sharing class AccountSelector {
|
||||
LIMIT 50)
|
||||
FROM Account
|
||||
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
|
||||
FROM Opportunity
|
||||
WHERE AccountId IN :accountIds
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
];
|
||||
}
|
||||
|
||||
@ -402,7 +402,7 @@ public inherited sharing class BulkQueryHelper {
|
||||
SELECT Id, Name, Industry
|
||||
FROM Account
|
||||
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
|
||||
FROM Contact
|
||||
WHERE AccountId IN :accountIds
|
||||
WITH SECURITY_ENFORCED
|
||||
WITH USER_MODE
|
||||
]) {
|
||||
if (!contactsByAccount.containsKey(c.AccountId)) {
|
||||
contactsByAccount.put(c.AccountId, new List<Contact>());
|
||||
@ -456,15 +456,15 @@ for (Opportunity opp : Trigger.new) {
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
| Practice | Benefit |
|
||||
|----------|---------|
|
||||
| Practice | Benefit |
|
||||
|--------------------------------|---------|
|
||||
| Centralize in Selector classes | One place to update field lists |
|
||||
| Use `WITH SECURITY_ENFORCED` | Automatic FLS enforcement |
|
||||
| Return empty List, not null | Prevents NullPointerException |
|
||||
| Use `inherited sharing` | Respects caller's sharing context |
|
||||
| Make fields list a constant | Easy to update across queries |
|
||||
| Add null/empty checks | Prevent unnecessary queries |
|
||||
| Support mocking in tests | Faster tests, no database dependencies |
|
||||
| Use `WITH USER_MODE` | Respects sharing rules and FLS |
|
||||
| Return empty List, not null | Prevents NullPointerException |
|
||||
| Use `inherited sharing` | Respects caller's sharing context |
|
||||
| Make fields list a constant | Easy to update across queries |
|
||||
| Add null/empty checks | Prevent unnecessary queries |
|
||||
| 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
|
||||
|
||||
| Clause | Description |
|
||||
|--------|-------------|
|
||||
| `WITH SECURITY_ENFORCED` | Enforce FLS (throws exception if no access) |
|
||||
| `WITH USER_MODE` | Respect sharing and FLS |
|
||||
| `WITH SYSTEM_MODE` | Bypass sharing rules |
|
||||
| Clause | Description |
|
||||
|--------|-----------------------------------------------------------------------------|
|
||||
| `WITH SECURITY_ENFORCED` | Enforce FLS (throws exception if no access; removed in API version 67.0+) |
|
||||
| `WITH USER_MODE` | Respect sharing and FLS |
|
||||
| `WITH SYSTEM_MODE` | Bypass sharing rules |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user