-
Notifications
You must be signed in to change notification settings - Fork 894
feat: add a paginated organization members endpoint #16835
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
Changes from 1 commit
cea4d3c
e97c9db
34003dc
9edb088
ef40967
6aaddaf
a724f48
4925592
902538c
06ff95c
560305d
76a4cbf
737ac9f
8910df3
0e579a2
73e356c
24332f4
7af0b60
cdc0322
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -985,6 +985,22 @@ func (s *MethodTestSuite) TestOrganization() { | |
mem, policy.ActionRead, | ||
) | ||
})) | ||
s.Run("PaginatedOrganizationMembers", s.Subtest(func(db database.Store, check *expects) { | ||
o := dbgen.Organization(s.T(), db, database.Organization{}) | ||
u := dbgen.User(s.T(), db, database.User{}) | ||
mem := dbgen.OrganizationMember(s.T(), db, database.OrganizationMember{ | ||
OrganizationID: o.ID, | ||
UserID: u.ID, | ||
Roles: []string{rbac.RoleOrgAdmin()}, | ||
}) | ||
|
||
check.Args(database.PaginatedOrganizationMembersParams{ | ||
OrganizationID: uuid.UUID{}, | ||
LimitOpt: 1, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this different check means we also don't need this field any more There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Accidentally pushed some test code in the test so not sure which one you were looking at but that's needed. |
||
}).Asserts( | ||
mem, policy.ActionRead, | ||
) | ||
})) | ||
s.Run("UpdateMemberRoles", s.Subtest(func(db database.Store, check *expects) { | ||
o := dbgen.Organization(s.T(), db, database.Organization{}) | ||
u := dbgen.User(s.T(), db, database.User{}) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9590,7 +9590,37 @@ func (q *FakeQuerier) PaginatedOrganizationMembers(ctx context.Context, arg data | |
return nil, err | ||
} | ||
|
||
panic("not implemented") | ||
q.mutex.RLock() | ||
defer q.mutex.RUnlock() | ||
|
||
tmp := make([]database.PaginatedOrganizationMembersRow, 0) | ||
|
||
skippedMembers := 0 | ||
for _, organizationMember := range q.organizationMembers { | ||
if arg.OrganizationID != uuid.Nil && organizationMember.OrganizationID != arg.OrganizationID { | ||
continue | ||
} | ||
|
||
if skippedMembers < int(arg.OffsetOpt) { | ||
skippedMembers += 1 | ||
continue | ||
} | ||
|
||
if len(tmp) >= int(arg.LimitOpt) { | ||
break | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this needs to check if the limitOpt is not 0. If it is zero, skip this check. To match the SQL |
||
|
||
user, _ := q.getUserByIDNoLock(organizationMember.UserID) | ||
tmp = append(tmp, database.PaginatedOrganizationMembersRow{ | ||
OrganizationMember: organizationMember, | ||
Username: user.Username, | ||
AvatarURL: user.AvatarURL, | ||
Name: user.Name, | ||
Email: user.Email, | ||
GlobalRoles: user.RBACRoles, | ||
}) | ||
} | ||
return tmp, nil | ||
} | ||
|
||
func (q *FakeQuerier) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(_ context.Context, templateID uuid.UUID) error { | ||
|
Uh oh!
There was an error while loading. Please reload this page.
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.
Unfortunately this does not work.
By default, a
member
can only read themselves, not other org members. So if you were paginating 10 users at a time, and the user was the 12th. Your query would return 10 rows, all would fail theauthz
check, and you would get an empty set as your return.You would need to
offset = 2
to find yourself, but that is not intuitive.Pagination is a general problem for authorization engines. The user is requesting "The next 10 elements I can access". If you cannot join the authz data into the query itself then:
These solutions kinda suck. So we have 2 methods in our codebase to solve this.
Quick and dirty
The first is the quick solution. We just restrict access to this endpoint to users who can read the entire table (with
organization_id = @org_id
). This functionally changes the query to only usable by a certain class of users. Regular members now get aForbidden
instead of a list of["me"]
. If we go this route, we just need to fix the page that currently displays just yourself in these cases.The correct solution
The more correct solution is to properly filter the list, so if there exists a user who can read a subset of members, this query still works.
A good example is workspaces
coder/coderd/database/dbauthz/dbauthz.go
Lines 3018 to 3024 in 902538c
The function
prepareSQLFilter
actually createsWHERE
clause expressions that return the truthy value of anauthorize()
call for the given user. So we apply the filter in SQL, and you can paginate through the rows you can access.There is some extra code required, but we have precedent to copy from if we go this route.
I think we should just go the quick and dirty route in this case. We don't have any roles that grant subset access to members. It is also strange to request organization members, and only return a subset.
For workspaces/templates this makes sense, as the resources have access control. However in this case, I think it makes more sense to return all or nothing. And we can update the UI to be in line with this decision.
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.
Thanks for the feedback/explanation! I agree that from a product perspective the quick and dirty route makes the most sense