在 JavaScript 的 `User` 模型中弃用 `user.groups`

这与 The road to stable, then permanent, for granular_anonymous_and_logged_in_groups_permissionsGranular group-based permissions for anonymous and logged in users 这两个主题都有关。

在核心代码以及许多主题和插件中,以下这种模式已经变得相当常见:

const groupIds = this.currentUser.groups.map((g) => g.id);
const allowedGroupIds = this.siteSettings.some_group_setting.split("|").map((groupId) => parseInt(groupId, 10));

const hasPermission = allowedGroups.some((groupId) =>
  userGroupIds.includes(groupId)
);

if (!hasPermission) {
  return;
}

然而,这并不是检查用户权限的有效方法。用户可以属于对他们不可见的组,因此这些组不会被序列化到客户端,也就无法在安全检查中一致或准确地使用。

为了使这一点更加明确,我们将在 User 模型中将 currentUser.groups/user.groups 重命名为 currentUser.visibleGroups/user.visibleGroups,并弃用旧属性。实现此变更的初始 PR 是 DEV: Deprecate calling user.groups on client directly - Pull Request #42711 - discourse/discourse - GitHub

如果你需要在客户端的 JavaScript 中基于组 ID 列表来检查用户权限,有几种替代方案:

针对插件

扩展 current_user 序列化器,添加一个新属性,并在服务器端使用 scope.in_any_groups? 来检查用户权限,该方法还涵盖了 logged_in_usersanonymous_users 等伪组:

add_to_serializer(
  :current_user,
  :has_some_permission,
  include_condition: -> do
    SiteSetting.plugin_enabled
  end,
) { scope.in_any_groups?(SiteSetting.group_list_setting_map) }

然后你可以在客户端使用 this.currentUser.has_some_permission

针对主题和组件

对于具有 list_type: grouplist 类型主题设置,你可以使用 resolve_group_membership: true

copy_button_allowed_groups:
  default: "1|3"
  type: list
  list_type: group
  resolve_group_membership: true

这将在客户端将 settings.copy_button_allowed_groups 替换为 settings.user_in_copy_button_allowed_groups(在设置前添加 user_in_ 前缀),这是一个基于用户组成员身份在服务器端计算的布尔值。

对于具有 type: groups 的对象设置,这也适用。在 groups 属性中添加 resolve_group_membership: true

menu_sections:
  type: objects
  default:
    - name: section 1
      groups:
        - 1
        - 3
  schema:
    name: menu section
    properties:
      name:
        type: string
      groups:
        type: groups
        resolve_group_membership: true

然后访问方式如下:

for (const section of settings.menu_sections) {
  if (section.user_in_groups) {
    // 用户属于该部分选定的至少一个组。
  }
}