Prevent org membership removal with existing group/project memberships

What does this MR do and why?

Prevent organization membership removal when the user has group/project membership in the organization.

References

Screenshot

image

How to set up and validate locally

  1. Open a Rails console:

    rails console
  2. Create an organization with an owner, and a second organization user. The second user is also added to a second organization, because the service otherwise blocks removal with "A user must associate with at least one organization":

    organization = FactoryBot.create(:organization)
    owner = FactoryBot.create(:organization_owner, :without_common_organization, organization: organization).user
    
    org_user = FactoryBot.create(:organization_user, :without_common_organization, organization: organization)
    member_user = org_user.user
    FactoryBot.create(:organization_user, organization: FactoryBot.create(:organization), user: member_user)
  3. Create a group in that organization and add the second user as a developer (the membership within the org), then print the GlobalID of the organization user for the mutation:

    group = FactoryBot.create(:group, organization: organization)
    group.add_developer(member_user)
    
    org_user.to_global_id.to_s # => "gid://gitlab/Organizations::OrganizationUser/<id>"
  4. Sign in as the owner, open GraphiQL at http://gdk.test:3000/-/graphql-explorer, and run the mutation with the GlobalID from the previous step. Confirm it returns the :has_memberships error and does not delete the record:

    mutation {
      organizationUserDelete(input: { id: "gid://gitlab/Organizations::OrganizationUser/<id>" }) {
        organizationUser {
          id
        }
        errors
      }
    }

    Expected response — organizationUser is null and errors contains the message:

    {
      "data": {
        "organizationUserDelete": {
          "organizationUser": null,
          "errors": [
            "You cannot remove a user from an organization while they are a member of groups or projects in the organization"
          ]
        }
      }
    }
  5. Confirm the organization_users row is retained (in the Rails console):

    Organizations::OrganizationUser.exists?(org_user.id) # => true
  6. Contrast: remove the group membership, then re-run the same mutation in GraphiQL and confirm it now succeeds (errors is empty and organizationUser is returned). The removal succeeds because the user still belongs to the second organization created earlier:

    group.members.find_by(user: member_user).destroy
    {
      "data": {
        "organizationUserDelete": {
          "organizationUser": {
            "id": "gid://gitlab/Organizations::OrganizationUser/<id>"
          },
          "errors": []
        }
      }
    }

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.

Related to #614050 (closed)

Edited by Shane Maglangit

Merge request reports

Loading
Loading