Allow subgroup-provisioned SAs to create subgroups
Background
A recent change (!225090 (merged)) added include ::Authn::SubgroupProvisionedServiceAccountRestriction to EE::Organizations::OrganizationPolicy so that subgroup- and project-provisioned service accounts couldn't create top-level groups or spawn other service accounts. Both restrictions are legitimate and we want to keep them.
The bug
The problem is a subtle interaction with the API. POST /api/v4/groups always runs authorize! :create_group, organization because organization_id has a default: lambda — it's never nil. So the OrganizationPolicy's prevent :create_group rule fires before the API even gets to checking :create_subgroup. The net effect: subgroup-provisioned SAs started getting 403 Forbidden when creating subgroups under namespaces they were provisioned in — a case !225090 (merged) never meant to block.
Fix
The fix is a one-line removal of that include from EE::Organizations::OrganizationPolicy. The security properties from !225090 (merged) are preserved elsewhere:
GlobalPolicystill blocks these SAs from creating top-level groups.GroupPolicyandProjectPolicystill block them from creating other service accounts.- Only the unintended subgroup-creation block goes away.
Customer impact
This unblocks real workflows — for example, Terraform onboarding scripts where a subgroup-provisioned SA needs to create subgroups under its provisioning namespace. (Project-provisioned SAs are independently isolated to their origin project by Members::ServiceAccounts::EligibilityChecker and cannot reach a state where the bug is observable, so they're unaffected in practice.)
Tests
A new request spec in ee/spec/requests/api/groups_spec.rb covers a subgroup-provisioned SA. It verifies top-level group creation is still blocked (403), subgroup creation under the SA's provisioning namespace succeeds (201), and several scope-containment cases — unrelated namespace, ancestor of provisioning namespace, readable namespace without Maintainer access, cross-organization — still deny. Setup wires admin through add_owner so Members::ServiceAccounts::EligibilityChecker runs in before_all, matching production. Project-provisioned SAs aren't exercised here — eligibility blocks their invitation to any group, so they can't reach the state where the fix is observable.
How to verify locally
If you want to convince yourself in your own GDK: set up a subgroup-provisioned SA, then hit the API. The CI suite covers the broader matrix (scope containment, cross-org, etc.); this section is for a quick eyeball check that the fix actually flips the failing call. Swap http://gdk.test:3000 for :3443 if your GDK uses the HTTP router.
Setup
In rails console (assumes User.find(1) is your admin):
admin = User.find(1)
org = Organizations::Organization.default_organization
parent = Groups::CreateService.new(admin, name: 'parent', path: 'parent-grp', organization_id: org.id).execute.payload[:group]
subgroup = Groups::CreateService.new(admin, name: 'sub', path: 'sub-grp', parent_id: parent.id, organization_id: org.id).execute.payload[:group]
sa = Users::ServiceAccounts::CreateService.new(admin, organization_id: org.id).execute.payload[:user]
sa.user_detail.update!(provisioned_by_group_id: subgroup.id)
sa.update_column(:can_create_group, true)
subgroup.add_owner(sa)
puts "SUBGROUP_ID=#{subgroup.id}"
puts "TOKEN=#{PersonalAccessToken.create!(user: sa, scopes: [:api], name: 'verify', expires_at: 1.day.from_now).token}"Exercise the API
# 1. The fix in action: subgroup creation -> 201 on this branch, 403 on master.
curl --request POST --header "PRIVATE-TOKEN: <TOKEN>" \
--data "name=child&path=child&parent_id=<SUBGROUP_ID>" \
http://gdk.test:3000/api/v4/groups
# 2. Sanity: top-level group creation still denied -> 403 (GlobalPolicy enforces).
curl --request POST --header "PRIVATE-TOKEN: <TOKEN>" \
--data "name=toplevel&path=toplevel" \
http://gdk.test:3000/api/v4/groupsTo see the regression directly: check out master (or the commit before !235396 (merged)), repeat call 1, observe 403 Forbidden.
References
- Closes #599158
- Related to !225090 (merged), #540774, #540773
- Part of gitlab-org/-/work_items/21579