Draft: Show group container registry to non-members and anonymous users of public groups
What does this MR do and why?
On a public group, the Deploy > Container Registry page was only available to group members with at least the Reporter role. Everyone else, including visitors who are not signed in, did not see the sidebar entry and got a 404 at /groups/<group>/-/container_registries, even when the group contains public projects with public container images. Projects already work the way you would expect: a public project's container registry is visible to everyone.
With this change, anyone who can view a public group can open its container registry page and see the images from the projects in that group that they are allowed to see:
- Public projects with a public registry: shown to everyone, including users who are not signed in.
- Public projects whose registry visibility is "Only Project Members": hidden from anonymous and non-member users. Shown only to project members with at least the Reporter role.
- Private projects: never shown to users who cannot access them.
- Private groups: unchanged. Non-members and anonymous users still get a 404.
- Internal groups: signed-in users gain the same access, because the
public_authenticatedrole inherits frompublic_anonymous.
The same permission and finder are used by the REST API (GET /groups/:id/registry/repositories) and the GraphQL field Group.containerRepositories, so they gain the same behavior.
Technical details
config/authz/roles/public_anonymous.yml: addsread_container_imageto thegroupraw permissions.GroupPolicygrants these on public groups (rule { public_group }), and throughpublic_authenticatedinheritance to signed-in users on internal groups. This mirrors the existingprojectentry in the same role.app/models/container_repository.rb: adds a new scopefor_group_and_its_subgroups_visible_to_user(group, user). It is the same group-scopedINNER JOINsubquery as the existingfor_group_and_its_subgroups, but usesProject.filter_by_feature_visibility(:container_registry, user)instead ofwith_feature_enabled(:container_registry). That helper combineswith_feature_available_for_user(respects the "Only Project Members" registry setting, Reporter minimum) andpublic_or_visible_to_user(respects project visibility). The existingfor_group_and_its_subgroupsscope is unchanged becauseGroup#has_container_repository_including_subgroups?uses it and must see all repositories regardless of user.app/finders/container_repositories_finder.rb:group_repositoriesnow uses the new scope. Before this change the finder returned every repository in the group hierarchy once theread_container_imagecheck passed, which was safe only because that check required Reporter membership.doc/user/packages/container_registry/_index.md: documents the group registry visibility.- Specs: finder, model, group policy (and shared context), request spec for the group registry controller, and sidebar menu.
Database
Only ContainerRepositoriesFinder#group_repositories changes. The query keeps the same shape as on master (a single INNER JOIN on a group-scoped projects subquery); the subquery gains the registry access level and project visibility conditions.
BEFORE (master, ContainerRepository.for_group_and_its_subgroups(group)):
SELECT "container_repositories".*
FROM "container_repositories"
INNER JOIN (
SELECT "projects"."id"
FROM "projects"
INNER JOIN "project_features" ON "project_features"."project_id" = "projects"."id"
WHERE "projects"."namespace_id" IN (<group and descendants via traversal_ids CTE>)
AND ("project_features"."container_registry_access_level" IS NULL
OR "project_features"."container_registry_access_level" > 0)
) projects ON projects.id = container_repositories.project_idAFTER, anonymous user (user: nil):
SELECT "container_repositories".*
FROM "container_repositories"
INNER JOIN (
SELECT "projects"."id"
FROM "projects"
INNER JOIN "project_features" ON "project_features"."project_id" = "projects"."id"
WHERE "projects"."namespace_id" IN (<group and descendants via traversal_ids CTE>)
AND ("project_features"."container_registry_access_level" IN (20, 30)
OR "project_features"."container_registry_access_level" IS NULL)
AND "projects"."visibility_level" = 20
) projects ON projects.id = container_repositories.project_idAFTER, signed-in user (:user_id; 20 is the Reporter access level):
SELECT "container_repositories".*
FROM "container_repositories"
INNER JOIN (
SELECT "projects"."id"
FROM "projects"
INNER JOIN "project_features" ON "project_features"."project_id" = "projects"."id"
WHERE "projects"."namespace_id" IN (<group and descendants via traversal_ids CTE>)
AND ("project_features"."container_registry_access_level" IS NULL
OR "project_features"."container_registry_access_level" IN (20, 30)
OR ("project_features"."container_registry_access_level" = 10
AND EXISTS (SELECT 1 FROM "project_authorizations"
WHERE "project_authorizations"."user_id" = :user_id
AND "project_authorizations"."access_level" >= 20
AND "project_authorizations"."project_id" = "project_features"."project_id")))
AND (EXISTS (SELECT 1 FROM "project_authorizations"
WHERE "project_authorizations"."user_id" = :user_id
AND "project_authorizations"."access_level" >= 20
AND "project_authorizations"."project_id" = "projects"."id")
OR "projects"."visibility_level" IN (10, 20))
) projects ON projects.id = container_repositories.project_idNote for the database reviewer: the SQL above is derived from the ActiveRecord scope chain; the author does not have access to postgres.ai or Database Lab. Please generate the actual SQL and query plans for a large public group such as
gitlab-org(id 9970) for the anonymous, signed-in non-member, and group member cases, and confirm the structure matches.
References
Closes #390311
Earlier attempt: !239908 (closed) (closed)
Screenshots or screen recordings
Screenshots to be added before marking ready.
| Before | After |
|---|---|
| Signed-out visitor on a public group: no Container Registry entry under Deploy, and the direct URL returns 404 | Entry is shown and the page lists images from the group's public projects |
How to set up and validate locally
- Enable the container registry in your GDK.
- Create a public group containing:
- a public project with a container image pushed to its registry,
- a private project with a container image,
- a public project whose container registry visibility is set to Only Project Members (Settings > General > Visibility, project features, permissions), with a container image.
- In a signed-out (incognito) session, visit the group:
- Before: Deploy > Container Registry is hidden and
/groups/<group>/-/container_registriesreturns 404. - After: the menu entry is visible and the page lists only the image from the first project. Images from the private project and from the members-only registry are not listed.
- Before: Deploy > Container Registry is hidden and
- Repeat step 3 signed in as a user who is not a member of the group.
- Repeat with a private group: non-members and signed-out users still get a 404.
- Optional:
GET /api/v4/groups/<id>/registry/repositorieswithout a token returns the same filtered list for a public group.
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.