Fix subgroup support for approval rules
What does this MR do and why?
- Contributes to #371106 (closed)
- Follow-up for !91598 (merged)
Problem
We had a problem with inherited permissions for subgroups. Users couldn't see appovers for groups if they don't have a direct membership in them. It was fixed in scope of !91598 (merged).
However, there is a similar invalid check that filters subgroups with inherited permissions out.
Solution
public_or_visible_to_user method doesn't correctly support subgroup
permissions. Instead we can verify if user has an access to the group.
Database
Before
Click to expand
SELECT
"namespaces"."id"
FROM
"namespaces"
WHERE
"namespaces"."type" = 'Group'
AND "namespaces"."id" IN (9970, 4955423)
AND ("namespaces"."visibility_level" IN (0, 10, 20)
OR EXISTS (
SELECT
1
FROM (
SELECT
"namespaces".*
FROM (( WITH "direct_groups" AS MATERIALIZED (
SELECT
"namespaces".*
FROM ((
SELECT
"namespaces".*
FROM
"namespaces"
INNER JOIN "members" ON "namespaces"."id" = "members"."source_id"
WHERE
"members"."type" = 'GroupMember'
AND "members"."source_type" = 'Namespace'
AND "namespaces"."type" = 'Group'
AND "members"."user_id" = 421631
AND "members"."requested_at" IS NULL
AND (
access_level >= 10
)
)
UNION (
SELECT
"namespaces".*
FROM
"projects"
INNER JOIN "project_authorizations" ON "projects"."id" = "project_authorizations"."project_id"
INNER JOIN "namespaces" ON "namespaces"."id" = "projects"."namespace_id"
WHERE
"project_authorizations"."user_id" = 421631
)
) namespaces
WHERE
"namespaces"."type" = 'Group'
)
SELECT
"namespaces".*
FROM ((
SELECT
"namespaces".*
FROM
"direct_groups" "namespaces"
WHERE
"namespaces"."type" = 'Group')
UNION (
SELECT
"namespaces".*
FROM
"namespaces"
INNER JOIN "group_group_links" ON "group_group_links"."shared_group_id" = "namespaces"."id"
WHERE
"namespaces"."type" = 'Group'
AND "group_group_links"."shared_with_group_id" IN (
SELECT
"namespaces"."id"
FROM
"direct_groups" "namespaces"
WHERE
"namespaces"."type" = 'Group'))) namespaces
WHERE
"namespaces"."type" = 'Group')
UNION (
SELECT
"namespaces".*
FROM
"namespaces"
INNER JOIN "members" ON "namespaces"."id" = "members"."source_id"
WHERE
"members"."type" = 'GroupMember'
AND "members"."source_type" = 'Namespace'
AND "namespaces"."type" = 'Group'
AND "members"."user_id" = 421631
AND "members"."access_level" = 5)) namespaces
WHERE
"namespaces"."type" = 'Group') authorized
WHERE
authorized. "id" = "namespaces"."id"))
https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/11789/commands/41802
After
I rely on group preload logic introduced here - !73121 (merged)
Click to expand
SELECT "namespaces".* FROM "namespaces" WHERE "namespaces"."type" = 'Group' AND "namespaces"."id" IN (9970, 4955423)
https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/11789/commands/41804
Click to expand
SELECT
namespaces.*,
root_query.id AS source_id
FROM
"namespaces"
INNER JOIN (
SELECT
id,
traversal_ids[1] AS root_id
FROM
"namespaces"
WHERE
"namespaces"."type" = 'Group'
AND "namespaces"."id" IN (9970, 4955423)) AS root_query ON root_query.root_id = namespaces.id
https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/11789/commands/41806
Click to expand
SELECT
MAX("members"."access_level") AS maximum_access_level,
"hierarchy"."id" AS hierarchy_id
FROM
"members"
LEFT OUTER JOIN "users" ON "users"."id" = "members"."user_id"
INNER JOIN (
SELECT
id,
unnest(traversal_ids) AS traversal_id
FROM
"namespaces"
WHERE
"namespaces"."id" IN (9970, 4955423)) AS hierarchy ON members.source_id = hierarchy.traversal_id
WHERE
"members"."type" = 'GroupMember'
AND "members"."source_type" = 'Namespace'
AND "users"."state" = 'active'
AND "members"."state" = 0
AND "members"."requested_at" IS NULL
AND "members"."invite_token" IS NULL
AND (members.access_level > 5)
AND "members"."user_id" = 421631
GROUP BY
"hierarchy"."id"
https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/11789/commands/41807
Click to expand
SELECT
1 AS one
FROM ((
SELECT
"projects".*
FROM
"projects"
WHERE
"projects"."namespace_id" IN (
SELECT
"namespaces"."id"
FROM
"namespaces"
WHERE
"namespaces"."type" = 'Group'
AND (traversal_ids @> ('{9970, 4955423}'))))) projects
WHERE (EXISTS (
SELECT
1
FROM
"project_authorizations"
WHERE
"project_authorizations"."user_id" = 421631
AND (project_authorizations.project_id = projects.id))
OR projects.visibility_level IN (10, 20))
AND "projects"."hidden" = FALSE
LIMIT 1
https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/11789/commands/41818
Screenshots or screen recordings
| Before | After |
|---|---|
![]() |
![]() |
How to set up and validate locally
- Check the great walk-through video: #371106 (comment 1083294024)
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.

