feat: Implied 'member' roles for site and organization (#1917)

* feat: Member roles are implied and never exlpicitly added
* Rename "GetAllUserRoles" to "GetAuthorizationRoles"
* feat: Add migration to remove implied roles
* rename user auth role middleware
This commit is contained in:
Steven Masley 2022-06-01 09:07:50 -05:00 committed by GitHub
parent 2878346f19
commit cc87a0cf6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 131 additions and 115 deletions

View File

@ -13,12 +13,12 @@ import (
)
func AuthorizeFilter[O rbac.Objecter](api *API, r *http.Request, action rbac.Action, objects []O) []O {
roles := httpmw.UserRoles(r)
roles := httpmw.AuthorizationUserRoles(r)
return rbac.Filter(r.Context(), api.Authorizer, roles.ID.String(), roles.Roles, action, objects)
}
func (api *API) Authorize(rw http.ResponseWriter, r *http.Request, action rbac.Action, object rbac.Objecter) bool {
roles := httpmw.UserRoles(r)
roles := httpmw.AuthorizationUserRoles(r)
err := api.Authorizer.ByRoleName(r.Context(), roles.ID.String(), roles.Roles, action, object.RBACObject())
if err != nil {
httpapi.Write(rw, http.StatusForbidden, httpapi.Response{

View File

@ -281,7 +281,7 @@ func CreateAnotherUser(t *testing.T, client *codersdk.Client, organizationID uui
organizationID, err := uuid.Parse(orgID)
require.NoError(t, err, fmt.Sprintf("parse org id %q", orgID))
_, err = client.UpdateOrganizationMemberRoles(context.Background(), organizationID, user.ID.String(),
codersdk.UpdateRoles{Roles: append(roles, rbac.RoleOrgMember(organizationID))})
codersdk.UpdateRoles{Roles: roles})
require.NoError(t, err, "update org membership roles")
}
}

View File

@ -276,7 +276,7 @@ func (q *fakeQuerier) GetUsersByIDs(_ context.Context, ids []uuid.UUID) ([]datab
return users, nil
}
func (q *fakeQuerier) GetAllUserRoles(_ context.Context, userID uuid.UUID) (database.GetAllUserRolesRow, error) {
func (q *fakeQuerier) GetAuthorizationUserRoles(_ context.Context, userID uuid.UUID) (database.GetAuthorizationUserRolesRow, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
@ -286,6 +286,7 @@ func (q *fakeQuerier) GetAllUserRoles(_ context.Context, userID uuid.UUID) (data
if u.ID == userID {
u := u
roles = append(roles, u.RBACRoles...)
roles = append(roles, "member")
user = &u
break
}
@ -294,14 +295,15 @@ func (q *fakeQuerier) GetAllUserRoles(_ context.Context, userID uuid.UUID) (data
for _, mem := range q.organizationMembers {
if mem.UserID == userID {
roles = append(roles, mem.Roles...)
roles = append(roles, "organization-member:"+mem.OrganizationID.String())
}
}
if user == nil {
return database.GetAllUserRolesRow{}, sql.ErrNoRows
return database.GetAuthorizationUserRolesRow{}, sql.ErrNoRows
}
return database.GetAllUserRolesRow{
return database.GetAuthorizationUserRolesRow{
ID: userID,
Username: user.Username,
Status: user.Status,

View File

@ -0,0 +1,11 @@
--- Remove the now implied 'member' role.
UPDATE
users
SET
rbac_roles = array_append(rbac_roles, 'member');
--- Remove the now implied 'organization-member' role.
UPDATE
organization_members
SET
roles = array_append(roles, 'organization-member:'||organization_id::text);

View File

@ -0,0 +1,11 @@
--- Remove the now implied 'member' role.
UPDATE
users
SET
rbac_roles = array_remove(rbac_roles, 'member');
--- Remove the now implied 'organization-member' role.
UPDATE
organization_members
SET
roles = array_remove(roles, 'organization-member:'||organization_id::text);

View File

@ -22,10 +22,12 @@ type querier interface {
DeleteGitSSHKey(ctx context.Context, userID uuid.UUID) error
DeleteParameterValueByID(ctx context.Context, id uuid.UUID) error
GetAPIKeyByID(ctx context.Context, id string) (APIKey, error)
GetAllUserRoles(ctx context.Context, userID uuid.UUID) (GetAllUserRolesRow, error)
// GetAuditLogsBefore retrieves `limit` number of audit logs before the provided
// ID.
GetAuditLogsBefore(ctx context.Context, arg GetAuditLogsBeforeParams) ([]AuditLog, error)
// This function returns roles for authorization purposes. Implied member roles
// are included.
GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error)
GetFileByHash(ctx context.Context, hash string) (File, error)
GetGitSSHKey(ctx context.Context, userID uuid.UUID) (GitSSHKey, error)
GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error)

View File

@ -2088,12 +2088,18 @@ func (q *sqlQuerier) UpdateTemplateVersionDescriptionByJobID(ctx context.Context
return err
}
const getAllUserRoles = `-- name: GetAllUserRoles :one
const getAuthorizationUserRoles = `-- name: GetAuthorizationUserRoles :one
SELECT
-- username is returned just to help for logging purposes
-- status is used to enforce 'suspended' users, as all roles are ignored
-- when suspended.
id, username, status, array_cat(users.rbac_roles, organization_members.roles) :: text[] AS roles
-- username is returned just to help for logging purposes
-- status is used to enforce 'suspended' users, as all roles are ignored
-- when suspended.
id, username, status,
array_cat(
-- All users are members
array_append(users.rbac_roles, 'member'),
-- All org_members get the org-member role for their orgs
array_append(organization_members.roles, 'organization-member:'||organization_members.organization_id::text)) :: text[]
AS roles
FROM
users
LEFT JOIN organization_members
@ -2102,16 +2108,18 @@ WHERE
id = $1
`
type GetAllUserRolesRow struct {
type GetAuthorizationUserRolesRow struct {
ID uuid.UUID `db:"id" json:"id"`
Username string `db:"username" json:"username"`
Status UserStatus `db:"status" json:"status"`
Roles []string `db:"roles" json:"roles"`
}
func (q *sqlQuerier) GetAllUserRoles(ctx context.Context, userID uuid.UUID) (GetAllUserRolesRow, error) {
row := q.db.QueryRowContext(ctx, getAllUserRoles, userID)
var i GetAllUserRolesRow
// This function returns roles for authorization purposes. Implied member roles
// are included.
func (q *sqlQuerier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error) {
row := q.db.QueryRowContext(ctx, getAuthorizationUserRoles, userID)
var i GetAuthorizationUserRolesRow
err := row.Scan(
&i.ID,
&i.Username,

View File

@ -134,12 +134,20 @@ WHERE
id = $1 RETURNING *;
-- name: GetAllUserRoles :one
-- name: GetAuthorizationUserRoles :one
-- This function returns roles for authorization purposes. Implied member roles
-- are included.
SELECT
-- username is returned just to help for logging purposes
-- status is used to enforce 'suspended' users, as all roles are ignored
-- when suspended.
id, username, status, array_cat(users.rbac_roles, organization_members.roles) :: text[] AS roles
-- username is returned just to help for logging purposes
-- status is used to enforce 'suspended' users, as all roles are ignored
-- when suspended.
id, username, status,
array_cat(
-- All users are members
array_append(users.rbac_roles, 'member'),
-- All org_members get the org-member role for their orgs
array_append(organization_members.roles, 'organization-member:'||organization_members.organization_id::text)) :: text[]
AS roles
FROM
users
LEFT JOIN organization_members

View File

@ -31,6 +31,19 @@ func APIKey(r *http.Request) database.APIKey {
return apiKey
}
// User roles are the 'subject' field of Authorize()
type userRolesKey struct{}
// AuthorizationUserRoles returns the roles used for authorization.
// Comes from the ExtractAPIKey handler.
func AuthorizationUserRoles(r *http.Request) database.GetAuthorizationUserRolesRow {
apiKey, ok := r.Context().Value(userRolesKey{}).(database.GetAuthorizationUserRolesRow)
if !ok {
panic("developer error: user roles middleware not provided")
}
return apiKey
}
// OAuth2Configs is a collection of configurations for OAuth-based authentication.
// This should be extended to support other authentication types in the future.
type OAuth2Configs struct {
@ -178,7 +191,7 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs) func(http.Handler) h
// If the key is valid, we also fetch the user roles and status.
// The roles are used for RBAC authorize checks, and the status
// is to block 'suspended' users from accessing the platform.
roles, err := db.GetAllUserRoles(r.Context(), key.UserID)
roles, err := db.GetAuthorizationUserRoles(r.Context(), key.UserID)
if err != nil {
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
Message: "roles not found",

View File

@ -1,40 +0,0 @@
package httpmw
import (
"context"
"net/http"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/httpapi"
)
// User roles are the 'subject' field of Authorize()
type userRolesKey struct{}
// UserRoles returns the API key from the ExtractUserRoles handler.
func UserRoles(r *http.Request) database.GetAllUserRolesRow {
apiKey, ok := r.Context().Value(userRolesKey{}).(database.GetAllUserRolesRow)
if !ok {
panic("developer error: user roles middleware not provided")
}
return apiKey
}
// ExtractUserRoles requires authentication using a valid API key.
func ExtractUserRoles(db database.Store) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
apiKey := APIKey(r)
role, err := db.GetAllUserRoles(r.Context(), apiKey.UserID)
if err != nil {
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
Message: "roles not found",
})
return
}
ctx := context.WithValue(r.Context(), userRolesKey{}, role)
next.ServeHTTP(rw, r.WithContext(ctx))
})
}
}

View File

@ -31,23 +31,23 @@ func TestExtractUserRoles(t *testing.T) {
{
Name: "Member",
AddUser: func(db database.Store) (database.User, []string, string) {
roles := []string{rbac.RoleMember()}
roles := []string{}
user, token := addUser(t, db, roles...)
return user, roles, token
return user, append(roles, rbac.RoleMember()), token
},
},
{
Name: "Admin",
AddUser: func(db database.Store) (database.User, []string, string) {
roles := []string{rbac.RoleMember(), rbac.RoleAdmin()}
roles := []string{rbac.RoleAdmin()}
user, token := addUser(t, db, roles...)
return user, roles, token
return user, append(roles, rbac.RoleMember()), token
},
},
{
Name: "OrgMember",
AddUser: func(db database.Store) (database.User, []string, string) {
roles := []string{rbac.RoleMember()}
roles := []string{}
user, token := addUser(t, db, roles...)
org, err := db.InsertOrganization(context.Background(), database.InsertOrganizationParams{
ID: uuid.New(),
@ -58,7 +58,7 @@ func TestExtractUserRoles(t *testing.T) {
})
require.NoError(t, err)
orgRoles := []string{rbac.RoleOrgMember(org.ID)}
orgRoles := []string{}
_, err = db.InsertOrganizationMember(context.Background(), database.InsertOrganizationMemberParams{
OrganizationID: org.ID,
UserID: user.ID,
@ -67,7 +67,7 @@ func TestExtractUserRoles(t *testing.T) {
Roles: orgRoles,
})
require.NoError(t, err)
return user, append(roles, orgRoles...), token
return user, append(roles, append(orgRoles, rbac.RoleMember(), rbac.RoleOrgMember(org.ID))...), token
},
},
}
@ -86,7 +86,7 @@ func TestExtractUserRoles(t *testing.T) {
httpmw.ExtractAPIKey(db, &httpmw.OAuth2Configs{}),
)
rtr.Get("/", func(_ http.ResponseWriter, r *http.Request) {
roles := httpmw.UserRoles(r)
roles := httpmw.AuthorizationUserRoles(r)
require.ElementsMatch(t, user.ID, roles.ID)
require.ElementsMatch(t, expRoles, roles.Roles)
})

View File

@ -34,7 +34,9 @@ func (api *API) putMemberRoles(rw http.ResponseWriter, r *http.Request) {
return
}
added, removed := rbac.ChangeRoleSet(member.Roles, params.Roles)
// The org-member role is always implied.
impliedTypes := append(params.Roles, rbac.RoleOrgMember(organization.ID))
added, removed := rbac.ChangeRoleSet(member.Roles, impliedTypes)
for _, roleName := range added {
// Assigning a role requires the create permission.
if !api.Authorize(rw, r, rbac.ActionCreate, rbac.ResourceOrgRoleAssignment.WithID(roleName).InOrg(organization.ID)) {

View File

@ -73,13 +73,11 @@ func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) {
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
Roles: []string{
// Also assign member role incase they get demoted from admin
rbac.RoleOrgMember(organization.ID),
rbac.RoleOrgAdmin(organization.ID),
},
})
if err != nil {
return xerrors.Errorf("create organization member: %w", err)
return xerrors.Errorf("create organization admin: %w", err)
}
return nil
})

View File

@ -63,7 +63,7 @@ var (
member: func(_ string) Role {
return Role{
Name: member,
DisplayName: "Member",
DisplayName: "",
Site: permissions(map[Object][]Action{
// All users can read all other users and know they exist.
ResourceUser: {ActionRead},
@ -116,7 +116,7 @@ var (
orgMember: func(organizationID string) Role {
return Role{
Name: roleName(orgMember, organizationID),
DisplayName: "Organization Member",
DisplayName: "",
Org: map[string][]Permission{
organizationID: {
{

View File

@ -17,7 +17,9 @@ type Permission struct {
// Users of this package should instead **only** use the role names, and
// this package will expand the role names into their json payloads.
type Role struct {
Name string `json:"name"`
Name string `json:"name"`
// DisplayName is used for UI purposes. If the role has no display name,
// that means the UI should never display it.
DisplayName string `json:"display_name"`
Site []Permission `json:"site"`
// Org is a map of orgid to permissions. We represent orgid as a string.

View File

@ -45,7 +45,7 @@ func (api *API) checkPermissions(rw http.ResponseWriter, r *http.Request) {
}
// use the roles of the user specified, not the person making the request.
roles, err := api.Database.GetAllUserRoles(r.Context(), user.ID)
roles, err := api.Database.GetAuthorizationUserRoles(r.Context(), user.ID)
if err != nil {
httpapi.Forbidden(rw)
return
@ -91,6 +91,10 @@ func convertRole(role rbac.Role) codersdk.Role {
func convertRoles(roles []rbac.Role) []codersdk.Role {
converted := make([]codersdk.Role, 0, len(roles))
for _, role := range roles {
// Roles without display names should never be shown to the ui.
if role.DisplayName == "" {
continue
}
converted = append(converted, convertRole(role))
}
return converted

View File

@ -113,6 +113,8 @@ func TestListRoles(t *testing.T) {
require.NoError(t, err, "create org")
const forbidden = "forbidden"
siteRoles := convertRoles(rbac.RoleAdmin(), "auditor")
orgRoles := convertRoles(rbac.RoleOrgAdmin(admin.OrganizationID))
testCases := []struct {
Name string
@ -127,14 +129,14 @@ func TestListRoles(t *testing.T) {
x, err := member.ListSiteRoles(ctx)
return x, err
},
ExpectedRoles: convertRoles(rbac.SiteRoles()),
ExpectedRoles: siteRoles,
},
{
Name: "OrgMemberListOrg",
APICall: func() ([]codersdk.Role, error) {
return member.ListOrganizationRoles(ctx, admin.OrganizationID)
},
ExpectedRoles: convertRoles(rbac.OrganizationRoles(admin.OrganizationID)),
ExpectedRoles: orgRoles,
},
{
Name: "NonOrgMemberListOrg",
@ -149,14 +151,14 @@ func TestListRoles(t *testing.T) {
APICall: func() ([]codersdk.Role, error) {
return orgAdmin.ListSiteRoles(ctx)
},
ExpectedRoles: convertRoles(rbac.SiteRoles()),
ExpectedRoles: siteRoles,
},
{
Name: "OrgAdminListOrg",
APICall: func() ([]codersdk.Role, error) {
return orgAdmin.ListOrganizationRoles(ctx, admin.OrganizationID)
},
ExpectedRoles: convertRoles(rbac.OrganizationRoles(admin.OrganizationID)),
ExpectedRoles: orgRoles,
},
{
Name: "OrgAdminListOtherOrg",
@ -171,14 +173,14 @@ func TestListRoles(t *testing.T) {
APICall: func() ([]codersdk.Role, error) {
return client.ListSiteRoles(ctx)
},
ExpectedRoles: convertRoles(rbac.SiteRoles()),
ExpectedRoles: siteRoles,
},
{
Name: "AdminListOrg",
APICall: func() ([]codersdk.Role, error) {
return client.ListOrganizationRoles(ctx, admin.OrganizationID)
},
ExpectedRoles: convertRoles(rbac.OrganizationRoles(admin.OrganizationID)),
ExpectedRoles: orgRoles,
},
}
@ -200,17 +202,18 @@ func TestListRoles(t *testing.T) {
}
}
func convertRole(role rbac.Role) codersdk.Role {
func convertRole(roleName string) codersdk.Role {
role, _ := rbac.RoleByName(roleName)
return codersdk.Role{
DisplayName: role.DisplayName,
Name: role.Name,
}
}
func convertRoles(roles []rbac.Role) []codersdk.Role {
converted := make([]codersdk.Role, 0, len(roles))
for _, role := range roles {
converted = append(converted, convertRole(role))
func convertRoles(roleNames ...string) []codersdk.Role {
converted := make([]codersdk.Role, 0, len(roleNames))
for _, roleName := range roleNames {
converted = append(converted, convertRole(roleName))
}
return converted
}

View File

@ -88,7 +88,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
// and add some rbac bypass when calling api functions this way??
// Add the admin role to this first user.
_, err = api.Database.UpdateUserRoles(r.Context(), database.UpdateUserRolesParams{
GrantedRoles: []string{rbac.RoleAdmin(), rbac.RoleMember()},
GrantedRoles: []string{rbac.RoleAdmin()},
ID: user.ID,
})
if err != nil {
@ -473,7 +473,7 @@ func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) {
func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) {
// User is the user to modify.
user := httpmw.UserParam(r)
roles := httpmw.UserRoles(r)
roles := httpmw.AuthorizationUserRoles(r)
apiKey := httpmw.APIKey(r)
if apiKey.UserID == user.ID {
@ -488,7 +488,9 @@ func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) {
return
}
added, removed := rbac.ChangeRoleSet(roles.Roles, params.Roles)
// The member role is always implied.
impliedTypes := append(params.Roles, rbac.RoleMember())
added, removed := rbac.ChangeRoleSet(roles.Roles, impliedTypes)
for _, roleName := range added {
// Assigning a role requires the create permission.
if !api.Authorize(rw, r, rbac.ActionCreate, rbac.ResourceRoleAssignment.WithID(roleName)) {
@ -757,7 +759,7 @@ func (api *API) createAPIKey(rw http.ResponseWriter, r *http.Request, params dat
func (api *API) createUser(ctx context.Context, req codersdk.CreateUserRequest) (database.User, uuid.UUID, error) {
var user database.User
return user, req.OrganizationID, api.Database.InTx(func(db database.Store) error {
var orgRoles []string
orgRoles := make([]string, 0)
// If no organization is provided, create a new one for the user.
if req.OrganizationID == uuid.Nil {
organization, err := db.InsertOrganization(ctx, database.InsertOrganizationParams{
@ -772,8 +774,6 @@ func (api *API) createUser(ctx context.Context, req codersdk.CreateUserRequest)
req.OrganizationID = organization.ID
orgRoles = append(orgRoles, rbac.RoleOrgAdmin(req.OrganizationID))
}
// Always also be a member.
orgRoles = append(orgRoles, rbac.RoleOrgMember(req.OrganizationID))
params := database.InsertUserParams{
ID: uuid.New(),
@ -782,7 +782,7 @@ func (api *API) createUser(ctx context.Context, req codersdk.CreateUserRequest)
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
// All new users are defaulted to members of the site.
RBACRoles: []string{rbac.RoleMember()},
RBACRoles: []string{},
}
// If a user signs up with OAuth, they can have no password!
if req.Password != "" {

View File

@ -416,43 +416,43 @@ func TestGrantRoles(t *testing.T) {
member := coderdtest.CreateAnotherUser(t, admin, first.OrganizationID)
_, err = admin.UpdateUserRoles(ctx, codersdk.Me, codersdk.UpdateRoles{
Roles: []string{rbac.RoleOrgMember(first.OrganizationID)},
Roles: []string{rbac.RoleOrgAdmin(first.OrganizationID)},
})
require.Error(t, err, "org role in site")
requireStatusCode(t, err, http.StatusBadRequest)
_, err = admin.UpdateUserRoles(ctx, uuid.New().String(), codersdk.UpdateRoles{
Roles: []string{rbac.RoleOrgMember(first.OrganizationID)},
Roles: []string{rbac.RoleOrgAdmin(first.OrganizationID)},
})
require.Error(t, err, "user does not exist")
requireStatusCode(t, err, http.StatusBadRequest)
_, err = admin.UpdateOrganizationMemberRoles(ctx, first.OrganizationID, codersdk.Me, codersdk.UpdateRoles{
Roles: []string{rbac.RoleMember()},
Roles: []string{rbac.RoleAdmin()},
})
require.Error(t, err, "site role in org")
requireStatusCode(t, err, http.StatusBadRequest)
_, err = admin.UpdateOrganizationMemberRoles(ctx, uuid.New(), codersdk.Me, codersdk.UpdateRoles{
Roles: []string{rbac.RoleMember()},
Roles: []string{},
})
require.Error(t, err, "role in org without membership")
requireStatusCode(t, err, http.StatusNotFound)
_, err = member.UpdateUserRoles(ctx, first.UserID.String(), codersdk.UpdateRoles{
Roles: []string{rbac.RoleMember()},
Roles: []string{},
})
require.Error(t, err, "member cannot change other's roles")
requireStatusCode(t, err, http.StatusForbidden)
_, err = member.UpdateUserRoles(ctx, first.UserID.String(), codersdk.UpdateRoles{
Roles: []string{rbac.RoleMember()},
Roles: []string{},
})
require.Error(t, err, "member cannot change any roles")
requireStatusCode(t, err, http.StatusForbidden)
_, err = member.UpdateOrganizationMemberRoles(ctx, first.OrganizationID, first.UserID.String(), codersdk.UpdateRoles{
Roles: []string{rbac.RoleMember()},
Roles: []string{},
})
require.Error(t, err, "member cannot change other's org roles")
requireStatusCode(t, err, http.StatusForbidden)
@ -480,11 +480,9 @@ func TestGrantRoles(t *testing.T) {
require.NoError(t, err)
require.ElementsMatch(t, roles.Roles, []string{
rbac.RoleAdmin(),
rbac.RoleMember(),
}, "should be a member and admin")
require.ElementsMatch(t, roles.OrganizationRoles[first.OrganizationID], []string{
rbac.RoleOrgMember(first.OrganizationID),
rbac.RoleOrgAdmin(first.OrganizationID),
}, "should be a member and admin")
})
@ -498,12 +496,10 @@ func TestGrantRoles(t *testing.T) {
member := coderdtest.CreateAnotherUser(t, admin, first.OrganizationID)
roles, err := member.GetUserRoles(ctx, codersdk.Me)
require.NoError(t, err)
require.ElementsMatch(t, roles.Roles, []string{
rbac.RoleMember(),
}, "should be a member and admin")
require.ElementsMatch(t, roles.Roles, []string{}, "should be a member")
require.ElementsMatch(t,
roles.OrganizationRoles[first.OrganizationID],
[]string{rbac.RoleOrgMember(first.OrganizationID)},
[]string{},
)
memberUser, err := member.User(ctx, codersdk.Me)
@ -513,7 +509,6 @@ func TestGrantRoles(t *testing.T) {
_, err = admin.UpdateUserRoles(ctx, memberUser.ID.String(), codersdk.UpdateRoles{
Roles: []string{
// Promote to site admin
rbac.RoleMember(),
rbac.RoleAdmin(),
},
})
@ -523,7 +518,6 @@ func TestGrantRoles(t *testing.T) {
_, err = admin.UpdateOrganizationMemberRoles(ctx, first.OrganizationID, memberUser.ID.String(), codersdk.UpdateRoles{
Roles: []string{
// Promote to org admin
rbac.RoleOrgMember(first.OrganizationID),
rbac.RoleOrgAdmin(first.OrganizationID),
},
})
@ -532,12 +526,10 @@ func TestGrantRoles(t *testing.T) {
roles, err = member.GetUserRoles(ctx, codersdk.Me)
require.NoError(t, err)
require.ElementsMatch(t, roles.Roles, []string{
rbac.RoleMember(),
rbac.RoleAdmin(),
}, "should be a member and admin")
require.ElementsMatch(t, roles.OrganizationRoles[first.OrganizationID], []string{
rbac.RoleOrgMember(first.OrganizationID),
rbac.RoleOrgAdmin(first.OrganizationID),
}, "should be a member and admin")
})

View File

@ -88,7 +88,7 @@ func TestAdminViewAllWorkspaces(t *testing.T) {
// This other user is not in the first user's org. Since other is an admin, they can
// still see the "first" user's workspace.
other := coderdtest.CreateAnotherUser(t, client, otherOrg.ID, rbac.RoleAdmin(), rbac.RoleMember())
other := coderdtest.CreateAnotherUser(t, client, otherOrg.ID, rbac.RoleAdmin())
otherWorkspaces, err := other.Workspaces(context.Background(), codersdk.WorkspaceFilter{})
require.NoError(t, err, "(other) fetch workspaces")

View File

@ -69,7 +69,7 @@ type UpdateUserPasswordRequest struct {
}
type UpdateRoles struct {
Roles []string `json:"roles" validate:"required"`
Roles []string `json:"roles" validate:""`
}
type UserRoles struct {