Draft: Allow public container images to be viewed at group level

What does this MR do and why?

Fixes #390311: the group-level Container Registry page (e.g. /groups/<group>/-/container_registries) was returning 404 for non-members and anonymous users of public groups, even when projects in the group have public container images.

Root cause

config/authz/roles/public_anonymous.yml grants read_container_image under the project: section but not under the group: section. So can?(user, :read_container_image, group) failed for non-members, causing the controller to render 404 and the sidebar item to be hidden.

Changes

1. config/authz/roles/public_anonymous.yml

Added read_container_image to the group: raw_permissions list (alphabetically sorted). This grants the permission to anonymous users and, via inheritance, to public_authenticated (logged-in non-members) on public groups.

2. app/models/container_repository.rb

Added a new scope for_group_and_its_subgroups_visible_to(group, user) that filters repositories to only those from projects visible to the given user, respecting:

  • Project visibility (public/internal/private via Project.public_or_visible_to_user)
  • Container registry feature access level (via ProjectFeature.with_feature_available_for_user)

The existing for_group_and_its_subgroups scope is unchanged (used by Group#has_container_repository_including_subgroups? for admin checks that must see all repos).

3. app/finders/container_repositories_finder.rb

Updated group_repositories to use the new visibility-aware scope, ensuring private/internal project images are never exposed to users who cannot see those projects.

Security

  • Anonymous users see only repositories from public projects with container registry set to ENABLED or PUBLIC
  • Logged-in non-members see repositories from public and internal projects with accessible registry
  • Private project images are never exposed to non-members
  • Private group behavior is unchanged (404 for non-members)
  • Group#has_container_repository_including_subgroups? (used for transfer/deletion/rename checks) is unaffected

SQL query for the new scope (for database review)

SELECT container_repositories.*
FROM container_repositories
INNER JOIN (
  SELECT projects.id
  FROM projects
  INNER JOIN namespaces ON namespaces.id = projects.namespace_id
  INNER JOIN project_features ON project_features.project_id = projects.id
  WHERE namespaces.id IN (
    SELECT id FROM namespaces WHERE traversal_ids @> ARRAY[<group_id>]
  )
  AND projects.visibility_level IN (20)  -- public only (for anonymous)
  AND (
    project_features.container_registry_access_level IN (20, 30)  -- ENABLED, PUBLIC
    OR project_features.container_registry_access_level IS NULL
  )
) projects ON projects.id = container_repositories.project_id

The existing index index_project_features_on_project_id_include_container_registry (btree on project_id INCLUDE container_registry_access_level) supports this query efficiently.

References

Screenshots or screen recordings

N/A (backend-only change)

Before After
Group container registry page returns 404 for anonymous/non-member on public group Returns 200 and shows only images from visible projects

How to set up and validate locally

  1. Create a public group with a public project that has container images
  2. Visit /groups/<group>/-/container_registries while logged out
  3. Before: 404. After: 200 showing public project images
  4. Verify private project images in the same group are not shown

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.

Merge request reports

Loading
Loading