Fix approval visible groups detection
What does this MR do and why?
Contributes to #255981 (closed)
Problem
- User has access to Group A
- Group B is a subgroup of the Group A
- Group B is a approval group
User with inherited permissions from Group A cannot see approvers from approval Group B.
Solution
Apply subgroup permissions for approval rules check
Screenshots or screen recordings
Before
[
{
"id": 3,
"name": "Approval",
"rule_type": "regular",
"eligible_approvers": [], # <- empty list
"approvals_required": 1,
"users": [],
"groups": [],
"contains_hidden_groups": true, # <- has hidden groups
"protected_branches": []
}
]
After
[
{
"id": 3,
"name": "Approval",
"rule_type": "regular",
"eligible_approvers": [ # <- shows approvers
{
"id": 1,
"username": "root",
"name": "Root",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "http://127.0.0.1:3000/root"
},
{
"id": 46,
"username": "user2",
"name": "Sidney Jones2",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/2c75f98f3ea6cd6ee75cfbdfbb031388?s=80&d=identicon",
"web_url": "http://127.0.0.1:3000/user2"
}
],
"approvals_required": 1,
"users": [],
"groups": [
{
"id": 213,
"web_url": "http://127.0.0.1:3000/groups/group-a/approval",
"name": "approval",
"path": "approval",
"description": "",
"visibility": "private",
"share_with_group_lock": false,
"require_two_factor_authentication": false,
"two_factor_grace_period": 48,
"project_creation_level": "developer",
"auto_devops_enabled": null,
"subgroup_creation_level": "maintainer",
"emails_disabled": null,
"mentions_disabled": null,
"lfs_enabled": true,
"default_branch_protection": 2,
"avatar_url": null,
"request_access_enabled": true,
"full_name": "Group A / approval",
"full_path": "group-a/approval",
"created_at": "2022-06-29T15:14:16.930Z",
"parent_id": 130,
"ldap_cn": null,
"ldap_access": null,
"marked_for_deletion_on": null
}
],
"contains_hidden_groups": false, # <- no hidden groups
"protected_branches": []
}
]
Database
Before
Click to expand
SELECT
"namespaces".*
FROM
"namespaces"
INNER JOIN "approval_project_rules_groups" ON "namespaces"."id" = "approval_project_rules_groups"."group_id"
WHERE
"namespaces"."type" = 'Group'
AND "approval_project_rules_groups"."approval_project_rule_id" = 3
AND ("namespaces"."visibility_level" IN (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/10891/commands/39060
After
I rely on group preload logic introduced here - !73121 (merged)
Click to expand
SELECT
"namespaces".*
FROM
"namespaces"
INNER JOIN "approval_project_rules_groups" ON "namespaces"."id" = "approval_project_rules_groups"."group_id"
WHERE
"namespaces"."type" = 'Group'
AND "approval_project_rules_groups"."approval_project_rule_id" = 72703
https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/10891/commands/39061
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" = 9970) AS root_query ON root_query.root_id = namespaces.id
https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/10891/commands/39063
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" = 9970) 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/10891/commands/39062
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}'))))) 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/10891/commands/39065
How to set up and validate locally
- Enable feature flag
Feature.enable(:subgroups_approval_rules)
- Login as an admin
- Create "Group A"
- Create "Group B" as a subgroup of "Group A" ( Group A / Group B )
- Create "Project" as a project of "Group A" ( Group A / Project )
- Add "User 1" with Developer permissions to "Group A" (http://127.0.0.1:3000/groups/group-a/-/group_members)
- Add "Group B" with Developer permissions as a member group of "Project" ( http://127.0.0.1:3000/group-a/project/-/project_members?tab=groups )
- Create approval rule and select "Group B" as an approval (http://127.0.0.1:3000/group-a/project/edit)
- Login as a "User 1"
- You should have access to both groups and the project
- Visit http://127.0.0.1:3000/api/v4/projects/group-a%2Fproject/approval_rules
- You should see an non-empty
eligible_approvals
array in the response
Or follow instructions from here
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.