Incorrect membership source attribution when users have multiple inheritance paths

Summary

When users have membership through multiple inheritance paths (direct membership, subgroup inheritance, and/or group invites), GitLab incorrectly displays the membership source. The system should always display the source of the user's highest permission level, but currently shows incorrect or incomplete information about where that permission originates.

This manifests in several ways:

  1. Subgroup invite scenario: When a subgroup is invited to a project and users are inherited through that subgroup, the source shows the top-level parent group instead of the invited subgroup
  2. Data integrity issue: In some cases, users who were never members of a namespace are shown as the source of membership
  3. Complex inheritance with role escalation: When users have both direct group membership and subgroup membership with higher roles, and the subgroup is invited to a project, the source displays the lower-permission path instead of the highest-permission path

Expected Behavior

Core principle: The membership source should always reflect where the user's displayed highest permission comes from.

When a user has multiple possible sources of access to a project:

  • Display the source that grants the highest role
  • Use the correct membership type ("Shared" vs "Inherited" vs "Direct") based on that highest-permission source
  • Show the most specific group/path in the inheritance chain

Membership type definitions:

  • Shared: Access comes from a group being invited to the group or project (group sharing)
  • Inherited: Access comes from being a member of a parent group/namespace in the hierarchy
  • Direct: User was directly added to the project

Steps to Reproduce

Scenario A: Subgroup invite with simple inheritance

  1. Create a user test and add them to the test group
  2. Ensure the test/banking subgroup inherits users from the test parent group
  3. Add the test/banking group to deploy/test/banking/banking (another top-level group's project) via group invite
  4. Navigate to the deploy/test/banking/banking project members page
  5. Observe the inheritance information for user test

Current Behavior: User test displays Source: Inherited from test (top-level parent group)

Expected Behavior: User test should display Source: Invited group test/banking with membership type "Shared" (because the access comes from the subgroup being invited to the project)

Scenario B: Complex inheritance with role escalation

Screenshot_2024-10-02_at_11.27.21_AM

  1. Create a top-level group "Group 1" (with path group-1)
  2. Create a subgroup within "Group 1" (group-1/subgroup)
  3. Create a project within "Group 1" (group-1/target-project)
  4. Invite a user (example-user) to "Group 1" with the Guest role
  5. Invite example-user to group-1/subgroup with the Developer role
  6. Invite group-1/subgroup to group-1/target-project with Developer permissions
  7. View the group-1/target-project > Manage > Members page and check the "Source" for example-user

Current Behavior: example-user shows Source: Inherited from Group 1 with Guest role

Expected Behavior: example-user should show Source: Invited group group-1/subgroup with Developer role and membership type "Shared" (because the highest permission comes from the subgroup being invited to the project, not the inherited Guest role from the parent)

Example Project: https://gitlab.com/membership-type-bug/target-project

Scenario C: Incorrect non-member attribution

Current Behavior: Users who aren't and have never been members of a namespace are shown as the source of membership

Expected Behavior: Only actual members in the inheritance chain should be shown as sources

Note: This scenario is intermittent and affects certain namespaces - full reproduction steps not yet identified.

Current vs Expected Comparison

Scenario Current Behavior Expected Behavior Expected Membership Type
User in parent group → subgroup invited to project Shows "Inherited from parent-group" Shows "Invited group subgroup" Shared
User: Parent (Guest) + Subgroup (Developer) → subgroup invited to project Shows "Inherited from parent-group" (Guest) Shows "Invited group subgroup" (Developer) Shared
Non-member shown as source Shows incorrect user as source Shows actual source in permission inheritance chain [Depends on actual source]

Impact

  • Permission auditing is unreliable: Users cannot accurately determine who has access and why
  • Security confusion: May lead to misunderstandings about actual access paths and permission levels
  • Troubleshooting difficulty: Makes debugging permission issues significantly harder
  • User trust: Violates principle of least surprise for access management
  • Compliance risk: Incorrect source attribution could cause issues in security audits

Technical Investigation Needed

  • How does the system currently determine which source to display when multiple paths exist?
  • Is there logic to compare permission levels across different sources?
  • Are there edge cases where the "highest permission source" might be ambiguous?
  • What happens when permissions are equal from multiple sources?
Edited by Christina Lohr