- Notifications
You must be signed in to change notification settings - Fork1.1k
fix: allow group members to read group information#14200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
fix: allow group members to read group information#14200
Uh oh!
There was an error while loading.Please reload this page.
Conversation
github-actionsbot commentedAug 7, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
All contributors have signed the CLA ✍️ ✅ |
hugodutka commentedAug 7, 2024
I have read the CLA Document and I hereby sign the CLA |
Emyrk left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This has made me realize that groups andusers are pretty intertwined, but we should probably have aGroupMember resource that helps handle some of these permission edge cases.
they shouldn't be able to access detailed information about the group members
This is true, and maybe we should just handle this all the way at the database layer. I know originally we returneddatabase.User because the tablegroup_members has almost no information and is basically useless without more info. So we returned the user.
But we could do something like we do for templates.
Basically, all template queries use the viewtemplates_with_names to join in relevant username, icon, etc information.
This ends up being yourReducedGroup, and optionally you could do theTotalMemberCount there 🤷♂️.
SQLc config to make the template view the primary model:https://github.com/coder/coder/blob/main/coderd/database/sqlc.yaml#L77-L78
PR example of updating said view:https://github.com/coder/coder/pull/13858/files
P.S: This is touching many parts of the code with some weak documentation and examples. There is an effort to document some of the RBAC stuff here:#14065
This PR is solving some fundamental problems with groups that should be fixed, and really happy to see this PR coming in 👍
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
hugodutka commentedAug 8, 2024
Thank you for the review@Emyrk. Couple of questions: GroupMember resource
I considered this before and found some issues:
Do you think we should add the new resource given these problems? GroupMember DB ViewI like this idea. It would also simplify querying for members of the
If we add the |
Emyrk commentedAug 8, 2024
Custom roles is not yet released, it's in experimental. So there are currently no custom_roles in the wild. You are correct, adding related resources causes an issue when making roles. As granting 1 half of the relation doesn't really make much sense, and there is no obvious linkage between the two. But this is a flaw of our current RBAC, and it already exists in a few places. The extra resource is easier to understand then the ACL workaround you have in place imo. The good news of the custom_roles though is that we control the interface to making custom roles. Currently, it's basically a 1:1 with permissions, but we could implement some nicer UI and auto create these linkages. So the custom_roles endpoint or frontend could assume So tl;dr, this issue exists for other resources today, and I think we can solve this somewhere else.
Actually I do not think we do. When you insert/delete a member from the group, you are not accessing the group member, you are mutating the If you are reading/updating an individual member, then it makes sense to use A final note on all of this. If we model this as a As deployments grow, we could easily be fetching a few thousand users, and doing 1k authz checks isn't very efficient (they are in memory and can be ~1ms). So if we model things as we have been discussing, it's a fast follow up to optimize into a SQL query later. |
3930c6b toe07e1d9Comparehugodutka commentedAug 9, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@Emyrk, I adjusted the PR according to your feedback. List of changes:
Let me know if this matches what you had in mind. I'll fix the tests then. |
Emyrk left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This is overall looking good 👍
I created a test to mess around with some of this in an easier setting:
#14223
If I get a review of it intomain, it'll be easier to use for your testing. Right now I just copy pasted it in to check out the code paths.
| CREATE VIEW | ||
| group_members_expanded | ||
| AS | ||
| -- If the group is a user made group, then we need to check the group_members table. | ||
| -- If it is the "Everyone" group, then we need to check the organization_members table. | ||
| WITH all_membersAS ( | ||
| SELECT user_id, group_idFROM group_members | ||
| UNION | ||
| SELECT user_id, organization_idAS group_idFROM organization_members | ||
| ) | ||
| SELECT | ||
| users.idAS user_id, | ||
| users.emailAS user_email, | ||
| users.usernameAS user_username, | ||
| users.hashed_passwordAS user_hashed_password, | ||
| users.created_atAS user_created_at, | ||
| users.updated_atAS user_updated_at, | ||
| users.statusAS user_status, | ||
| users.rbac_rolesAS user_rbac_roles, | ||
| users.login_typeAS user_login_type, | ||
| users.avatar_urlAS user_avatar_url, | ||
| users.deletedAS user_deleted, | ||
| users.last_seen_atAS user_last_seen_at, | ||
| users.quiet_hours_scheduleAS user_quiet_hours_schedule, | ||
| users.theme_preferenceAS user_theme_preference, | ||
| users.nameAS user_name, | ||
| users.github_com_user_idAS user_github_com_user_id, | ||
| groups.organization_idAS organization_id, | ||
| groups.nameAS group_name, | ||
| all_members.group_idAS group_id | ||
| FROM | ||
| all_members | ||
| JOIN | ||
| usersONusers.id=all_members.user_id | ||
| JOIN | ||
| groupsONgroups.id=all_members.group_id | ||
| WHERE | ||
| users.deleted='false'; | ||
| COMMENT ON VIEW group_members_expanded IS'Joins group members with user information, organization ID, group name. Includes both regular group members and organization members (as part of the "Everyone" group).'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Uh oh!
There was an error while loading.Please reload this page.
coderd/database/dbmem/dbmem.go Outdated
| // getEveryoneGroupMembersNoLock fetches all the users in an organization. | ||
| func (q*FakeQuerier)getEveryoneGroupMembersNoLock(orgID uuid.UUID) []database.User { | ||
| func (q*FakeQuerier)getEveryoneGroupMembersNoLock(orgID uuid.UUID) []database.GroupMember { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
TheFakeQuerier has a data fieldgroupMembers []database.GroupMember. This should now begroupMembers []database.GroupMemberTable
Doing that will review some queries that need to be updated to include the joined information.
e07e1d9 to49297b2CompareEmyrk commentedAug 12, 2024
My unit test was merged. You will have to just fixup these test assertions: coder/coderd/database/dbauthz/groupsauth_test.go Lines 114 to 130 in8af8c77
Just wanted to get an actual breaking test from these changes 👍 |
- allow group members to see they are part of the group, but not see that information about other members- add a GetGroupMembersCountByGroupID SQL query, which allows group members to see members count without revealing other information about the members- expose a reducedGroupsByUserAndOrganization API endpoint
- fix type issues coming from replacing User with GroupMember in group member queries
… work on empty groups
…on the account page
6d469d5 to1080b29Comparehugodutka commentedAug 12, 2024
@Emyrk I think I updated all the tests. I rebased onto I updated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@hugodutka this is great 👍
I updated groupsauth_test.go to work with the new permission system. I saw that you assumed that if a user didn't have GroupMember read permission then GetGroupMembersByGroupID would return an error, but my implementation returns an empty list instead. I updated the test to match this behavior. Hope that's ok.
👍 That is fine
My only blocking request is thedbgen change mentioned in one of my comments. If you change that, the rest LGTM and I'll merge it in. Appreciate the contribution 🥳 .
Just leaving notes for myself, I'll open some issues for some follow ups after this:
- Reduce the number of fields returned in
group_members_expanded. Requires plumbing through the apis and UI. An excess number of fields is currently returned.- Ideally we can replace
codersdk.ReducedUserincodersdk.Groupwith thisdatabase.GroupMembertype?
- Ideally we can replace
/groupsapi endpoint exists in/{organization}. Given#14213, we should reconsider a single/groupsendpoint with filters, or add additional endpoints for fetching groups.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
| func (gmGroupMember)RBACObject() rbac.Object { | ||
| returnrbac.ResourceGroupMember.WithID(gm.UserID).InOrg(gm.OrganizationID).WithOwner(gm.UserID.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
👍
| // Subjects to user | ||
| memberMe:=authSubject{Name:"member_me",Actor: rbac.Subject{ID:currentUser.String(),Roles: rbac.RoleIdentifiers{rbac.RoleMember()}}} | ||
| orgMemberMe:=authSubject{Name:"org_member_me",Actor: rbac.Subject{ID:currentUser.String(),Roles: rbac.RoleIdentifiers{rbac.RoleMember(),rbac.ScopedRoleOrgMember(orgID)}}} | ||
| groupMemberMe:=authSubject{Name:"group_member_me",Actor: rbac.Subject{ID:currentUser.String(),Roles: rbac.RoleIdentifiers{rbac.RoleMember(),rbac.ScopedRoleOrgMember(orgID)},Groups: []string{groupID.String()}}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This is great 👍
hugodutka commentedAug 13, 2024
@Emyrk I made the changes you requested. Thank you for all the reviews! Contributing this PR was fun. |
Emyrk left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
👍 Approved! I'll merge it in. The weekly-docs CI job is failing because docker changed a link 😢

Fixes#13988. See the issue for background information.
This PR:
/organizations/{organization}/users/{user}/reduced-groupsthat returns an array ofReducedGroupobjects. These objects contain basic information about the group and the total member count./settings/account) use this endpoint to display information about groupsI asked questions about permission scope in the issue, but didn't get an answer in time, so I assumed that by default:
Let me know if these assumptions are correct.
This is my first PR to Coder, and I'm not that familiar with the codebase, so I'm very much open to feedback and suggestions. :)