Add GraphQL mutation to remove an organization user

What does this MR do and why?

Organization owners and admins could update an organization user's access level, but there was no way to delete a user from an organization. This adds the OrganizationUserDelete GraphQL mutation, backed by Organizations::OrganizationUsers::DestroyService.

The mutation deletes a single organization user identified by its global ID.

Key points:

  • Permission: the existing :remove_user policy ability is renamed to :delete_organization_user (as suggested in the issue). It requires update_organization — org owners/admins or instance admins only.
  • Last owner protection: the delete_organization_user ability denies the last organization owner, so through GraphQL authorization fails before the service runs and the caller gets a top-level access error rather than a mutation error. The service also carries its own last-owner message with reason: :last_owner for any future non-GraphQL caller.
  • Multiple organizations: a user cannot be deleted from their only organization. The model's before_destroy callback previously added an error but did not halt the delete; it now throw :aborts so the record is genuinely preserved.
  • Home organization guard: the service refuses to delete a user from their home organization (the one users.organization_id points at). Otherwise that column would keep pointing at an organization the user is no longer a member of, and User#update_home_organization_user would re-add them the next time the user's admin flag changed. The error carries reason: :home_organization.

Known limitation

Deletion is not durable for a user who still holds group or project memberships inside the organization. Member#create_organization_user_record recreates the organization_users row whenever a non-pending membership in that organization is saved. Cleaning up those memberships is out of scope for this MR and is tracked in #613955

References

Screenshots or screen recordings

Backend-only change; no UI.

How to set up and validate locally

  1. Sign in as an instance admin or an owner of the target organization.

  2. Ensure the target user belongs to at least two organizations, is not the last owner of the organization you are deleting them from, and that organization is not their home organization.

  3. Open GraphiQL at http://gdk.test:3000/-/graphql-explorer and run:

    mutation {
      organizationUserDelete(input: {
        id: "gid://gitlab/Organizations::OrganizationUser/1"
      }) {
        organizationUser {
          id
        }
        errors
      }
    }
  4. Confirm the organization user is deleted and the errors array is empty.

  5. Re-run against the last owner of an organization and confirm a top-level access error.

  6. Re-run against a user who belongs to only one organization and confirm the error A user must associate with at least one organization.

  7. Re-run against a user whose organization_id is the organization you are deleting them from and confirm the error You cannot delete a user from their home organization.

  8. Sign in as a non-owner member and confirm a top-level access error.

Tests:

bundle exec rspec \
  spec/services/organizations/organization_users/destroy_service_spec.rb \
  spec/policies/organizations/organization_user_policy_spec.rb \
  spec/requests/api/graphql/mutations/organizations/organization_users/delete_spec.rb

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.

Edited by Shane Maglangit

Merge request reports

Loading
Loading