精通 Salesforce 共享规则:细粒度数据访问与安全控制

背景与应用场景

在 Salesforce 平台中,数据安全和访问控制是任何成功实施的核心。组织范围默认值(Organization-Wide Defaults (OWD))提供了数据的基线访问权限,它决定了用户对不拥有记录的默认访问级别。然而,仅仅依靠 OWD 往往不足以满足复杂的业务需求。例如,您可能希望销售团队的经理能够查看其下属的所有销售机会(Opportunity)记录,即使他们并不直接拥有这些记录。又或者,您希望所有“高优先级”的客户服务案例(Case)对特定的支持团队可见,无论这些案例由谁拥有。

这就是 Salesforce 共享规则(Sharing Rules)发挥作用的地方。共享规则是 Salesforce 强大的数据访问控制模型中的一个关键组成部分,它允许您在不更改 OWD 的前提下,进一步“打开”对特定记录的访问权限。它们是声明式(Declarative)工具,意味着您可以通过配置而不是编写代码来实现复杂的共享逻辑。

共享规则的主要应用场景包括:

  • 按角色或公共组共享:例如,允许特定地区的所有销售代表查看该地区的所有客户(Account)。
  • 按条件共享:例如,允许支持经理查看所有“状态为已升级”的案例。
  • 跨部门协作:当不同部门需要访问彼此的特定记录以完成项目或任务时。
  • 为外部用户共享:通过客户社区(Community)或合作伙伴社区(Partner Community)向外部用户提供精细的数据访问。

原理说明

Salesforce 的共享模型是一个分层结构,访问权限总是累积的,这意味着更开放的设置会覆盖更严格的设置。这个模型的层次顺序大致如下:

  1. 组织范围默认值 (Organization-Wide Defaults - OWD):这是最基本的访问级别,定义了特定对象在整个组织中的默认访问权限。它通常是最严格的设置,例如“私有”(Private)或“公共只读”(Public Read Only)。
  2. 角色层次结构 (Role Hierarchy):在 OWD 之上,角色层次结构允许用户向上查看。位于层次结构中较高位置的用户可以自动访问其下属用户拥有的所有记录。例如,销售总监可以查看所有区域销售经理的记录。
  3. 共享规则 (Sharing Rules):共享规则在 OWD 和角色层次结构之上,用于授予对特定记录的额外访问权限。它们允许您根据记录的所有者或记录的字段条件,向特定用户、角色或公共组(Public Group)开放访问。共享规则是“打开”访问,而不是“限制”访问。
  4. 手动共享 (Manual Sharing):手动共享允许记录所有者或具有“完全访问”(Full Access)权限的用户,手动地将特定记录共享给其他用户、公共组、角色或角色和下属。它适用于一次性、非通用的共享需求。
  5. Apex 管理共享 (Apex Managed Sharing):对于通过声明式工具无法实现的复杂或动态共享场景,可以使用 Apex 代码来管理共享。这为开发者提供了对共享模型的最大粒度控制。

共享规则类型

共享规则主要分为两种类型:

  • 基于所有者的共享规则 (Owner-Based Sharing Rules):根据记录的所有者来共享记录。例如,将所有者是“销售团队”角色成员的客户记录,共享给“销售经理”角色。
  • 基于条件的共享规则 (Criteria-Based Sharing Rules):根据记录中特定字段的值来共享记录。例如,将“状态”字段为“已关闭 - 赢单”的所有销售机会,共享给“财务部”公共组。

Apex 管理共享 (Apex Managed Sharing)

当声明式共享规则无法满足复杂的业务逻辑时,Apex 管理共享提供了编程控制共享的能力。通过操作特定的共享对象,如 AccountShareOpportunityShare 或任何自定义对象的 `[CustomObject]__Share` 对象,您可以动态地授予或撤销访问权限。

每个共享对象都包含以下关键字段:

  • ParentId:被共享记录的 ID(例如,`AccountId`,`OpportunityId`)。
  • UserOrGroupId:被授予访问权限的用户(User)或组(Group)的 ID。
  • AccessLevel:授予的访问级别(例如,对于 AccountShare,是 `AccountAccessLevel`;对于通用共享,是 `AccessLevel`)。常见的访问级别包括 `Read` (只读) 和 `Edit` (读写)。
  • RowCause:这是一个非常重要的字段,它指示了记录被共享的原因。对于 Apex 管理共享,您可以指定 `Manual`(手动)作为标准对象共享的原因。对于自定义对象,您可以创建自定义的“共享原因”(Schema.SharingReason)来标识您的 Apex 共享逻辑。`RowCause` 字段对于跟踪和管理通过 Apex 授予的共享记录至关重要。

示例代码

由于声明式共享规则通过用户界面配置,没有直接的 Apex 代码来“创建”它们。然而,Apex 管理共享是共享模型的重要组成部分,它允许通过 Apex 代码来实现复杂的共享逻辑。以下是基于 Salesforce 官方文档的 Apex 管理共享示例,演示如何创建和删除共享记录。

示例 1:通过 Apex 为 Account 对象授予读写权限

此示例演示如何通过创建 AccountShare 记录,将特定客户的读写权限授予另一个用户。

// 查询一个现有的客户记录
// Query an existing Account record
Account targetAccount = [SELECT Id, Name FROM Account WHERE Name = 'Salesforce Inc.' LIMIT 1];

// 查询一个用于共享的现有用户
// Query an existing User to share with
User targetUser = [SELECT Id, Name FROM User WHERE IsActive = true AND Profile.Name = 'Standard User' LIMIT 1];

if (targetAccount != null && targetUser != null) {
    // 创建一个新的 AccountShare 对象
    // Create a new AccountShare object
    AccountShare newShare = new AccountShare();

    // 设置被共享的客户记录的ID
    // Set the ID of the Account record being shared
    newShare.AccountId = targetAccount.Id;

    // 设置被授予访问权限的用户或组的ID
    // Set the ID of the User or Group to whom access is granted
    newShare.UserOrGroupId = targetUser.Id;

    // 设置访问级别。对于AccountShare,可以是 'Read' (只读) 或 'Edit' (读写)。
    // Set the access level. For AccountShare, it can be 'Read' or 'Edit'.
    newShare.AccountAccessLevel = 'Edit'; // 授予读写权限

    // 设置共享的原因。对于标准对象的Apex管理共享,通常使用 'Manual'。
    // For custom objects, you can define a custom Sharing Reason.
    // Set the RowCause. For Apex managed sharing on standard objects, 'Manual' is commonly used.
    // For custom objects, you can define a custom Sharing Reason.
    newShare.RowCause = 'Manual';

    // 尝试插入 AccountShare 记录。使用 Database.insert(record, false) 以允许部分成功并捕获错误。
    // Attempt to insert the AccountShare record. Use Database.insert(record, false) to allow partial success and capture errors.
    Database.SaveResult sr = Database.insert(newShare, false);

    if (sr.isSuccess()) {
        System.debug('AccountShare created successfully with ID: ' + sr.getId());
        System.debug('User ' + targetUser.Name + ' now has Edit access to Account ' + targetAccount.Name);
    } else {
        // 如果插入失败,遍历并打印所有错误信息
        // If insertion fails, iterate and print all error messages
        for(Database.Error err : sr.getErrors()) {
            System.debug('Error creating AccountShare for Account ' + targetAccount.Name + ' and User ' + targetUser.Name + ': ' + err.getMessage());
        }
    }
} else {
    System.debug('Could not find target Account or User for sharing example.');
}

示例 2:通过 Apex 为自定义对象授予只读权限

此示例假设您有一个名为 My_Custom_Object__c 的自定义对象,并演示如何将其只读权限授予一个公共组。

// 查询一个现有的自定义对象记录
// Query an existing custom object record
My_Custom_Object__c customRecord = [SELECT Id, Name FROM My_Custom_Object__c WHERE Name = 'Project Alpha' LIMIT 1];

// 查询一个用于共享的公共组
// Query an existing Public Group to share with
Group targetGroup = [SELECT Id, Name FROM Group WHERE Type = 'Regular' AND Name = 'All Sales Users' LIMIT 1];

if (customRecord != null && targetGroup != null) {
    // 创建一个新的自定义对象的 Share 对象
    // Create a new Share object for the custom object
    My_Custom_Object__Share newCustomShare = new My_Custom_Object__Share();

    // 设置被共享的自定义记录的ID
    // Set the ID of the custom record being shared
    newCustomShare.ParentId = customRecord.Id;

    // 设置被授予访问权限的用户或组的ID
    // Set the ID of the User or Group to whom access is granted
    newCustomShare.UserOrGroupId = targetGroup.Id;

    // 设置访问级别。对于自定义对象,通常是 'Read' 或 'Edit'。
    // Set the access level. For custom objects, it's typically 'Read' or 'Edit'.
    newCustomShare.AccessLevel = 'Read'; // 授予只读权限

    // 对于自定义对象,RowCause 字段是只读的,除非您定义了自定义的 Sharing Reason。
    // 如果没有自定义 Sharing Reason,则通常会使用 'Manual'。
    // For custom objects, the RowCause field is read-only unless you define a custom Sharing Reason.
    // If no custom Sharing Reason, 'Manual' is often used.
    newCustomShare.RowCause = 'Manual';

    Database.SaveResult sr = Database.insert(newCustomShare, false);

    if (sr.isSuccess()) {
        System.debug('Custom Object Share created successfully with ID: ' + sr.getId());
        System.debug('Group ' + targetGroup.Name + ' now has Read access to Custom Object ' + customRecord.Name);
    } else {
        for(Database.Error err : sr.getErrors()) {
            System.debug('Error creating Custom Object Share for ' + customRecord.Name + ' and Group ' + targetGroup.Name + ': ' + err.getMessage());
        }
    }
} else {
    System.debug('Could not find target Custom Object record or Group for sharing example.');
}

示例 3:通过 Apex 删除 Apex 管理的共享记录

此示例演示如何删除之前通过 Apex 管理共享创建的共享记录。

// 查询需要删除共享记录的客户
// Query the Account for which to delete the share record
Account accountToDeleteShareFrom = [SELECT Id, Name FROM Account WHERE Name = 'Salesforce Inc.' LIMIT 1];

// 查询需要删除其共享权限的用户
// Query the User whose share permission needs to be deleted
User userToDeleteShareFor = [SELECT Id, Name FROM User WHERE IsActive = true AND Profile.Name = 'Standard User' LIMIT 1];

if (accountToDeleteShareFrom != null && userToDeleteShareFor != null) {
    // 查找特定的 Apex 管理的 AccountShare 记录
    // 注意:务必确保 RowCause 匹配您最初用于创建共享的原因(例如 'Manual'),以避免删除由 Salesforce 自动生成的共享记录。
    // Find the specific Apex managed AccountShare record
    // IMPORTANT: Ensure RowCause matches the reason you originally used to create the share (e.g., 'Manual')
    // to avoid deleting automatically generated sharing records by Salesforce.
    AccountShare existingShare = [
        SELECT Id, RowCause
        FROM AccountShare
        WHERE AccountId = :accountToDeleteShareFrom.Id
        AND UserOrGroupId = :userToDeleteShareFor.Id
        AND RowCause = 'Manual' // 确保只删除通过 Apex 管理的共享
        LIMIT 1
    ];

    if (existingShare != null) {
        // 尝试删除 AccountShare 记录
        // Attempt to delete the AccountShare record
        Database.DeleteResult dr = Database.delete(existingShare, false);

        if (dr.isSuccess()) {
            System.debug('AccountShare deleted successfully with ID: ' + dr.getId());
            System.debug('User ' + userToDeleteShareFor.Name + ' no longer has Apex managed Edit access to Account ' + accountToDeleteShareFrom.Name);
        } else {
            // 如果删除失败,遍历并打印所有错误信息
            // If deletion fails, iterate and print all error messages
            for(Database.Error err : dr.getErrors()) {
                System.debug('Error deleting AccountShare for Account ' + accountToDeleteShareFrom.Name + ' and User ' + userToDeleteShareFor.Name + ': ' + err.getMessage());
            }
        }
    } else {
        System.debug('No Apex Managed AccountShare found for deletion for Account ' + accountToDeleteShareFrom.Name + ' and User ' + userToDeleteShareFor.Name + '.');
    }
} else {
    System.debug('Could not find target Account or User for share deletion example.');
}

注意事项

  • 性能影响:在大型组织中,过多的共享规则,尤其是复杂的基于条件的共享规则,可能会导致性能问题,因为每次数据更改或用户结构更改(如角色变动)都可能触发共享计算(Recalculation)。
  • 复杂性管理:共享规则的累积效应可能使数据访问权限的理解和管理变得复杂。建议保持共享模型尽可能简单,并定期审查和清理不必要的规则。
  • 权限:只有具有“管理所有数据”(Manage All Data)权限或特定对象“修改所有”(Modify All)权限的管理员或用户才能创建和管理共享规则。对于 Apex 管理共享,需要拥有“修改所有数据”(Modify All Data)权限。
  • API 限制与批量处理:使用 Apex 管理共享时,必须遵守 Apex 的 governor 限制,例如 DML 操作限制。对于批量操作,务必批量化(Bulkify)您的 Apex 代码,避免在循环中执行 DML 语句。
  • 错误处理:在 Apex 管理共享中,使用 Database.insert()Database.delete() 方法的非全成功版本(即第二个参数为 false)可以捕获部分操作失败的情况,并进行适当的错误处理。
  • 标准对象与自定义对象:所有自定义对象都会自动创建一个关联的共享对象([CustomObject]__Share)。大多数标准对象,如 Account、Opportunity、Case 等,也有对应的共享对象(如 AccountShare)。但并非所有标准对象都支持 Apex 管理共享。
  • 共享原因 (RowCause):在 Apex 管理共享中,为共享记录设置正确的 RowCause 至关重要。对于标准对象,通常使用 `Manual`。对于自定义对象,您可以定义自定义共享原因来更好地追踪共享来源。
  • 查询访问权限:您可以使用 UserRecordAccess 对象来查询特定用户对特定记录的访问权限。这对于调试共享问题非常有用。例如:
    List<UserRecordAccess> uraList = [
        SELECT RecordId, HasReadAccess, HasEditAccess, HasDeleteAccess
        FROM UserRecordAccess
        WHERE UserId = :UserInfo.getUserId() AND RecordId = :someAccountId
    ];
    if (!uraList.isEmpty()) {
        System.debug('User has read access to account: ' + uraList[0].HasReadAccess);
    }
            
    ⚠️ 未找到官方文档支持:需要强调的是,UserRecordAccess 对象并非用于创建共享,而是用于查询用户对特定记录的访问权限。
  • 重新计算共享:当 OWDs、角色层次结构或共享规则发生变化时,Salesforce 会进行共享重新计算,这可能需要时间,尤其是在大型组织中。

总结与最佳实践

Salesforce 共享规则是构建安全且灵活的数据访问模型的强大工具。要有效利用它们,请遵循以下最佳实践:

  • 始于最严格的 OWD:始终从最严格的组织范围默认值开始(例如,将默认外部访问设置为“私有”),然后逐步开放访问。
  • 充分利用角色层次结构:首先通过角色层次结构来满足自然的数据可见性需求,因为它是最直接且性能最好的共享方式。
  • 明智地使用共享规则:仅当 OWD 和角色层次结构不足以满足需求时,才使用共享规则来授予额外的访问权限。优先使用基于所有者的规则,因为它们通常比基于条件的规则更易于管理和理解。
  • 谨慎使用手动共享:手动共享应仅用于一次性的、非重复性的共享场景。过度使用手动共享会导致管理上的噩梦。
  • 当声明式不足时才使用 Apex 管理共享:Apex 管理共享是解决复杂、动态共享需求的最强大工具,但也引入了代码维护和测试的开销。仅在无法通过声明式工具实现时才考虑使用。
  • 保持简洁:避免创建过多的共享规则。规则越多,系统性能受到的影响越大,而且排查访问问题也越困难。尝试通过公共组或角色层次结构的优化来简化共享规则。
  • 定期审查和文档化:定期审查您的共享设置,确保它们仍然符合业务需求,并消除任何过时或冗余的规则。清晰地文档化您的共享模型,这对于未来的维护和故障排除至关重要。
  • 全面测试:在部署任何共享规则或 Apex 共享逻辑之前,务必在沙盒环境中进行彻底测试,以验证预期的访问权限行为。

通过遵循这些原则和最佳实践,您可以构建一个健壮、高效且易于管理的 Salesforce 共享模型,确保您的数据既安全又对需要访问它的用户可用。

评论

此博客中的热门博文

Salesforce 登录取证:深入解析用户访问监控与安全

Salesforce Experience Cloud 技术深度解析:构建社区站点 (Community Sites)

Salesforce Data Loader 全方位指南:数据迁移与管理的最佳实践