Fix crash in combined search authorization filter

What does this MR do and why?

Fixes a 500 error in the REST search API for users who are denied read_cross_project.

Search::Elastic::Filters.by_combined_search_level_and_membership passes a Search::Elastic::BoolExpr to by_search_level_and_membership and by_search_level_and_group_membership. When the user is denied read_cross_project, those filters call match_none_filter(query_hash). That method writes to query_hash[:query][:bool][:filter], which a BoolExpr does not have. The result is NoMethodError: undefined method 'concat' for nil.

The fix checks cross_project_search_restricted_for_user? once at the top of by_combined_search_level_and_membership. If the user is restricted, it returns match_none_filter(query_hash) on the real query hash, before the nested filters are built. Users with cross-project access get the same query as before.

Who is affected:

  • Only instances with external authorization enabled. read_cross_project is only denied then (app/policies/base_policy.rb), and never for admins or auditors. As far as we know, GitLab.com does not use it.
  • GET /api/v4/search and GET /api/v4/groups/:id/search with scope=issues or scope=work_items. These are built by Search::Elastic::WorkItemQueryBuilder. They returned a 500 instead of empty results.
  • Not affected: the web search page, because SearchController uses requires_cross_project_access and returns 403 first. Project-level search is also not affected.

I found this while working on #630618, where milestone search moves to the same combined filter. An existing milestone spec for cross-project restriction failed.

!260603 needs this fix to merge first. Without it, that MR would bring the same 500 to milestone search.

References

Screenshots or screen recordings

Reproduced in GDK as a non-admin user with external authorization enabled.

Before After
cross-project-api-before-500 cross-project-api-after-empty

Before: GET /api/v4/search?scope=work_items&search=probe returns {"message":"500 Internal Server Error"}, with the concat stack trace in log/development.log. After: it returns [].

How to set up and validate locally

Advanced search must be enabled.

  1. In a Rails console, enable external authorization:

    ApplicationSetting.current.update!(external_authorization_service_enabled: true, external_authorization_service_url: 'https://external-auth.invalid/check', external_authorization_service_default_label: 'default')
  2. Sign in as a user who is not an admin or auditor.

  3. Open https://gdk.test:3443/api/v4/search?scope=work_items&search=test. Expect []. Before this change it returned a 500.

  4. Disable external authorization again:

    ApplicationSetting.current.update!(external_authorization_service_enabled: false)

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

🤖 Generated with Claude Code

Edited by Ravi Kumar

Merge request reports

Loading
Loading