JavaScriptの`User`モデルで`user.groups`の使用を非推奨にする

これは The road to stable, then permanent, for granular_anonymous_and_logged_in_groups_permissions および Granular 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.groupscurrentUser.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: group を持つ list タイプのテーマ設定では、resolve_group_membership: true を使用できます:

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

これにより、クライアント上の settings.copy_button_allowed_groupssettings.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) {
    // ユーザーはこのセクションで選択されたグループの少なくとも1つに属しています。
  }
}