Grant the Organization Administrator role to owners created through Users::CreateService
What does this MR do and why?
Closes #630676 (closed). Replaces !257477 (merged), which was marked merged by mistake when its commit was pushed to the parent branch and then removed again.
Owners created through Users::CreateService never get the IAM organization_admin (Organization Administrator) tuple. Two paths write an owner membership without going through an organization-user service, so none of the existing sync hooks fire:
- The admin new-user form's "Organization administrator" option, which writes the membership through
Users::BuildService#assign_organization. - Users created with the admin flag, who get an owner row on their home organization from
Organizations::OrganizationUser.home_organization_access_level(user_is_admin: true).
This MR adds a hook to Users::CreateService#after_create_hook that enqueues Authz::Organizations::GrantOwnerRoleWorker for each owner membership of the new user. The creating admin is passed as actor, and the worker uses their token only when they currently own that organization; otherwise, which is the usual case for an instance admin creating a user elsewhere, the worker mints the token as the new owner themselves. The hook is gated by Authz::Organizations::OwnerRoleSync.enabled?, matching the other hooks in this series.
The hook does not try to distinguish why a row exists. That decision lives in the worker: per maintainer dlrussel's rule (!255373 (comment 3893548002)), the worker skips an owner row only when the user has the admin flag and the row is their home-organization membership. So an admin-flag user still enqueues a job, which the worker logs and skips, while a non-admin user created with "Organization administrator" gets the tuple granted.
The change is small: two files, about 80 lines including specs.
This is split out of !255373 (merged) at reviewer skundapur's suggestion, so the other owner-creation paths could merge first. That MR has merged, so this one is rebased on master and targets it directly.
Known gaps and follow-ups
- A new instance admin created with "Organization administrator" explicitly selected writes the same home-organization owner row the admin flag would, so the worker cannot tell them apart and skips both. This is unsupported until IAM has an instance admin subject and role.
Users::ServiceAccounts::CreateServicereusesUsers::CreateService, but service accounts get the default access level, so nothing is enqueued for them. No change needed there.- An owner saved in
blocked_pending_approval(EE applies the instance user cap to admin-created users) still gets the tuple by design, since the grant is separate from whether the account can sign in and the token exchange refuses non-active users. Two consequences are tracked elsewhere: when no owning actor is available the worker mints the write token as that pending user (#631235), and a rejected user is hard-deleted leaving the tuple behind, which belongs with role removal on deletion (#630816). - Owners promoted or demoted through
Users::UpdateService, and the home organization row rewritten when the admin flag changes, never sync the tuple. Tracked in #630819 (closed).
References
- Closes #630676 (closed)
- Parent MR (worker implementation, targeted by this MR): !255373 (merged)
- Maintainer discussion establishing the admin-flag skip rule: !255373 (comment 3893548002)
- Closed beta blocker: gitlab-org/ops/artifact-registry#1326 (closed)
- Follow-ups: #631235 (acting user when the fallback owner cannot sign in), #630819 (closed) (promotion and demotion)
- Original contract: #601665 (closed)
Screenshots or screen recordings
Not applicable, this is a backend-only change.
How to set up and validate locally
Configure IAM data access (iam_data_access_service.secret_file in gitlab.yml) on an EE instance with a Cloud Connector key.
- In the admin area, create a new user, select "Organization administrator", and save. Confirm exactly one
GrantOwnerRoleWorkerjob is enqueued, with the new user's id and the creating admin's id. - Create a user with the admin flag set. Confirm a job is still enqueued, but the worker logs that the owner row comes from the instance admin flag and skips granting, without calling out to IAM.
- Create a regular user with no admin flag and no "Organization administrator" option. Confirm nothing is enqueued.
RuboCop is clean on both files. RSpec was not run locally because the test database cluster for this checkout is down, so the MR pipeline verifies the specs.
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.