-
Notifications
You must be signed in to change notification settings - Fork 876
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 our terms of service and privacy 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
Conversation
All contributors have signed the CLA ✍️ ✅ |
I have read the CLA Document and I hereby sign the CLA |
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 and users
are pretty intertwined, but we should probably have a GroupMember
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 returned database.User
because the table group_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 view templates_with_names
to join in relevant username, icon, etc information.
This ends up being your ReducedGroup
, and optionally you could do the TotalMemberCount
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 👍
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 |
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
to
e07e1d9
Compare
@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. |
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 into main
, 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_members AS ( | ||
SELECT user_id, group_id FROM group_members | ||
UNION | ||
SELECT user_id, organization_id AS group_id FROM organization_members | ||
) | ||
SELECT | ||
users.id AS user_id, | ||
users.email AS user_email, | ||
users.username AS user_username, | ||
users.hashed_password AS user_hashed_password, | ||
users.created_at AS user_created_at, | ||
users.updated_at AS user_updated_at, | ||
users.status AS user_status, | ||
users.rbac_roles AS user_rbac_roles, | ||
users.login_type AS user_login_type, | ||
users.avatar_url AS user_avatar_url, | ||
users.deleted AS user_deleted, | ||
users.last_seen_at AS user_last_seen_at, | ||
users.quiet_hours_schedule AS user_quiet_hours_schedule, | ||
users.theme_preference AS user_theme_preference, | ||
users.name AS user_name, | ||
users.github_com_user_id AS user_github_com_user_id, | ||
groups.organization_id AS organization_id, | ||
groups.name AS group_name, | ||
all_members.group_id AS group_id | ||
FROM | ||
all_members | ||
JOIN | ||
users ON users.id = all_members.user_id | ||
JOIN | ||
groups ON groups.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.
coderd/database/dbmem/dbmem.go
Outdated
@@ -724,17 +724,40 @@ func (q *FakeQuerier) getOrganizationMemberNoLock(orgID uuid.UUID) []database.Or | |||
} | |||
|
|||
// 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.
The FakeQuerier
has a data field groupMembers []database.GroupMember
. This should now be groupMembers []database.GroupMemberTable
Doing that will review some queries that need to be updated to include the joined information.
e07e1d9
to
49297b2
Compare
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 in 8af8c77
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
to
1080b29
Compare
@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 the dbgen
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.ReducedUser
incodersdk.Group
with thisdatabase.GroupMember
type?
- Ideally we can replace
/groups
api endpoint exists in/{organization}
. Given Account settings page currently lists the current groups a user is in. Should either replace, or include which organizations #14213, we should reconsider a single/groups
endpoint with filters, or add additional endpoints for fetching groups.
func (gm GroupMember) RBACObject() rbac.Object { | ||
return rbac.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.
👍
@@ -112,6 +112,7 @@ func TestRolePermissions(t *testing.T) { | |||
// 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 👍
@Emyrk I made the changes you requested. Thank you for all the reviews! Contributing this PR was fun. |
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-groups
that returns an array ofReducedGroup
objects. 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. :)