UNPKG

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
# 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@company.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(); } @AuraEnabled 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; } } @RestResource(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