sf-agent-framework
Version:
AI Agent Orchestration Framework for Salesforce Development - Two-phase architecture with 70% context reduction
603 lines (472 loc) • 17.5 kB
Markdown
# Salesforce Security Patterns
## Overview
Security patterns are proven, reusable solutions to common security challenges
in Salesforce implementations. This guide provides architectural patterns and
implementation approaches for robust security.
## Authentication Patterns
### Pattern: Federated Authentication
**Context**: Enterprise with existing identity provider
**Solution Architecture**:
```
[User] → [Corporate IdP] → [SAML Assertion] → [Salesforce]
↓
[Active Directory]
```
**Implementation**:
```xml
<!-- SAML Response Pattern -->
<saml2:Assertion>
<saml2:Subject>
<saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">
user.com
</saml2:NameID>
</saml2:Subject>
<saml2:AttributeStatement>
<saml2:Attribute Name="User.FederationId">
<saml2:AttributeValue>EMP001234</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="User.ProfileId">
<saml2:AttributeValue>SalesUser</saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
</saml2:Assertion>
```
### Pattern: Delegated Authentication
**Context**: Custom authentication requirements
**Implementation**:
```apex
global class CustomAuthenticationService {
global static Boolean authenticate(String username, String password) {
// Call external authentication service
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://auth.company.com/validate');
request.setMethod('POST');
request.setBody(JSON.serialize(new Map<String,String>{
'username' => username,
'password' => EncodingUtil.base64Encode(Crypto.generateDigest('SHA256', Blob.valueOf(password)))
}));
HttpResponse response = http.send(request);
AuthResponse authResp = (AuthResponse)JSON.deserialize(response.getBody(), AuthResponse.class);
// Log authentication attempt
createAuthLog(username, authResp.success);
return authResp.success;
}
}
```
### Pattern: Multi-Factor Authentication Flow
**Context**: High-security requirements
**Flow Pattern**:
```
1. Username/Password → Validate
2. If valid → Send OTP
3. Verify OTP → Create Session
4. If invalid → Increment failure count
5. If failures > threshold → Lock account
```
## Authorization Patterns
### Pattern: Role-Based Access Control (RBAC)
**Context**: Hierarchical organization structure
**Implementation Structure**:
```apex
public class RBACPattern {
// Role hierarchy definition
public enum Role {
EXECUTIVE,
MANAGER,
SALES_REP,
SUPPORT_AGENT
}
// Permission mapping
private static Map<Role, Set<String>> rolePermissions = new Map<Role, Set<String>>{
Role.EXECUTIVE => new Set<String>{'VIEW_ALL', 'MODIFY_ALL', 'DELETE_ALL'},
Role.MANAGER => new Set<String>{'VIEW_TEAM', 'MODIFY_TEAM', 'APPROVE_DEALS'},
Role.SALES_REP => new Set<String>{'VIEW_OWN', 'MODIFY_OWN', 'CREATE_OPPS'},
Role.SUPPORT_AGENT => new Set<String>{'VIEW_CASES', 'MODIFY_CASES'}
};
public static Boolean hasPermission(Id userId, String permission) {
Role userRole = getUserRole(userId);
return rolePermissions.get(userRole).contains(permission);
}
}
```
### Pattern: Attribute-Based Access Control (ABAC)
**Context**: Complex, dynamic access rules
**Implementation**:
```apex
public class ABACPattern {
public class AccessContext {
public User user;
public SObject record;
public String action;
public Map<String, Object> environment;
}
public interface AccessRule {
Boolean evaluate(AccessContext context);
}
// Example rule: Region-based access
public class RegionAccessRule implements AccessRule {
public Boolean evaluate(AccessContext context) {
Account acc = (Account)context.record;
return context.user.Region__c == acc.Region__c ||
context.user.Profile.Name == 'System Administrator';
}
}
// Rule engine
public static Boolean evaluateAccess(AccessContext context) {
List<AccessRule> rules = getRulesForObject(context.record.getSObjectType());
for(AccessRule rule : rules) {
if(!rule.evaluate(context)) {
logAccessDenial(context);
return false;
}
}
return true;
}
}
```
### Pattern: Principle of Least Privilege
**Context**: Minimize access rights
**Implementation Strategy**:
```yaml
Base Profile: Minimum User
Objects:
- Read: Contact, Account (only owned)
- No Create/Edit/Delete
System Permissions:
- API Enabled: false
- Export Reports: false
- View Setup: false
Permission Sets (Additive):
Sales_User_PS:
- Create/Edit: Opportunity, Lead
- Run Reports: true
Manager_PS:
- View All: Opportunity
- Modify All: Team's records
API_User_PS:
- API Enabled: true
- Specific object access
```
## Data Security Patterns
### Pattern: Field-Level Encryption
**Context**: Protect sensitive data at rest
**Implementation**:
```apex
public class FieldEncryptionPattern {
private static final String ALGORITHM = 'AES256';
public static String encryptField(String plainText, String fieldName) {
// Get encryption key for field
Blob key = getEncryptionKey(fieldName);
// Encrypt data
Blob encrypted = Crypto.encryptWithManagedIV(ALGORITHM, key, Blob.valueOf(plainText));
// Return base64 encoded
return EncodingUtil.base64Encode(encrypted);
}
public static String decryptField(String encryptedText, String fieldName) {
// Check user has decrypt permission
if(!hasDecryptPermission(fieldName)) {
return '[ENCRYPTED]';
}
Blob key = getEncryptionKey(fieldName);
Blob encrypted = EncodingUtil.base64Decode(encryptedText);
Blob decrypted = Crypto.decryptWithManagedIV(ALGORITHM, key, encrypted);
return decrypted.toString();
}
}
```
### Pattern: Data Masking
**Context**: Hide sensitive data from unauthorized users
**Masking Strategies**:
```apex
public class DataMaskingPattern {
public enum MaskingType {
FULL, // Replace all: ****
PARTIAL, // Show partial: ***1234
FORMAT, // Preserve format: XXX-XX-1234
RANDOM // Random replacement
}
public static String maskData(String data, String dataType, MaskingType maskType) {
switch on dataType {
when 'SSN' {
return maskType == MaskingType.PARTIAL ?
'XXX-XX-' + data.substring(7) : 'XXX-XX-XXXX';
}
when 'CREDIT_CARD' {
return maskType == MaskingType.PARTIAL ?
'**** **** **** ' + data.substring(15) : '**** **** **** ****';
}
when 'EMAIL' {
Integer atIndex = data.indexOf('@');
return maskType == MaskingType.PARTIAL ?
data.substring(0,2) + '****' + data.substring(atIndex) : '****@****.***';
}
when else {
return String.rightPad('', data.length(), '*');
}
}
}
}
```
### Pattern: Row-Level Security
**Context**: Complex sharing requirements
**Implementation**:
```apex
public class RowLevelSecurityPattern {
// Dynamic sharing based on criteria
public static void applyDynamicSharing(List<SObject> records) {
List<SObject> sharesToCreate = new List<SObject>();
for(SObject record : records) {
// Evaluate sharing rules
List<ShareRule> rules = getShareRules(record.getSObjectType());
for(ShareRule rule : rules) {
if(rule.evaluate(record)) {
SObject share = createShare(record, rule);
sharesToCreate.add(share);
}
}
}
// Bulk insert shares
if(!sharesToCreate.isEmpty()) {
Database.insert(sharesToCreate, false);
}
}
private static SObject createShare(SObject record, ShareRule rule) {
String shareObjectName = record.getSObjectType().getDescribe().getName();
if(!shareObjectName.endsWith('__c')) {
shareObjectName += 'Share';
} else {
shareObjectName = shareObjectName.replace('__c', '__Share');
}
SObject share = Schema.getGlobalDescribe().get(shareObjectName).newSObject();
share.put('ParentId', record.Id);
share.put('UserOrGroupId', rule.shareWith);
share.put('AccessLevel', rule.accessLevel);
share.put('RowCause', 'Manual');
return share;
}
}
```
## Session Security Patterns
### Pattern: Session Timeout Management
**Context**: Enforce session limits based on user activity
**Implementation**:
```apex
public class SessionTimeoutPattern {
private static final Integer DEFAULT_TIMEOUT = 120; // minutes
private static final Integer SENSITIVE_TIMEOUT = 15; // minutes
public static void enforceSessionTimeout() {
DateTime lastActivity = getLastActivityTime();
Integer timeout = isSensitiveOperation() ? SENSITIVE_TIMEOUT : DEFAULT_TIMEOUT;
if(DateTime.now() > lastActivity.addMinutes(timeout)) {
// Force re-authentication
invalidateSession();
throw new SessionExpiredException('Session expired due to inactivity');
}
updateLastActivityTime();
}
public static void extendSession() {
// Called by client-side heartbeat
if(isValidSession()) {
updateLastActivityTime();
}
}
}
```
### Pattern: Concurrent Session Control
**Context**: Limit number of active sessions per user
**Implementation**:
```apex
public class ConcurrentSessionPattern {
private static final Integer MAX_SESSIONS = 3;
public static void enforceSessionLimit(Id userId) {
List<AuthSession> activeSessions = [SELECT Id, CreatedDate
FROM AuthSession
WHERE UsersId = :userId
AND IsCurrent = true
ORDER BY CreatedDate DESC];
if(activeSessions.size() >= MAX_SESSIONS) {
// Invalidate oldest sessions
List<Id> sessionsToKill = new List<Id>();
for(Integer i = MAX_SESSIONS - 1; i < activeSessions.size(); i++) {
sessionsToKill.add(activeSessions[i].Id);
}
invalidateSessions(sessionsToKill);
}
}
}
```
## API Security Patterns
### Pattern: API Rate Limiting
**Context**: Prevent API abuse
**Implementation**:
```apex
public class APIRateLimitPattern {
private static final Integer RATE_LIMIT = 1000; // requests per hour
public static Boolean checkRateLimit(String apiKey) {
DateTime oneHourAgo = DateTime.now().addHours(-1);
Integer requestCount = [SELECT COUNT()
FROM API_Log__c
WHERE API_Key__c = :apiKey
AND Request_Time__c > :oneHourAgo];
if(requestCount >= RATE_LIMIT) {
logRateLimitExceeded(apiKey);
return false;
}
// Log this request
insert new API_Log__c(
API_Key__c = apiKey,
Request_Time__c = DateTime.now(),
Endpoint__c = RestContext.request.requestURI
);
return true;
}
}
(urlMapping='/api/v1/*')
global class SecureAPIEndpoint {
@HttpPost
global static void doPost() {
String apiKey = RestContext.request.headers.get('X-API-Key');
if(!APIRateLimitPattern.checkRateLimit(apiKey)) {
RestContext.response.statusCode = 429; // Too Many Requests
RestContext.response.responseBody = Blob.valueOf('Rate limit exceeded');
return;
}
// Process request...
}
}
```
### Pattern: API Authentication Token
**Context**: Secure API access with rotating tokens
**Implementation**:
```apex
public class APITokenPattern {
public class TokenInfo {
public String token;
public DateTime expiry;
public String scope;
}
public static TokenInfo generateToken(String clientId, String clientSecret) {
// Validate credentials
if(!validateClient(clientId, clientSecret)) {
throw new AuthenticationException('Invalid credentials');
}
// Generate secure token
String token = EncodingUtil.base64Encode(Crypto.generateAesKey(256));
DateTime expiry = DateTime.now().addHours(1);
// Store token
API_Token__c tokenRecord = new API_Token__c(
Token__c = hashToken(token),
Client_ID__c = clientId,
Expiry__c = expiry,
Active__c = true
);
insert tokenRecord;
return new TokenInfo(){
token = token,
expiry = expiry,
scope = getClientScope(clientId)
};
}
public static Boolean validateToken(String token) {
String hashedToken = hashToken(token);
List<API_Token__c> tokens = [SELECT Id, Expiry__c, Client_ID__c
FROM API_Token__c
WHERE Token__c = :hashedToken
AND Active__c = true
AND Expiry__c > :DateTime.now()
LIMIT 1];
return !tokens.isEmpty();
}
}
```
## Audit and Compliance Patterns
### Pattern: Comprehensive Audit Trail
**Context**: Track all sensitive operations
**Implementation**:
```apex
public class AuditTrailPattern {
public enum AuditEvent {
DATA_ACCESS,
DATA_MODIFY,
PERMISSION_CHANGE,
CONFIG_CHANGE,
SECURITY_EVENT
}
@InvocableMethod(label='Create Audit Log')
public static void createAuditLog(List<AuditRequest> requests) {
List<Audit_Log__c> logs = new List<Audit_Log__c>();
for(AuditRequest request : requests) {
Audit_Log__c log = new Audit_Log__c(
Event_Type__c = request.eventType.name(),
User__c = UserInfo.getUserId(),
Record_Id__c = request.recordId,
Object_Name__c = request.objectName,
Action__c = request.action,
Old_Value__c = request.oldValue,
New_Value__c = request.newValue,
IP_Address__c = getClientIP(),
Session_Id__c = UserInfo.getSessionId(),
Timestamp__c = DateTime.now()
);
// Add context
if(request.additionalContext != null) {
log.Context__c = JSON.serialize(request.additionalContext);
}
logs.add(log);
}
// Insert with allOrNone = false to ensure logging
Database.insert(logs, false);
}
}
```
### Pattern: Security Monitoring Dashboard
**Context**: Real-time security monitoring
**Components**:
```apex
public class SecurityMonitoringPattern {
@AuraEnabled(cacheable=true)
public static SecurityMetrics getSecurityMetrics() {
SecurityMetrics metrics = new SecurityMetrics();
// Failed login attempts
metrics.failedLogins = [SELECT COUNT()
FROM LoginHistory
WHERE Status != 'Success'
AND LoginTime = LAST_N_DAYS:1];
// Suspicious API usage
metrics.suspiciousAPI = [SELECT COUNT()
FROM API_Log__c
WHERE Is_Suspicious__c = true
AND Created_Date__c = TODAY];
// Permission changes
metrics.permissionChanges = [SELECT COUNT()
FROM SetupAuditTrail
WHERE Action LIKE '%Permission%'
AND CreatedDate = LAST_N_DAYS:7];
// Data exports
metrics.dataExports = [SELECT COUNT()
FROM Audit_Log__c
WHERE Event_Type__c = 'DATA_EXPORT'
AND Timestamp__c = LAST_N_DAYS:1];
return metrics;
}
public class SecurityMetrics {
@AuraEnabled public Integer failedLogins;
@AuraEnabled public Integer suspiciousAPI;
@AuraEnabled public Integer permissionChanges;
@AuraEnabled public Integer dataExports;
}
}
```
## Best Practices Summary
1. **Layer Security**: Multiple layers of defense
2. **Fail Secure**: Default to denying access
3. **Audit Everything**: Comprehensive logging
4. **Encrypt Sensitive Data**: At rest and in transit
5. **Validate Input**: Never trust user input
6. **Minimize Attack Surface**: Disable unused features
7. **Regular Reviews**: Periodic security assessments
8. **Incident Response**: Have a plan ready
9. **Security Training**: Educate all users
10. **Stay Updated**: Apply security patches promptly