Authorize group milestones in milestone search

What

Let group milestone documents pass the authorization filter so they show up in results, and route group-level milestone searches by both routing keys.

Changes

  • ee/lib/search/elastic/milestone_query_builder.rb and ee/lib/gitlab/elastic/search_results.rb: group milestone documents don't have issues_access_level/merge_requests_access_level, so the current features: [:issues, :merge_requests] filter drops them. Add group-level authorization based on group membership and group visibility, modeled on work items (by_combined_search_level_and_membership).
  • Add group: :route to the milestone eager load in ee/lib/gitlab/elastic/search_results.rb, to avoid an N+1 when rendering group milestones.
  • Route group-level milestone searches by both keys: n_<root_ancestor_id> for group milestones and project_<id> for the group's projects, as a comma-separated routing list. Elastic::Latest::Routing caps this at 128 values; beyond that, leave routing off. Project milestones keep project_<id> and group milestones use n_<root_ancestor_id> since #630654 (closed).

Why project and group milestones need different authorization

A project milestone is only readable if the user can access the project's issues or merge requests. ProjectPolicy (app/policies/project_policy.rb):

rule { issues_disabled & merge_requests_disabled }.policy do
  prevent :read_milestone

"Disabled" is per user: a feature set to "Only project members" counts as disabled for a non-member. This is why project milestone documents index issues_access_level and merge_requests_access_level, and why milestone search passes features: [:issues, :merge_requests]. The filter checks those fields on every access path: public/internal projects need ENABLED for non-members, members need ENABLED or PRIVATE plus the feature's minimum role (with a relaxed merge requests rule that lets Guest and Planner members see milestones), and admins need ENABLED or PRIVATE.

A group milestone works differently. It is readable by anyone who can read the group. GroupPolicy (app/policies/group_policy.rb):

rule { can?(:read_group) }.policy do
  enable :read_milestone

Groups have no issues or merge requests feature settings, so group milestone documents don't have, and shouldn't get, those two fields. The new filter branch for group milestones must mirror read_group instead: group visibility for non-members, and the same membership rules as read_group for private groups, with no dependency on *_access_level fields. Project milestones keep their current rules unchanged.

This also means the earlier thread comment saying "the same *_access_level fields will apply" doesn't hold for group milestones.

Depends on

#630654 (closed) (group milestone fields, routing, indexing and sync).

Notes

This corrects the implementation plan, which says the query builder needs no changes.

Part of #428589. Based on the implementation plan.

Edited by Ravi Kumar