Draft: Serve the Policy Store to groups through group abilities
What does this MR do and why?
The group Policy Store pages authorize through the group-level *_govern_policy abilities (which group Owners hold via the authz role definitions), but the data they render came from the organization-scoped REST endpoints, which only organization owners and instance administrators may call. On SaaS, group Owners are never owners of the default organization, so the page rendered and every list/create call returned 403 — the "Policy store is broken on staging" report.
This MR gives the group surface its own authorized data path, end to end:
- Group REST endpoints (
/groups/:id/security/policy_store, full CRUD) authorize the existing group abilities (boundary_type: :group; thegovern_policygranular token permissions gain the group boundary). The payload params are shared with the organization endpoints via Grape shared params, so the two surfaces cannot drift. - Services take a container — a
Groupor anOrganization— as the authorization subject. A group container stamps created policies with the group's namespace (govern_policies.namespace_id, which existed for this), lists only that namespace, and reports policies outside it as not found, indistinguishably from a missing id. The organization surface keeps managing every policy of the organization, including group-stamped ones. - The store port's
listgains anamespace_idfilter (gemgitlab-policy-store). - Evaluation is scoped:
Govern::Policy.evaluation_candidatesnow takesnamespace_idsand returns organization-wide policies plus only the named namespaces' policies. With none named, only organization-wide policies evaluate — a group-authored policy can never enforce outside its subtree by default. (No in-repo caller passes namespaces yet; the default is the safe one.) - The frontend picks its surface from an injected scope (
{ groupId }on group pages,{ organizationId }on organization pages), so the group pages call the endpoints their controllers' abilities actually match.
References
Follow-up to the staging triage that produced !252552 (closed) and !252544 (closed). No tracked issue yet.
Screenshots or screen recordings
No visual change: the group list/wizard pages render as before, but their data calls now succeed for group Owners. Before, a group Owner saw the page with the alert "You do not have permission to view the policies of this organization." and an empty table; after, the list loads and the wizard saves.
How to set up and validate locally
-
Enable the experiment and set up a group Owner who does not own the organization:
Feature.enable(:security_policies_v2) ApplicationSetting.current.update!(policy_store_experiment_enabled: true) group = Group.find_by_full_path('<your-root-group>') group.namespace_settings.update!(policy_store_experiment_enabled: true) user = User.find_by_username('<some-user>') group.add_owner(user) -
As that user, open
Secure > Policy storeon the group: the list loads without the permission alert (previously every data call returned 403). -
Create a policy through the wizard: the save succeeds, and
POST /api/v4/groups/:id/security/policy_storereturns201with"namespace_id"set to the group's id. -
Confirm the boundaries hold with the user's PAT:
# The group surface serves only the group's own policies: curl --header "PRIVATE-TOKEN: <user-token>" "http://gdk.test:3000/api/v4/groups/<group-id>/security/policy_store" # The organization surface still refuses a non-owner of the organization: curl --header "PRIVATE-TOKEN: <user-token>" "http://gdk.test:3000/api/v4/organizations/1/security/policy_store"The first returns
200with the group's policies; the second stays403. -
As an organization owner or admin,
GET /api/v4/organizations/1/security/policy_storelists both organization-wide and group-stamped policies.