Draft: Return public and internal project snippets to non-members

What does this MR do and why?

Step 3 of 3 for #35760.

Advanced Search never returned public or internal project snippets to users who are not project members. The same was true for admins without admin mode. Elastic::Latest::SnippetClassProxy#filter_project_snippets only matched snippets in user.authorized_projects. Basic search did return these snippets.

filter_project_snippets now has a second branch next to the existing membership branch. It matches a project snippet when both of these are true:

  • project_visibility_level and the snippet's visibility_level are both in Gitlab::VisibilityLevel.levels_for_user(user).
  • snippets_access_level is ProjectFeature::ENABLED.

The existing read_cross_project check still applies first. The new branch is only added once the backfill_project_access_on_snippets migration has finished. This way, search never relies on documents that don't have the new fields yet.

This stack must merge in order:

  1. !259626 (merged) indexes project_visibility_level and snippets_access_level on project snippet documents.
  2. !259752 (merged) adds the BackfillProjectAccessOnSnippets migration.
  3. This MR.

This MR is stacked on the branch of step 2. It must merge after both.

The rule matches ProjectSnippetPolicy for non-members.

How the rule was checked

The rule was compared against Ability.allowed?(user, :read_snippet, snippet) across these inputs:

  • Project visibility: public, internal, private.
  • Snippets access level: enabled, private, disabled.
  • Snippet visibility.
  • Users: anonymous, regular, external, admin without admin mode, guest member.

There were no differences.

External users only get public snippets, because levels_for_user returns only public for them. Private projects and private snippets never match the new branch.

Also removed:

  • The 8 pending rows in ee/spec/services/ee/search/snippet_service_spec.rb. They now pass.
  • The pending: option in ee/spec/support/helpers/search_results_helpers.rb. It has no other caller.
  • The TODO in ee/lib/elastic/latest/snippet_instance_proxy.rb.

Tests

  • ee/spec/models/concerns/elastic/snippet_spec.rb has a new spec. A non-member finds a public project snippet. A non-member does not find it when the backfill migration has not finished. The spec fails if the new branch is removed.
  • ee/spec/features/search/elastic/snippet_search_spec.rb used a public project snippet in a non-member project as its check that the Elasticsearch path is in use. That check relied on this bug. The spec now asserts that this snippet is shown.
  • The new check uses a snippet that matches the query only through its file name. Basic search (Snippet.search) matches title, description, and file name. The Elasticsearch path matches title and description only. The header comment is updated to match.
  • Known gap, not changed here: the :anonymous rows in the snippet visibility table pass vacuously. user_from_membership(:anonymous) returns nil, and expect_search_results iterates over Array(users).

References

Screenshots or screen recordings

Not applicable. Search results only change for non-members. There is no UI change.

How to set up and validate locally

  1. Enable Advanced Search in GDK and make sure all migrations have finished.
  2. Sign in as a user who is not a member of a public project that has snippets enabled.
  3. In global snippet search, search for the title of a public project snippet in that project. The snippet now appears.
  4. Set the project's snippets feature to "Only project members".
  5. Search again. The snippet no longer appears.

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

Merge request reports

Loading
Loading