mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: allow group members to read group information (#14200)
* - allow group members to read basic Group info - 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 - add the group_members_expanded db view - rewrite group member queries to use the group_members_expanded view - add the RBAC ResourceGroupMember and add it to relevant roles - rewrite GetGroupMembersByGroupID permission checks - make the GroupMember type contain all user fields - fix type issues coming from replacing User with GroupMember in group member queries - add the MemberTotalCount field to codersdk.Group - display `group.total_member_count` instead of `group.members.length` on the account page
This commit is contained in:
@@ -1322,7 +1322,7 @@ func (q *sqlQuerier) DeleteGroupMemberFromGroup(ctx context.Context, arg DeleteG
|
||||
}
|
||||
|
||||
const getGroupMembers = `-- name: GetGroupMembers :many
|
||||
SELECT user_id, group_id FROM group_members
|
||||
SELECT user_id, user_email, user_username, user_hashed_password, user_created_at, user_updated_at, user_status, user_rbac_roles, user_login_type, user_avatar_url, user_deleted, user_last_seen_at, user_quiet_hours_schedule, user_theme_preference, user_name, user_github_com_user_id, organization_id, group_name, group_id FROM group_members_expanded
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetGroupMembers(ctx context.Context) ([]GroupMember, error) {
|
||||
@@ -1334,7 +1334,27 @@ func (q *sqlQuerier) GetGroupMembers(ctx context.Context) ([]GroupMember, error)
|
||||
var items []GroupMember
|
||||
for rows.Next() {
|
||||
var i GroupMember
|
||||
if err := rows.Scan(&i.UserID, &i.GroupID); err != nil {
|
||||
if err := rows.Scan(
|
||||
&i.UserID,
|
||||
&i.UserEmail,
|
||||
&i.UserUsername,
|
||||
&i.UserHashedPassword,
|
||||
&i.UserCreatedAt,
|
||||
&i.UserUpdatedAt,
|
||||
&i.UserStatus,
|
||||
pq.Array(&i.UserRbacRoles),
|
||||
&i.UserLoginType,
|
||||
&i.UserAvatarUrl,
|
||||
&i.UserDeleted,
|
||||
&i.UserLastSeenAt,
|
||||
&i.UserQuietHoursSchedule,
|
||||
&i.UserThemePreference,
|
||||
&i.UserName,
|
||||
&i.UserGithubComUserID,
|
||||
&i.OrganizationID,
|
||||
&i.GroupName,
|
||||
&i.GroupID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
@@ -1349,57 +1369,38 @@ func (q *sqlQuerier) GetGroupMembers(ctx context.Context) ([]GroupMember, error)
|
||||
}
|
||||
|
||||
const getGroupMembersByGroupID = `-- name: GetGroupMembersByGroupID :many
|
||||
SELECT
|
||||
users.id, users.email, users.username, users.hashed_password, users.created_at, users.updated_at, users.status, users.rbac_roles, users.login_type, users.avatar_url, users.deleted, users.last_seen_at, users.quiet_hours_schedule, users.theme_preference, users.name, users.github_com_user_id
|
||||
FROM
|
||||
users
|
||||
LEFT JOIN
|
||||
group_members
|
||||
ON
|
||||
group_members.user_id = users.id AND
|
||||
group_members.group_id = $1
|
||||
LEFT JOIN
|
||||
organization_members
|
||||
ON
|
||||
organization_members.user_id = users.id AND
|
||||
organization_members.organization_id = $1
|
||||
WHERE
|
||||
-- In either case, the group_id will only match an org or a group.
|
||||
(group_members.group_id = $1
|
||||
OR
|
||||
organization_members.organization_id = $1)
|
||||
AND
|
||||
users.deleted = 'false'
|
||||
SELECT user_id, user_email, user_username, user_hashed_password, user_created_at, user_updated_at, user_status, user_rbac_roles, user_login_type, user_avatar_url, user_deleted, user_last_seen_at, user_quiet_hours_schedule, user_theme_preference, user_name, user_github_com_user_id, organization_id, group_name, group_id FROM group_members_expanded WHERE group_id = $1
|
||||
`
|
||||
|
||||
// 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.
|
||||
func (q *sqlQuerier) GetGroupMembersByGroupID(ctx context.Context, groupID uuid.UUID) ([]User, error) {
|
||||
func (q *sqlQuerier) GetGroupMembersByGroupID(ctx context.Context, groupID uuid.UUID) ([]GroupMember, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getGroupMembersByGroupID, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []User
|
||||
var items []GroupMember
|
||||
for rows.Next() {
|
||||
var i User
|
||||
var i GroupMember
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Email,
|
||||
&i.Username,
|
||||
&i.HashedPassword,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Status,
|
||||
&i.RBACRoles,
|
||||
&i.LoginType,
|
||||
&i.AvatarURL,
|
||||
&i.Deleted,
|
||||
&i.LastSeenAt,
|
||||
&i.QuietHoursSchedule,
|
||||
&i.ThemePreference,
|
||||
&i.Name,
|
||||
&i.GithubComUserID,
|
||||
&i.UserID,
|
||||
&i.UserEmail,
|
||||
&i.UserUsername,
|
||||
&i.UserHashedPassword,
|
||||
&i.UserCreatedAt,
|
||||
&i.UserUpdatedAt,
|
||||
&i.UserStatus,
|
||||
pq.Array(&i.UserRbacRoles),
|
||||
&i.UserLoginType,
|
||||
&i.UserAvatarUrl,
|
||||
&i.UserDeleted,
|
||||
&i.UserLastSeenAt,
|
||||
&i.UserQuietHoursSchedule,
|
||||
&i.UserThemePreference,
|
||||
&i.UserName,
|
||||
&i.UserGithubComUserID,
|
||||
&i.OrganizationID,
|
||||
&i.GroupName,
|
||||
&i.GroupID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1414,6 +1415,20 @@ func (q *sqlQuerier) GetGroupMembersByGroupID(ctx context.Context, groupID uuid.
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getGroupMembersCountByGroupID = `-- name: GetGroupMembersCountByGroupID :one
|
||||
SELECT COUNT(*) FROM group_members_expanded WHERE group_id = $1
|
||||
`
|
||||
|
||||
// Returns the total count of members in a group. Shows the total
|
||||
// count even if the caller does not have read access to ResourceGroupMember.
|
||||
// They only need ResourceGroup read access.
|
||||
func (q *sqlQuerier) GetGroupMembersCountByGroupID(ctx context.Context, groupID uuid.UUID) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, getGroupMembersCountByGroupID, groupID)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const insertGroupMember = `-- name: InsertGroupMember :exec
|
||||
INSERT INTO
|
||||
group_members (user_id, group_id)
|
||||
@@ -1585,24 +1600,17 @@ SELECT
|
||||
groups.id, groups.name, groups.organization_id, groups.avatar_url, groups.quota_allowance, groups.display_name, groups.source
|
||||
FROM
|
||||
groups
|
||||
-- If the group is a user made group, then we need to check the group_members table.
|
||||
LEFT JOIN
|
||||
group_members
|
||||
ON
|
||||
group_members.group_id = groups.id AND
|
||||
group_members.user_id = $1
|
||||
-- If it is the "Everyone" group, then we need to check the organization_members table.
|
||||
LEFT JOIN
|
||||
organization_members
|
||||
ON
|
||||
organization_members.organization_id = groups.id AND
|
||||
organization_members.user_id = $1
|
||||
WHERE
|
||||
-- In either case, the group_id will only match an org or a group.
|
||||
(group_members.user_id = $1 OR organization_members.user_id = $1)
|
||||
AND
|
||||
-- Ensure the group or organization is the specified organization.
|
||||
groups.organization_id = $2
|
||||
groups.id IN (
|
||||
SELECT
|
||||
group_id
|
||||
FROM
|
||||
group_members_expanded gme
|
||||
WHERE
|
||||
gme.user_id = $1
|
||||
AND
|
||||
gme.organization_id = $2
|
||||
)
|
||||
`
|
||||
|
||||
type GetGroupsByOrganizationAndUserIDParams struct {
|
||||
|
||||
Reference in New Issue
Block a user