mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
chore: create type for unique role names (#13506)
* chore: create type for unique role names Using `string` was confusing when something should be combined with org context, and when not to. Naming this new name, "RoleIdentifier"
This commit is contained in:
@ -199,7 +199,8 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
|
||||
Roles: []codersdk.SlimRole{},
|
||||
}
|
||||
|
||||
for _, roleName := range dblog.UserRoles {
|
||||
for _, input := range dblog.UserRoles {
|
||||
roleName, _ := rbac.RoleNameFromString(input)
|
||||
rbacRole, _ := rbac.RoleByName(roleName)
|
||||
user.Roles = append(user.Roles, db2sdk.SlimRole(rbacRole))
|
||||
}
|
||||
|
@ -60,10 +60,13 @@ func AssertRBAC(t *testing.T, api *coderd.API, client *codersdk.Client) RBACAsse
|
||||
roles, err := api.Database.GetAuthorizationUserRoles(ctx, key.UserID)
|
||||
require.NoError(t, err, "fetch user roles")
|
||||
|
||||
roleNames, err := roles.RoleNames()
|
||||
require.NoError(t, err)
|
||||
|
||||
return RBACAsserter{
|
||||
Subject: rbac.Subject{
|
||||
ID: key.UserID.String(),
|
||||
Roles: rbac.RoleNames(roles.Roles),
|
||||
Roles: rbac.RoleIdentifiers(roleNames),
|
||||
Groups: roles.Groups,
|
||||
Scope: rbac.ScopeName(key.Scope),
|
||||
},
|
||||
@ -435,7 +438,7 @@ func randomRBACType() string {
|
||||
func RandomRBACSubject() rbac.Subject {
|
||||
return rbac.Subject{
|
||||
ID: uuid.NewString(),
|
||||
Roles: rbac.RoleNames{rbac.RoleMember()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.RoleMember()},
|
||||
Groups: []string{namesgenerator.GetRandomName(1)},
|
||||
Scope: rbac.ScopeAll,
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/autobuild"
|
||||
"github.com/coder/coder/v2/coderd/awsidentity"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbrollup"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
@ -663,21 +664,25 @@ func CreateFirstUser(t testing.TB, client *codersdk.Client) codersdk.CreateFirst
|
||||
|
||||
// CreateAnotherUser creates and authenticates a new user.
|
||||
// Roles can include org scoped roles with 'roleName:<organization_id>'
|
||||
func CreateAnotherUser(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, roles ...string) (*codersdk.Client, codersdk.User) {
|
||||
func CreateAnotherUser(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, roles ...rbac.RoleIdentifier) (*codersdk.Client, codersdk.User) {
|
||||
return createAnotherUserRetry(t, client, organizationID, 5, roles)
|
||||
}
|
||||
|
||||
func CreateAnotherUserMutators(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, roles []string, mutators ...func(r *codersdk.CreateUserRequest)) (*codersdk.Client, codersdk.User) {
|
||||
func CreateAnotherUserMutators(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, roles []rbac.RoleIdentifier, mutators ...func(r *codersdk.CreateUserRequest)) (*codersdk.Client, codersdk.User) {
|
||||
return createAnotherUserRetry(t, client, organizationID, 5, roles, mutators...)
|
||||
}
|
||||
|
||||
// AuthzUserSubject does not include the user's groups.
|
||||
func AuthzUserSubject(user codersdk.User, orgID uuid.UUID) rbac.Subject {
|
||||
roles := make(rbac.RoleNames, 0, len(user.Roles))
|
||||
roles := make(rbac.RoleIdentifiers, 0, len(user.Roles))
|
||||
// Member role is always implied
|
||||
roles = append(roles, rbac.RoleMember())
|
||||
for _, r := range user.Roles {
|
||||
roles = append(roles, r.Name)
|
||||
orgID, _ := uuid.Parse(r.OrganizationID) // defaults to nil
|
||||
roles = append(roles, rbac.RoleIdentifier{
|
||||
Name: r.Name,
|
||||
OrganizationID: orgID,
|
||||
})
|
||||
}
|
||||
// We assume only 1 org exists
|
||||
roles = append(roles, rbac.ScopedRoleOrgMember(orgID))
|
||||
@ -690,7 +695,7 @@ func AuthzUserSubject(user codersdk.User, orgID uuid.UUID) rbac.Subject {
|
||||
}
|
||||
}
|
||||
|
||||
func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, retries int, roles []string, mutators ...func(r *codersdk.CreateUserRequest)) (*codersdk.Client, codersdk.User) {
|
||||
func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, retries int, roles []rbac.RoleIdentifier, mutators ...func(r *codersdk.CreateUserRequest)) (*codersdk.Client, codersdk.User) {
|
||||
req := codersdk.CreateUserRequest{
|
||||
Email: namesgenerator.GetRandomName(10) + "@coder.com",
|
||||
Username: RandomUsername(t),
|
||||
@ -748,36 +753,37 @@ func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationI
|
||||
|
||||
if len(roles) > 0 {
|
||||
// Find the roles for the org vs the site wide roles
|
||||
orgRoles := make(map[string][]string)
|
||||
var siteRoles []string
|
||||
orgRoles := make(map[uuid.UUID][]rbac.RoleIdentifier)
|
||||
var siteRoles []rbac.RoleIdentifier
|
||||
|
||||
for _, roleName := range roles {
|
||||
roleName := roleName
|
||||
orgID, ok := rbac.IsOrgRole(roleName)
|
||||
roleName, _, err = rbac.RoleSplit(roleName)
|
||||
require.NoError(t, err, "split org role name")
|
||||
ok := roleName.IsOrgRole()
|
||||
if ok {
|
||||
roleName, _, err = rbac.RoleSplit(roleName)
|
||||
require.NoError(t, err, "split rolename")
|
||||
orgRoles[orgID] = append(orgRoles[orgID], roleName)
|
||||
orgRoles[roleName.OrganizationID] = append(orgRoles[roleName.OrganizationID], roleName)
|
||||
} else {
|
||||
siteRoles = append(siteRoles, roleName)
|
||||
}
|
||||
}
|
||||
// Update the roles
|
||||
for _, r := range user.Roles {
|
||||
siteRoles = append(siteRoles, r.Name)
|
||||
orgID, _ := uuid.Parse(r.OrganizationID)
|
||||
siteRoles = append(siteRoles, rbac.RoleIdentifier{
|
||||
Name: r.Name,
|
||||
OrganizationID: orgID,
|
||||
})
|
||||
}
|
||||
|
||||
user, err = client.UpdateUserRoles(context.Background(), user.ID.String(), codersdk.UpdateRoles{Roles: siteRoles})
|
||||
onlyName := func(role rbac.RoleIdentifier) string {
|
||||
return role.Name
|
||||
}
|
||||
|
||||
user, err = client.UpdateUserRoles(context.Background(), user.ID.String(), codersdk.UpdateRoles{Roles: db2sdk.List(siteRoles, onlyName)})
|
||||
require.NoError(t, err, "update site roles")
|
||||
|
||||
// Update org roles
|
||||
for orgID, roles := range orgRoles {
|
||||
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: roles})
|
||||
_, err = client.UpdateOrganizationMemberRoles(context.Background(), orgID, user.ID.String(),
|
||||
codersdk.UpdateRoles{Roles: db2sdk.List(roles, onlyName)})
|
||||
require.NoError(t, err, "update org membership roles")
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +170,12 @@ func User(user database.User, organizationIDs []uuid.UUID) codersdk.User {
|
||||
}
|
||||
|
||||
for _, roleName := range user.RBACRoles {
|
||||
rbacRole, err := rbac.RoleByName(roleName)
|
||||
// TODO: Currently the api only returns site wide roles.
|
||||
// Should it return organization roles?
|
||||
rbacRole, err := rbac.RoleByName(rbac.RoleIdentifier{
|
||||
Name: roleName,
|
||||
OrganizationID: uuid.Nil,
|
||||
})
|
||||
if err == nil {
|
||||
convertedUser.Roles = append(convertedUser.Roles, SlimRole(rbacRole))
|
||||
} else {
|
||||
@ -519,29 +524,26 @@ func ProvisionerDaemon(dbDaemon database.ProvisionerDaemon) codersdk.Provisioner
|
||||
}
|
||||
|
||||
func SlimRole(role rbac.Role) codersdk.SlimRole {
|
||||
roleName, orgIDStr, err := rbac.RoleSplit(role.Name)
|
||||
if err != nil {
|
||||
roleName = role.Name
|
||||
orgID := ""
|
||||
if role.Identifier.OrganizationID != uuid.Nil {
|
||||
orgID = role.Identifier.OrganizationID.String()
|
||||
}
|
||||
|
||||
return codersdk.SlimRole{
|
||||
DisplayName: role.DisplayName,
|
||||
Name: roleName,
|
||||
OrganizationID: orgIDStr,
|
||||
Name: role.Identifier.Name,
|
||||
OrganizationID: orgID,
|
||||
}
|
||||
}
|
||||
|
||||
func RBACRole(role rbac.Role) codersdk.Role {
|
||||
roleName, orgIDStr, err := rbac.RoleSplit(role.Name)
|
||||
if err != nil {
|
||||
roleName = role.Name
|
||||
}
|
||||
orgPerms := role.Org[orgIDStr]
|
||||
slim := SlimRole(role)
|
||||
|
||||
orgPerms := role.Org[slim.OrganizationID]
|
||||
return codersdk.Role{
|
||||
Name: roleName,
|
||||
OrganizationID: orgIDStr,
|
||||
DisplayName: role.DisplayName,
|
||||
Name: slim.Name,
|
||||
OrganizationID: slim.OrganizationID,
|
||||
DisplayName: slim.DisplayName,
|
||||
SitePermissions: List(role.Site, RBACPermission),
|
||||
OrganizationPermissions: List(orgPerms, RBACPermission),
|
||||
UserPermissions: List(role.User, RBACPermission),
|
||||
|
@ -35,7 +35,7 @@ func TestUpsertCustomRoles(t *testing.T) {
|
||||
}
|
||||
|
||||
canAssignRole := rbac.Role{
|
||||
Name: "can-assign",
|
||||
Identifier: rbac.RoleIdentifier{Name: "can-assign"},
|
||||
DisplayName: "",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceAssignRole.Type: {policy.ActionRead, policy.ActionCreate},
|
||||
@ -51,7 +51,7 @@ func TestUpsertCustomRoles(t *testing.T) {
|
||||
all = append(all, t)
|
||||
case rbac.ExpandableRoles:
|
||||
all = append(all, must(t.Expand())...)
|
||||
case string:
|
||||
case rbac.RoleIdentifier:
|
||||
all = append(all, must(rbac.RoleByName(t)))
|
||||
default:
|
||||
panic("unknown type")
|
||||
@ -80,7 +80,7 @@ func TestUpsertCustomRoles(t *testing.T) {
|
||||
{
|
||||
// No roles, so no assign role
|
||||
name: "no-roles",
|
||||
subject: rbac.RoleNames([]string{}),
|
||||
subject: rbac.RoleIdentifiers{},
|
||||
errorContains: "forbidden",
|
||||
},
|
||||
{
|
||||
|
@ -162,7 +162,7 @@ var (
|
||||
ID: uuid.Nil.String(),
|
||||
Roles: rbac.Roles([]rbac.Role{
|
||||
{
|
||||
Name: "provisionerd",
|
||||
Identifier: rbac.RoleIdentifier{Name: "provisionerd"},
|
||||
DisplayName: "Provisioner Daemon",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
// TODO: Add ProvisionerJob resource type.
|
||||
@ -191,7 +191,7 @@ var (
|
||||
ID: uuid.Nil.String(),
|
||||
Roles: rbac.Roles([]rbac.Role{
|
||||
{
|
||||
Name: "autostart",
|
||||
Identifier: rbac.RoleIdentifier{Name: "autostart"},
|
||||
DisplayName: "Autostart Daemon",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceSystem.Type: {policy.WildcardSymbol},
|
||||
@ -213,7 +213,7 @@ var (
|
||||
ID: uuid.Nil.String(),
|
||||
Roles: rbac.Roles([]rbac.Role{
|
||||
{
|
||||
Name: "hangdetector",
|
||||
Identifier: rbac.RoleIdentifier{Name: "hangdetector"},
|
||||
DisplayName: "Hang Detector Daemon",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceSystem.Type: {policy.WildcardSymbol},
|
||||
@ -232,7 +232,7 @@ var (
|
||||
ID: uuid.Nil.String(),
|
||||
Roles: rbac.Roles([]rbac.Role{
|
||||
{
|
||||
Name: "system",
|
||||
Identifier: rbac.RoleIdentifier{Name: "system"},
|
||||
DisplayName: "Coder",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceWildcard.Type: {policy.ActionRead},
|
||||
@ -582,8 +582,38 @@ func (q *querier) authorizeUpdateFileTemplate(ctx context.Context, file database
|
||||
}
|
||||
}
|
||||
|
||||
// convertToOrganizationRoles converts a set of scoped role names to their unique
|
||||
// scoped names. The database stores roles as an array of strings, and needs to be
|
||||
// converted.
|
||||
// TODO: Maybe make `[]rbac.RoleIdentifier` a custom type that implements a sql scanner
|
||||
// to remove the need for these converters?
|
||||
func (*querier) convertToOrganizationRoles(organizationID uuid.UUID, names []string) ([]rbac.RoleIdentifier, error) {
|
||||
uniques := make([]rbac.RoleIdentifier, 0, len(names))
|
||||
for _, name := range names {
|
||||
// This check is a developer safety check. Old code might try to invoke this code path with
|
||||
// organization id suffixes. Catch this and return a nice error so it can be fixed.
|
||||
if strings.Contains(name, ":") {
|
||||
return nil, xerrors.Errorf("attempt to assign a role %q, remove the ':<organization_id> suffix", name)
|
||||
}
|
||||
|
||||
uniques = append(uniques, rbac.RoleIdentifier{Name: name, OrganizationID: organizationID})
|
||||
}
|
||||
|
||||
return uniques, nil
|
||||
}
|
||||
|
||||
// convertToDeploymentRoles converts string role names into deployment wide roles.
|
||||
func (*querier) convertToDeploymentRoles(names []string) []rbac.RoleIdentifier {
|
||||
uniques := make([]rbac.RoleIdentifier, 0, len(names))
|
||||
for _, name := range names {
|
||||
uniques = append(uniques, rbac.RoleIdentifier{Name: name})
|
||||
}
|
||||
|
||||
return uniques
|
||||
}
|
||||
|
||||
// canAssignRoles handles assigning built in and custom roles.
|
||||
func (q *querier) canAssignRoles(ctx context.Context, orgID *uuid.UUID, added, removed []string) error {
|
||||
func (q *querier) canAssignRoles(ctx context.Context, orgID *uuid.UUID, added, removed []rbac.RoleIdentifier) error {
|
||||
actor, ok := ActorFromContext(ctx)
|
||||
if !ok {
|
||||
return NoActorError
|
||||
@ -597,28 +627,24 @@ func (q *querier) canAssignRoles(ctx context.Context, orgID *uuid.UUID, added, r
|
||||
}
|
||||
|
||||
grantedRoles := append(added, removed...)
|
||||
customRoles := make([]string, 0)
|
||||
customRoles := make([]rbac.RoleIdentifier, 0)
|
||||
// Validate that the roles being assigned are valid.
|
||||
for _, r := range grantedRoles {
|
||||
roleOrgIDStr, isOrgRole := rbac.IsOrgRole(r)
|
||||
isOrgRole := r.OrganizationID != uuid.Nil
|
||||
if shouldBeOrgRoles && !isOrgRole {
|
||||
return xerrors.Errorf("Must only update org roles")
|
||||
}
|
||||
|
||||
if !shouldBeOrgRoles && isOrgRole {
|
||||
return xerrors.Errorf("Must only update site wide roles")
|
||||
}
|
||||
|
||||
if shouldBeOrgRoles {
|
||||
roleOrgID, err := uuid.Parse(roleOrgIDStr)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("role %q has invalid uuid for org: %w", r, err)
|
||||
}
|
||||
|
||||
if orgID == nil {
|
||||
return xerrors.Errorf("should never happen, orgID is nil, but trying to assign an organization role")
|
||||
}
|
||||
|
||||
if roleOrgID != *orgID {
|
||||
if r.OrganizationID != *orgID {
|
||||
return xerrors.Errorf("attempted to assign role from a different org, role %q to %q", r, orgID.String())
|
||||
}
|
||||
}
|
||||
@ -629,7 +655,7 @@ func (q *querier) canAssignRoles(ctx context.Context, orgID *uuid.UUID, added, r
|
||||
}
|
||||
}
|
||||
|
||||
customRolesMap := make(map[string]struct{}, len(customRoles))
|
||||
customRolesMap := make(map[rbac.RoleIdentifier]struct{}, len(customRoles))
|
||||
for _, r := range customRoles {
|
||||
customRolesMap[r] = struct{}{}
|
||||
}
|
||||
@ -649,7 +675,7 @@ func (q *querier) canAssignRoles(ctx context.Context, orgID *uuid.UUID, added, r
|
||||
// returns them all, but then someone could pass in a large list to make us do
|
||||
// a lot of loop iterations.
|
||||
if !slices.ContainsFunc(expandedCustomRoles, func(customRole rbac.Role) bool {
|
||||
return strings.EqualFold(customRole.Name, role)
|
||||
return strings.EqualFold(customRole.Identifier.Name, role.Name) && customRole.Identifier.OrganizationID == role.OrganizationID
|
||||
}) {
|
||||
return xerrors.Errorf("%q is not a supported role", role)
|
||||
}
|
||||
@ -2471,9 +2497,14 @@ func (q *querier) InsertOrganization(ctx context.Context, arg database.InsertOrg
|
||||
}
|
||||
|
||||
func (q *querier) InsertOrganizationMember(ctx context.Context, arg database.InsertOrganizationMemberParams) (database.OrganizationMember, error) {
|
||||
orgRoles, err := q.convertToOrganizationRoles(arg.OrganizationID, arg.Roles)
|
||||
if err != nil {
|
||||
return database.OrganizationMember{}, xerrors.Errorf("converting to organization roles: %w", err)
|
||||
}
|
||||
|
||||
// All roles are added roles. Org member is always implied.
|
||||
addedRoles := append(arg.Roles, rbac.ScopedRoleOrgMember(arg.OrganizationID))
|
||||
err := q.canAssignRoles(ctx, &arg.OrganizationID, addedRoles, []string{})
|
||||
addedRoles := append(orgRoles, rbac.ScopedRoleOrgMember(arg.OrganizationID))
|
||||
err = q.canAssignRoles(ctx, &arg.OrganizationID, addedRoles, []rbac.RoleIdentifier{})
|
||||
if err != nil {
|
||||
return database.OrganizationMember{}, err
|
||||
}
|
||||
@ -2559,8 +2590,8 @@ func (q *querier) InsertTemplateVersionWorkspaceTag(ctx context.Context, arg dat
|
||||
|
||||
func (q *querier) InsertUser(ctx context.Context, arg database.InsertUserParams) (database.User, error) {
|
||||
// Always check if the assigned roles can actually be assigned by this actor.
|
||||
impliedRoles := append([]string{rbac.RoleMember()}, arg.RBACRoles...)
|
||||
err := q.canAssignRoles(ctx, nil, impliedRoles, []string{})
|
||||
impliedRoles := append([]rbac.RoleIdentifier{rbac.RoleMember()}, q.convertToDeploymentRoles(arg.RBACRoles)...)
|
||||
err := q.canAssignRoles(ctx, nil, impliedRoles, []rbac.RoleIdentifier{})
|
||||
if err != nil {
|
||||
return database.User{}, err
|
||||
}
|
||||
@ -2847,23 +2878,22 @@ func (q *querier) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemb
|
||||
return database.OrganizationMember{}, err
|
||||
}
|
||||
|
||||
originalRoles, err := q.convertToOrganizationRoles(member.OrganizationID, member.Roles)
|
||||
if err != nil {
|
||||
return database.OrganizationMember{}, xerrors.Errorf("convert original roles: %w", err)
|
||||
}
|
||||
|
||||
// The 'rbac' package expects role names to be scoped.
|
||||
// Convert the argument roles for validation.
|
||||
scopedGranted := make([]string, 0, len(arg.GrantedRoles))
|
||||
for _, grantedRole := range arg.GrantedRoles {
|
||||
// This check is a developer safety check. Old code might try to invoke this code path with
|
||||
// organization id suffixes. Catch this and return a nice error so it can be fixed.
|
||||
_, foundOrg, _ := rbac.RoleSplit(grantedRole)
|
||||
if foundOrg != "" {
|
||||
return database.OrganizationMember{}, xerrors.Errorf("attempt to assign a role %q, remove the ':<organization_id> suffix", grantedRole)
|
||||
}
|
||||
|
||||
scopedGranted = append(scopedGranted, rbac.RoleName(grantedRole, arg.OrgID.String()))
|
||||
scopedGranted, err := q.convertToOrganizationRoles(arg.OrgID, arg.GrantedRoles)
|
||||
if err != nil {
|
||||
return database.OrganizationMember{}, err
|
||||
}
|
||||
|
||||
// The org member role is always implied.
|
||||
impliedTypes := append(scopedGranted, rbac.ScopedRoleOrgMember(arg.OrgID))
|
||||
added, removed := rbac.ChangeRoleSet(member.Roles, impliedTypes)
|
||||
|
||||
added, removed := rbac.ChangeRoleSet(originalRoles, impliedTypes)
|
||||
err = q.canAssignRoles(ctx, &arg.OrgID, added, removed)
|
||||
if err != nil {
|
||||
return database.OrganizationMember{}, err
|
||||
@ -3204,9 +3234,9 @@ func (q *querier) UpdateUserRoles(ctx context.Context, arg database.UpdateUserRo
|
||||
}
|
||||
|
||||
// The member role is always implied.
|
||||
impliedTypes := append(arg.GrantedRoles, rbac.RoleMember())
|
||||
impliedTypes := append(q.convertToDeploymentRoles(arg.GrantedRoles), rbac.RoleMember())
|
||||
// If the changeset is nothing, less rbac checks need to be done.
|
||||
added, removed := rbac.ChangeRoleSet(user.RBACRoles, impliedTypes)
|
||||
added, removed := rbac.ChangeRoleSet(q.convertToDeploymentRoles(user.RBACRoles), impliedTypes)
|
||||
err = q.canAssignRoles(ctx, nil, added, removed)
|
||||
if err != nil {
|
||||
return database.User{}, err
|
||||
|
@ -82,7 +82,7 @@ func TestInTX(t *testing.T) {
|
||||
}, slog.Make(), coderdtest.AccessControlStorePointer())
|
||||
actor := rbac.Subject{
|
||||
ID: uuid.NewString(),
|
||||
Roles: rbac.RoleNames{rbac.RoleOwner()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.RoleOwner()},
|
||||
Groups: []string{},
|
||||
Scope: rbac.ScopeAll,
|
||||
}
|
||||
@ -136,7 +136,7 @@ func TestDBAuthzRecursive(t *testing.T) {
|
||||
}, slog.Make(), coderdtest.AccessControlStorePointer())
|
||||
actor := rbac.Subject{
|
||||
ID: uuid.NewString(),
|
||||
Roles: rbac.RoleNames{rbac.RoleOwner()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.RoleOwner()},
|
||||
Groups: []string{},
|
||||
Scope: rbac.ScopeAll,
|
||||
}
|
||||
@ -636,7 +636,7 @@ func (s *MethodTestSuite) TestOrganization() {
|
||||
check.Args(database.InsertOrganizationMemberParams{
|
||||
OrganizationID: o.ID,
|
||||
UserID: u.ID,
|
||||
Roles: []string{rbac.ScopedRoleOrgAdmin(o.ID)},
|
||||
Roles: []string{codersdk.RoleOrganizationAdmin},
|
||||
}).Asserts(
|
||||
rbac.ResourceAssignRole.InOrg(o.ID), policy.ActionAssign,
|
||||
rbac.ResourceOrganizationMember.InOrg(o.ID).WithID(u.ID), policy.ActionCreate)
|
||||
@ -664,7 +664,7 @@ func (s *MethodTestSuite) TestOrganization() {
|
||||
mem := dbgen.OrganizationMember(s.T(), db, database.OrganizationMember{
|
||||
OrganizationID: o.ID,
|
||||
UserID: u.ID,
|
||||
Roles: []string{rbac.ScopedRoleOrgAdmin(o.ID)},
|
||||
Roles: []string{codersdk.RoleOrganizationAdmin},
|
||||
})
|
||||
out := mem
|
||||
out.Roles = []string{}
|
||||
@ -1179,11 +1179,11 @@ func (s *MethodTestSuite) TestUser() {
|
||||
}).Asserts(rbac.ResourceUserObject(link.UserID), policy.ActionUpdatePersonal).Returns(link)
|
||||
}))
|
||||
s.Run("UpdateUserRoles", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{RBACRoles: []string{rbac.RoleTemplateAdmin()}})
|
||||
u := dbgen.User(s.T(), db, database.User{RBACRoles: []string{codersdk.RoleTemplateAdmin}})
|
||||
o := u
|
||||
o.RBACRoles = []string{rbac.RoleUserAdmin()}
|
||||
o.RBACRoles = []string{codersdk.RoleUserAdmin}
|
||||
check.Args(database.UpdateUserRolesParams{
|
||||
GrantedRoles: []string{rbac.RoleUserAdmin()},
|
||||
GrantedRoles: []string{codersdk.RoleUserAdmin},
|
||||
ID: u.ID,
|
||||
}).Asserts(
|
||||
u, policy.ActionRead,
|
||||
|
@ -123,7 +123,7 @@ func (s *MethodTestSuite) Subtest(testCaseF func(db database.Store, check *expec
|
||||
az := dbauthz.New(db, rec, slog.Make(), coderdtest.AccessControlStorePointer())
|
||||
actor := rbac.Subject{
|
||||
ID: testActorID.String(),
|
||||
Roles: rbac.RoleNames{rbac.RoleOwner()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.RoleOwner()},
|
||||
Groups: []string{},
|
||||
Scope: rbac.ScopeAll,
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import (
|
||||
|
||||
var ownerCtx = dbauthz.As(context.Background(), rbac.Subject{
|
||||
ID: "owner",
|
||||
Roles: rbac.Roles(must(rbac.RoleNames{rbac.RoleOwner()}.Expand())),
|
||||
Roles: rbac.Roles(must(rbac.RoleIdentifiers{rbac.RoleOwner()}.Expand())),
|
||||
Groups: []string{},
|
||||
Scope: rbac.ExpandableScope(rbac.ScopeAll),
|
||||
})
|
||||
|
@ -33,7 +33,7 @@ import (
|
||||
// genCtx is to give all generator functions permission if the db is a dbauthz db.
|
||||
var genCtx = dbauthz.As(context.Background(), rbac.Subject{
|
||||
ID: "owner",
|
||||
Roles: rbac.Roles(must(rbac.RoleNames{rbac.RoleOwner()}.Expand())),
|
||||
Roles: rbac.Roles(must(rbac.RoleIdentifiers{rbac.RoleOwner()}.Expand())),
|
||||
Groups: []string{},
|
||||
Scope: rbac.ExpandableScope(rbac.ScopeAll),
|
||||
})
|
||||
|
@ -4808,7 +4808,7 @@ func (q *FakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams
|
||||
users = usersFilteredByStatus
|
||||
}
|
||||
|
||||
if len(params.RbacRole) > 0 && !slice.Contains(params.RbacRole, rbac.RoleMember()) {
|
||||
if len(params.RbacRole) > 0 && !slice.Contains(params.RbacRole, rbac.RoleMember().String()) {
|
||||
usersFilteredByRole := make([]database.User, 0, len(users))
|
||||
for i, user := range users {
|
||||
if slice.OverlapCompare(params.RbacRole, user.RBACRoles, strings.EqualFold) {
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
@ -373,3 +374,22 @@ func (p ProvisionerJob) FinishedAt() time.Time {
|
||||
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (r CustomRole) RoleIdentifier() rbac.RoleIdentifier {
|
||||
return rbac.RoleIdentifier{
|
||||
Name: r.Name,
|
||||
OrganizationID: r.OrganizationID.UUID,
|
||||
}
|
||||
}
|
||||
|
||||
func (r GetAuthorizationUserRolesRow) RoleNames() ([]rbac.RoleIdentifier, error) {
|
||||
names := make([]rbac.RoleIdentifier, 0, len(r.Roles))
|
||||
for _, role := range r.Roles {
|
||||
value, err := rbac.RoleNameFromString(role)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("convert role %q: %w", role, err)
|
||||
}
|
||||
names = append(names, value)
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
@ -438,8 +438,16 @@ func ExtractAPIKey(rw http.ResponseWriter, r *http.Request, cfg ExtractAPIKeyCon
|
||||
})
|
||||
}
|
||||
|
||||
roleNames, err := roles.RoleNames()
|
||||
if err != nil {
|
||||
return write(http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal Server Error",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:gocritic // Permission to lookup custom roles the user has assigned.
|
||||
rbacRoles, err := rolestore.Expand(dbauthz.AsSystemRestricted(ctx), cfg.DB, roles.Roles)
|
||||
rbacRoles, err := rolestore.Expand(dbauthz.AsSystemRestricted(ctx), cfg.DB, roleNames)
|
||||
if err != nil {
|
||||
return write(http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to expand authenticated user roles",
|
||||
|
@ -27,27 +27,26 @@ func TestExtractUserRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := []struct {
|
||||
Name string
|
||||
AddUser func(db database.Store) (database.User, []string, string)
|
||||
AddUser func(db database.Store) (database.User, []rbac.RoleIdentifier, string)
|
||||
}{
|
||||
{
|
||||
Name: "Member",
|
||||
AddUser: func(db database.Store) (database.User, []string, string) {
|
||||
roles := []string{}
|
||||
user, token := addUser(t, db, roles...)
|
||||
return user, append(roles, rbac.RoleMember()), token
|
||||
AddUser: func(db database.Store) (database.User, []rbac.RoleIdentifier, string) {
|
||||
user, token := addUser(t, db)
|
||||
return user, []rbac.RoleIdentifier{rbac.RoleMember()}, token
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Admin",
|
||||
AddUser: func(db database.Store) (database.User, []string, string) {
|
||||
roles := []string{rbac.RoleOwner()}
|
||||
Name: "Owner",
|
||||
AddUser: func(db database.Store) (database.User, []rbac.RoleIdentifier, string) {
|
||||
roles := []string{codersdk.RoleOwner}
|
||||
user, token := addUser(t, db, roles...)
|
||||
return user, append(roles, rbac.RoleMember()), token
|
||||
return user, []rbac.RoleIdentifier{rbac.RoleOwner(), rbac.RoleMember()}, token
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "OrgMember",
|
||||
AddUser: func(db database.Store) (database.User, []string, string) {
|
||||
AddUser: func(db database.Store) (database.User, []rbac.RoleIdentifier, string) {
|
||||
roles := []string{}
|
||||
user, token := addUser(t, db, roles...)
|
||||
org, err := db.InsertOrganization(context.Background(), database.InsertOrganizationParams{
|
||||
@ -68,15 +67,15 @@ func TestExtractUserRoles(t *testing.T) {
|
||||
Roles: orgRoles,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return user, append(roles, append(orgRoles, rbac.RoleMember(), rbac.ScopedRoleOrgMember(org.ID))...), token
|
||||
return user, []rbac.RoleIdentifier{rbac.RoleMember(), rbac.ScopedRoleOrgMember(org.ID)}, token
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "MultipleOrgMember",
|
||||
AddUser: func(db database.Store) (database.User, []string, string) {
|
||||
roles := []string{}
|
||||
user, token := addUser(t, db, roles...)
|
||||
roles = append(roles, rbac.RoleMember())
|
||||
AddUser: func(db database.Store) (database.User, []rbac.RoleIdentifier, string) {
|
||||
expected := []rbac.RoleIdentifier{}
|
||||
user, token := addUser(t, db)
|
||||
expected = append(expected, rbac.RoleMember())
|
||||
for i := 0; i < 3; i++ {
|
||||
organization, err := db.InsertOrganization(context.Background(), database.InsertOrganizationParams{
|
||||
ID: uuid.New(),
|
||||
@ -89,8 +88,8 @@ func TestExtractUserRoles(t *testing.T) {
|
||||
|
||||
orgRoles := []string{}
|
||||
if i%2 == 0 {
|
||||
orgRoles = append(orgRoles, rbac.RoleOrgAdmin())
|
||||
roles = append(roles, rbac.ScopedRoleOrgAdmin(organization.ID))
|
||||
orgRoles = append(orgRoles, codersdk.RoleOrganizationAdmin)
|
||||
expected = append(expected, rbac.ScopedRoleOrgAdmin(organization.ID))
|
||||
}
|
||||
_, err = db.InsertOrganizationMember(context.Background(), database.InsertOrganizationMemberParams{
|
||||
OrganizationID: organization.ID,
|
||||
@ -100,9 +99,9 @@ func TestExtractUserRoles(t *testing.T) {
|
||||
Roles: orgRoles,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
roles = append(roles, rbac.ScopedRoleOrgMember(organization.ID))
|
||||
expected = append(expected, rbac.ScopedRoleOrgMember(organization.ID))
|
||||
}
|
||||
return user, roles, token
|
||||
return user, expected, token
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -147,6 +146,9 @@ func addUser(t *testing.T, db database.Store, roles ...string) (database.User, s
|
||||
id, secret = randomAPIKeyParts()
|
||||
hashed = sha256.Sum256([]byte(secret))
|
||||
)
|
||||
if roles == nil {
|
||||
roles = []string{}
|
||||
}
|
||||
|
||||
user, err := db.InsertUser(context.Background(), database.InsertUserParams{
|
||||
ID: uuid.New(),
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/httpmw"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
)
|
||||
|
||||
func TestAsAuthzSystem(t *testing.T) {
|
||||
@ -34,7 +35,7 @@ func TestAsAuthzSystem(t *testing.T) {
|
||||
actor, ok := dbauthz.ActorFromContext(req.Context())
|
||||
assert.True(t, ok, "actor should exist")
|
||||
assert.False(t, userActor.Equal(actor), "systemActor should not be the user actor")
|
||||
assert.Contains(t, actor.Roles.Names(), "system", "should have system role")
|
||||
assert.Contains(t, actor.Roles.Names(), rbac.RoleIdentifier{Name: "system"}, "should have system role")
|
||||
})
|
||||
|
||||
mwAssertUser := mwAssert(func(req *http.Request) {
|
||||
|
@ -16,7 +16,6 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database/dbmem"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/httpmw"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
@ -152,11 +151,11 @@ func TestOrganizationParam(t *testing.T) {
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
OrganizationID: organization.ID,
|
||||
UserID: user.ID,
|
||||
Roles: []string{rbac.ScopedRoleOrgMember(organization.ID)},
|
||||
Roles: []string{codersdk.RoleOrganizationMember},
|
||||
})
|
||||
_, err := db.UpdateUserRoles(ctx, database.UpdateUserRolesParams{
|
||||
ID: user.ID,
|
||||
GrantedRoles: []string{rbac.RoleTemplateAdmin()},
|
||||
GrantedRoles: []string{codersdk.RoleTemplateAdmin},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -16,7 +16,6 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbmem"
|
||||
"github.com/coder/coder/v2/coderd/httpmw"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
@ -117,7 +116,7 @@ func TestRateLimit(t *testing.T) {
|
||||
db := dbmem.New()
|
||||
|
||||
u := dbgen.User(t, db, database.User{
|
||||
RBACRoles: []string{rbac.RoleOwner()},
|
||||
RBACRoles: []string{codersdk.RoleOwner},
|
||||
})
|
||||
_, key := dbgen.APIKey(t, db, database.APIKey{UserID: u.ID})
|
||||
|
||||
|
@ -119,9 +119,18 @@ func ExtractWorkspaceAgentAndLatestBuild(opts ExtractWorkspaceAgentAndLatestBuil
|
||||
return
|
||||
}
|
||||
|
||||
roleNames, err := roles.RoleNames()
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal server error",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
subject := rbac.Subject{
|
||||
ID: row.Workspace.OwnerID.String(),
|
||||
Roles: rbac.RoleNames(roles.Roles),
|
||||
Roles: rbac.RoleIdentifiers(roleNames),
|
||||
Groups: roles.Groups,
|
||||
Scope: rbac.WorkspaceAgentScope(rbac.WorkspaceAgentScopeParams{
|
||||
WorkspaceID: row.Workspace.ID,
|
||||
|
@ -214,9 +214,15 @@ func authorizationCodeGrant(ctx context.Context, db database.Store, app database
|
||||
if err != nil {
|
||||
return oauth2.Token{}, err
|
||||
}
|
||||
|
||||
roleNames, err := roles.RoleNames()
|
||||
if err != nil {
|
||||
return oauth2.Token{}, xerrors.Errorf("role names: %w", err)
|
||||
}
|
||||
|
||||
userSubj := rbac.Subject{
|
||||
ID: dbCode.UserID.String(),
|
||||
Roles: rbac.RoleNames(roles.Roles),
|
||||
Roles: rbac.RoleIdentifiers(roleNames),
|
||||
Groups: roles.Groups,
|
||||
Scope: rbac.ScopeAll,
|
||||
}
|
||||
@ -310,9 +316,15 @@ func refreshTokenGrant(ctx context.Context, db database.Store, app database.OAut
|
||||
if err != nil {
|
||||
return oauth2.Token{}, err
|
||||
}
|
||||
|
||||
roleNames, err := roles.RoleNames()
|
||||
if err != nil {
|
||||
return oauth2.Token{}, xerrors.Errorf("role names: %w", err)
|
||||
}
|
||||
|
||||
userSubj := rbac.Subject{
|
||||
ID: prevKey.UserID.String(),
|
||||
Roles: rbac.RoleNames(roles.Roles),
|
||||
Roles: rbac.RoleIdentifiers(roleNames),
|
||||
Groups: roles.Groups,
|
||||
Scope: rbac.ScopeAll,
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func convertOrganizationMember(mem database.OrganizationMember) codersdk.Organiz
|
||||
}
|
||||
|
||||
for _, roleName := range mem.Roles {
|
||||
rbacRole, _ := rbac.RoleByName(rbac.RoleName(roleName, mem.OrganizationID.String()))
|
||||
rbacRole, _ := rbac.RoleByName(rbac.RoleIdentifier{Name: roleName, OrganizationID: mem.OrganizationID})
|
||||
convertedMember.Roles = append(convertedMember.Roles, db2sdk.SlimRole(rbacRole))
|
||||
}
|
||||
return convertedMember
|
||||
|
@ -99,7 +99,7 @@ func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) {
|
||||
// come back to determining the default role of the person who
|
||||
// creates the org. Until that happens, all users in an organization
|
||||
// should be just regular members.
|
||||
rbac.ScopedRoleOrgMember(organization.ID),
|
||||
rbac.RoleOrgMember(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -110,13 +110,13 @@ func (s Subject) SafeScopeName() string {
|
||||
if s.Scope == nil {
|
||||
return "no-scope"
|
||||
}
|
||||
return s.Scope.Name()
|
||||
return s.Scope.Name().String()
|
||||
}
|
||||
|
||||
// SafeRoleNames prevent nil pointer dereference.
|
||||
func (s Subject) SafeRoleNames() []string {
|
||||
func (s Subject) SafeRoleNames() []RoleIdentifier {
|
||||
if s.Roles == nil {
|
||||
return []string{}
|
||||
return []RoleIdentifier{}
|
||||
}
|
||||
return s.Roles.Names()
|
||||
}
|
||||
@ -707,9 +707,15 @@ func (c *authCache) Prepare(ctx context.Context, subject Subject, action policy.
|
||||
// rbacTraceAttributes are the attributes that are added to all spans created by
|
||||
// the rbac package. These attributes should help to debug slow spans.
|
||||
func rbacTraceAttributes(actor Subject, action policy.Action, objectType string, extra ...attribute.KeyValue) trace.SpanStartOption {
|
||||
uniqueRoleNames := actor.SafeRoleNames()
|
||||
roleStrings := make([]string, 0, len(uniqueRoleNames))
|
||||
for _, roleName := range uniqueRoleNames {
|
||||
roleName := roleName
|
||||
roleStrings = append(roleStrings, roleName.String())
|
||||
}
|
||||
return trace.WithAttributes(
|
||||
append(extra,
|
||||
attribute.StringSlice("subject_roles", actor.SafeRoleNames()),
|
||||
attribute.StringSlice("subject_roles", roleStrings),
|
||||
attribute.Int("num_subject_roles", len(actor.SafeRoleNames())),
|
||||
attribute.Int("num_groups", len(actor.Groups)),
|
||||
attribute.String("scope", actor.SafeScopeName()),
|
||||
|
@ -56,7 +56,7 @@ func TestFilterError(t *testing.T) {
|
||||
auth := NewAuthorizer(prometheus.NewRegistry())
|
||||
subject := Subject{
|
||||
ID: uuid.NewString(),
|
||||
Roles: RoleNames{},
|
||||
Roles: RoleIdentifiers{},
|
||||
Groups: []string{},
|
||||
Scope: ScopeAll,
|
||||
}
|
||||
@ -77,7 +77,7 @@ func TestFilterError(t *testing.T) {
|
||||
|
||||
subject := Subject{
|
||||
ID: uuid.NewString(),
|
||||
Roles: RoleNames{
|
||||
Roles: RoleIdentifiers{
|
||||
RoleOwner(),
|
||||
},
|
||||
Groups: []string{},
|
||||
@ -159,7 +159,7 @@ func TestFilter(t *testing.T) {
|
||||
Name: "NoRoles",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{},
|
||||
Roles: RoleIdentifiers{},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: policy.ActionRead,
|
||||
@ -168,7 +168,7 @@ func TestFilter(t *testing.T) {
|
||||
Name: "Admin",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{ScopedRoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
|
||||
Roles: RoleIdentifiers{ScopedRoleOrgMember(orgIDs[0]), RoleAuditor(), RoleOwner(), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: policy.ActionRead,
|
||||
@ -177,7 +177,7 @@ func TestFilter(t *testing.T) {
|
||||
Name: "OrgAdmin",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgAdmin(orgIDs[0]), RoleMember()},
|
||||
Roles: RoleIdentifiers{ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgAdmin(orgIDs[0]), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: policy.ActionRead,
|
||||
@ -186,7 +186,7 @@ func TestFilter(t *testing.T) {
|
||||
Name: "OrgMember",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgMember(orgIDs[1]), RoleMember()},
|
||||
Roles: RoleIdentifiers{ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgMember(orgIDs[1]), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: policy.ActionRead,
|
||||
@ -195,7 +195,7 @@ func TestFilter(t *testing.T) {
|
||||
Name: "ManyRoles",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{
|
||||
Roles: RoleIdentifiers{
|
||||
ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgAdmin(orgIDs[0]),
|
||||
ScopedRoleOrgMember(orgIDs[1]), ScopedRoleOrgAdmin(orgIDs[1]),
|
||||
ScopedRoleOrgMember(orgIDs[2]), ScopedRoleOrgAdmin(orgIDs[2]),
|
||||
@ -211,7 +211,7 @@ func TestFilter(t *testing.T) {
|
||||
Name: "SiteMember",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{RoleMember()},
|
||||
Roles: RoleIdentifiers{RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceUser.Type,
|
||||
Action: policy.ActionRead,
|
||||
@ -220,7 +220,7 @@ func TestFilter(t *testing.T) {
|
||||
Name: "ReadOrgs",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{
|
||||
Roles: RoleIdentifiers{
|
||||
ScopedRoleOrgMember(orgIDs[0]),
|
||||
ScopedRoleOrgMember(orgIDs[1]),
|
||||
ScopedRoleOrgMember(orgIDs[2]),
|
||||
@ -235,7 +235,7 @@ func TestFilter(t *testing.T) {
|
||||
Name: "ScopeApplicationConnect",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{ScopedRoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
|
||||
Roles: RoleIdentifiers{ScopedRoleOrgMember(orgIDs[0]), RoleAuditor(), RoleOwner(), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: policy.ActionRead,
|
||||
@ -394,7 +394,7 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
ID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: Roles{{
|
||||
Name: "deny-all",
|
||||
Identifier: RoleIdentifier{Name: "deny-all"},
|
||||
// List out deny permissions explicitly
|
||||
Site: []Permission{
|
||||
{
|
||||
@ -607,8 +607,8 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: Roles{
|
||||
{
|
||||
Name: "ReadOnlyOrgAndUser",
|
||||
Site: []Permission{},
|
||||
Identifier: RoleIdentifier{Name: "ReadOnlyOrgAndUser"},
|
||||
Site: []Permission{},
|
||||
Org: map[string][]Permission{
|
||||
defOrg.String(): {{
|
||||
Negate: false,
|
||||
@ -701,7 +701,7 @@ func TestAuthorizeLevels(t *testing.T) {
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleOwner())),
|
||||
{
|
||||
Name: "org-deny:" + defOrg.String(),
|
||||
Identifier: RoleIdentifier{Name: "org-deny:", OrganizationID: defOrg},
|
||||
Org: map[string][]Permission{
|
||||
defOrg.String(): {
|
||||
{
|
||||
@ -713,7 +713,7 @@ func TestAuthorizeLevels(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "user-deny-all",
|
||||
Identifier: RoleIdentifier{Name: "user-deny-all"},
|
||||
// List out deny permissions explicitly
|
||||
User: []Permission{
|
||||
{
|
||||
@ -761,7 +761,7 @@ func TestAuthorizeLevels(t *testing.T) {
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: Roles{
|
||||
{
|
||||
Name: "site-noise",
|
||||
Identifier: RoleIdentifier{Name: "site-noise"},
|
||||
Site: []Permission{
|
||||
{
|
||||
Negate: true,
|
||||
@ -772,7 +772,7 @@ func TestAuthorizeLevels(t *testing.T) {
|
||||
},
|
||||
must(RoleByName(ScopedRoleOrgAdmin(defOrg))),
|
||||
{
|
||||
Name: "user-deny-all",
|
||||
Identifier: RoleIdentifier{Name: "user-deny-all"},
|
||||
// List out deny permissions explicitly
|
||||
User: []Permission{
|
||||
{
|
||||
@ -896,7 +896,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
},
|
||||
Scope: Scope{
|
||||
Role: Role{
|
||||
Name: "workspace_agent",
|
||||
Identifier: RoleIdentifier{Name: "workspace_agent"},
|
||||
DisplayName: "Workspace Agent",
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
// Only read access for workspaces.
|
||||
@ -985,7 +985,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
},
|
||||
Scope: Scope{
|
||||
Role: Role{
|
||||
Name: "create_workspace",
|
||||
Identifier: RoleIdentifier{Name: "create_workspace"},
|
||||
DisplayName: "Create Workspace",
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
// Only read access for workspaces.
|
||||
|
@ -41,7 +41,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
|
||||
Name: "NoRoles",
|
||||
Actor: rbac.Subject{
|
||||
ID: user.String(),
|
||||
Roles: rbac.RoleNames{},
|
||||
Roles: rbac.RoleIdentifiers{},
|
||||
Scope: rbac.ScopeAll,
|
||||
},
|
||||
},
|
||||
@ -49,7 +49,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
|
||||
Name: "Admin",
|
||||
Actor: rbac.Subject{
|
||||
// Give some extra roles that an admin might have
|
||||
Roles: rbac.RoleNames{rbac.ScopedRoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.ScopedRoleOrgMember(orgs[0]), rbac.RoleAuditor(), rbac.RoleOwner(), rbac.RoleMember()},
|
||||
ID: user.String(),
|
||||
Scope: rbac.ScopeAll,
|
||||
Groups: noiseGroups,
|
||||
@ -58,7 +58,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
|
||||
{
|
||||
Name: "OrgAdmin",
|
||||
Actor: rbac.Subject{
|
||||
Roles: rbac.RoleNames{rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgAdmin(orgs[0]), rbac.RoleMember()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgAdmin(orgs[0]), rbac.RoleMember()},
|
||||
ID: user.String(),
|
||||
Scope: rbac.ScopeAll,
|
||||
Groups: noiseGroups,
|
||||
@ -68,7 +68,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
|
||||
Name: "OrgMember",
|
||||
Actor: rbac.Subject{
|
||||
// Member of 2 orgs
|
||||
Roles: rbac.RoleNames{rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgMember(orgs[1]), rbac.RoleMember()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgMember(orgs[1]), rbac.RoleMember()},
|
||||
ID: user.String(),
|
||||
Scope: rbac.ScopeAll,
|
||||
Groups: noiseGroups,
|
||||
@ -78,7 +78,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
|
||||
Name: "ManyRoles",
|
||||
Actor: rbac.Subject{
|
||||
// Admin of many orgs
|
||||
Roles: rbac.RoleNames{
|
||||
Roles: rbac.RoleIdentifiers{
|
||||
rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgAdmin(orgs[0]),
|
||||
rbac.ScopedRoleOrgMember(orgs[1]), rbac.ScopedRoleOrgAdmin(orgs[1]),
|
||||
rbac.ScopedRoleOrgMember(orgs[2]), rbac.ScopedRoleOrgAdmin(orgs[2]),
|
||||
@ -93,7 +93,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
|
||||
Name: "ManyRolesCachedSubject",
|
||||
Actor: rbac.Subject{
|
||||
// Admin of many orgs
|
||||
Roles: rbac.RoleNames{
|
||||
Roles: rbac.RoleIdentifiers{
|
||||
rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgAdmin(orgs[0]),
|
||||
rbac.ScopedRoleOrgMember(orgs[1]), rbac.ScopedRoleOrgAdmin(orgs[1]),
|
||||
rbac.ScopedRoleOrgMember(orgs[2]), rbac.ScopedRoleOrgAdmin(orgs[2]),
|
||||
@ -108,7 +108,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
|
||||
Name: "AdminWithScope",
|
||||
Actor: rbac.Subject{
|
||||
// Give some extra roles that an admin might have
|
||||
Roles: rbac.RoleNames{rbac.ScopedRoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.ScopedRoleOrgMember(orgs[0]), rbac.RoleAuditor(), rbac.RoleOwner(), rbac.RoleMember()},
|
||||
ID: user.String(),
|
||||
Scope: rbac.ScopeApplicationConnect,
|
||||
Groups: noiseGroups,
|
||||
@ -119,8 +119,8 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
|
||||
Name: "StaticRoles",
|
||||
Actor: rbac.Subject{
|
||||
// Give some extra roles that an admin might have
|
||||
Roles: rbac.RoleNames{
|
||||
"auditor", rbac.RoleOwner(), rbac.RoleMember(),
|
||||
Roles: rbac.RoleIdentifiers{
|
||||
rbac.RoleAuditor(), rbac.RoleOwner(), rbac.RoleMember(),
|
||||
rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin(),
|
||||
},
|
||||
ID: user.String(),
|
||||
@ -133,8 +133,8 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
|
||||
Name: "StaticRolesWithCache",
|
||||
Actor: rbac.Subject{
|
||||
// Give some extra roles that an admin might have
|
||||
Roles: rbac.RoleNames{
|
||||
"auditor", rbac.RoleOwner(), rbac.RoleMember(),
|
||||
Roles: rbac.RoleIdentifiers{
|
||||
rbac.RoleAuditor(), rbac.RoleOwner(), rbac.RoleMember(),
|
||||
rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin(),
|
||||
},
|
||||
ID: user.String(),
|
||||
|
@ -1,6 +1,7 @@
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
@ -34,41 +35,98 @@ func init() {
|
||||
ReloadBuiltinRoles(nil)
|
||||
}
|
||||
|
||||
// RoleNames is a list of user assignable role names. The role names must be
|
||||
// RoleIdentifiers is a list of user assignable role names. The role names must be
|
||||
// in the builtInRoles map. Any non-user assignable roles will generate an
|
||||
// error on Expand.
|
||||
type RoleNames []string
|
||||
type RoleIdentifiers []RoleIdentifier
|
||||
|
||||
func (names RoleNames) Expand() ([]Role, error) {
|
||||
func (names RoleIdentifiers) Expand() ([]Role, error) {
|
||||
return rolesByNames(names)
|
||||
}
|
||||
|
||||
func (names RoleNames) Names() []string {
|
||||
func (names RoleIdentifiers) Names() []RoleIdentifier {
|
||||
return names
|
||||
}
|
||||
|
||||
// RoleIdentifier contains both the name of the role, and any organizational scope.
|
||||
// Both fields are required to be globally unique and identifiable.
|
||||
type RoleIdentifier struct {
|
||||
Name string
|
||||
// OrganizationID is uuid.Nil for unscoped roles (aka deployment wide)
|
||||
OrganizationID uuid.UUID
|
||||
}
|
||||
|
||||
func (r RoleIdentifier) IsOrgRole() bool {
|
||||
return r.OrganizationID != uuid.Nil
|
||||
}
|
||||
|
||||
// RoleNameFromString takes a formatted string '<role_name>[:org_id]'.
|
||||
func RoleNameFromString(input string) (RoleIdentifier, error) {
|
||||
var role RoleIdentifier
|
||||
|
||||
arr := strings.Split(input, ":")
|
||||
if len(arr) > 2 {
|
||||
return role, xerrors.Errorf("too many colons in role name")
|
||||
}
|
||||
|
||||
if len(arr) == 0 {
|
||||
return role, xerrors.Errorf("empty string not a valid role")
|
||||
}
|
||||
|
||||
if arr[0] == "" {
|
||||
return role, xerrors.Errorf("role cannot be the empty string")
|
||||
}
|
||||
|
||||
role.Name = arr[0]
|
||||
|
||||
if len(arr) == 2 {
|
||||
orgID, err := uuid.Parse(arr[1])
|
||||
if err != nil {
|
||||
return role, xerrors.Errorf("%q not a valid uuid: %w", arr[1], err)
|
||||
}
|
||||
role.OrganizationID = orgID
|
||||
}
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (r RoleIdentifier) String() string {
|
||||
if r.OrganizationID != uuid.Nil {
|
||||
return r.Name + ":" + r.OrganizationID.String()
|
||||
}
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *RoleIdentifier) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(r.String())
|
||||
}
|
||||
|
||||
func (r *RoleIdentifier) UnmarshalJSON(data []byte) error {
|
||||
var str string
|
||||
err := json.Unmarshal(data, &str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := RoleNameFromString(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = v
|
||||
return nil
|
||||
}
|
||||
|
||||
// The functions below ONLY need to exist for roles that are "defaulted" in some way.
|
||||
// Any other roles (like auditor), can be listed and let the user select/assigned.
|
||||
// Once we have a database implementation, the "default" roles can be defined on the
|
||||
// site and orgs, and these functions can be removed.
|
||||
|
||||
func RoleOwner() string {
|
||||
return RoleName(owner, "")
|
||||
}
|
||||
|
||||
func CustomSiteRole() string { return RoleName(customSiteRole, "") }
|
||||
|
||||
func RoleTemplateAdmin() string {
|
||||
return RoleName(templateAdmin, "")
|
||||
}
|
||||
|
||||
func RoleUserAdmin() string {
|
||||
return RoleName(userAdmin, "")
|
||||
}
|
||||
|
||||
func RoleMember() string {
|
||||
return RoleName(member, "")
|
||||
}
|
||||
func RoleOwner() RoleIdentifier { return RoleIdentifier{Name: owner} }
|
||||
func CustomSiteRole() RoleIdentifier { return RoleIdentifier{Name: customSiteRole} }
|
||||
func RoleTemplateAdmin() RoleIdentifier { return RoleIdentifier{Name: templateAdmin} }
|
||||
func RoleUserAdmin() RoleIdentifier { return RoleIdentifier{Name: userAdmin} }
|
||||
func RoleMember() RoleIdentifier { return RoleIdentifier{Name: member} }
|
||||
func RoleAuditor() RoleIdentifier { return RoleIdentifier{Name: auditor} }
|
||||
|
||||
func RoleOrgAdmin() string {
|
||||
return orgAdmin
|
||||
@ -81,15 +139,15 @@ func RoleOrgMember() string {
|
||||
// ScopedRoleOrgAdmin is the org role with the organization ID
|
||||
// Deprecated This was used before organization scope was included as a
|
||||
// field in all user facing APIs. Usage of 'ScopedRoleOrgAdmin()' is preferred.
|
||||
func ScopedRoleOrgAdmin(organizationID uuid.UUID) string {
|
||||
return RoleName(orgAdmin, organizationID.String())
|
||||
func ScopedRoleOrgAdmin(organizationID uuid.UUID) RoleIdentifier {
|
||||
return RoleIdentifier{Name: orgAdmin, OrganizationID: organizationID}
|
||||
}
|
||||
|
||||
// ScopedRoleOrgMember is the org role with the organization ID
|
||||
// Deprecated This was used before organization scope was included as a
|
||||
// field in all user facing APIs. Usage of 'ScopedRoleOrgMember()' is preferred.
|
||||
func ScopedRoleOrgMember(organizationID uuid.UUID) string {
|
||||
return RoleName(orgMember, organizationID.String())
|
||||
func ScopedRoleOrgMember(organizationID uuid.UUID) RoleIdentifier {
|
||||
return RoleIdentifier{Name: orgMember, OrganizationID: organizationID}
|
||||
}
|
||||
|
||||
func allPermsExcept(excepts ...Objecter) []Permission {
|
||||
@ -127,7 +185,7 @@ func allPermsExcept(excepts ...Objecter) []Permission {
|
||||
//
|
||||
// This map will be replaced by database storage defined by this ticket.
|
||||
// https://github.com/coder/coder/issues/1194
|
||||
var builtInRoles map[string]func(orgID string) Role
|
||||
var builtInRoles map[string]func(orgID uuid.UUID) Role
|
||||
|
||||
type RoleOptions struct {
|
||||
NoOwnerWorkspaceExec bool
|
||||
@ -158,7 +216,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
// on every authorize call. 'withCachedRegoValue' can be used as well to
|
||||
// preallocate the rego value that is used by the rego eval engine.
|
||||
ownerRole := Role{
|
||||
Name: owner,
|
||||
Identifier: RoleOwner(),
|
||||
DisplayName: "Owner",
|
||||
Site: append(
|
||||
// Workspace dormancy and workspace are omitted.
|
||||
@ -174,7 +232,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
}.withCachedRegoValue()
|
||||
|
||||
memberRole := Role{
|
||||
Name: member,
|
||||
Identifier: RoleMember(),
|
||||
DisplayName: "Member",
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
ResourceAssignRole.Type: {policy.ActionRead},
|
||||
@ -200,7 +258,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
}.withCachedRegoValue()
|
||||
|
||||
auditorRole := Role{
|
||||
Name: auditor,
|
||||
Identifier: RoleAuditor(),
|
||||
DisplayName: "Auditor",
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
// Should be able to read all template details, even in orgs they
|
||||
@ -220,7 +278,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
}.withCachedRegoValue()
|
||||
|
||||
templateAdminRole := Role{
|
||||
Name: templateAdmin,
|
||||
Identifier: RoleTemplateAdmin(),
|
||||
DisplayName: "Template Admin",
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
ResourceTemplate.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete, policy.ActionViewInsights},
|
||||
@ -241,7 +299,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
}.withCachedRegoValue()
|
||||
|
||||
userAdminRole := Role{
|
||||
Name: userAdmin,
|
||||
Identifier: RoleUserAdmin(),
|
||||
DisplayName: "User Admin",
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
ResourceAssignRole.Type: {policy.ActionAssign, policy.ActionDelete, policy.ActionRead},
|
||||
@ -257,42 +315,42 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
User: []Permission{},
|
||||
}.withCachedRegoValue()
|
||||
|
||||
builtInRoles = map[string]func(orgID string) Role{
|
||||
builtInRoles = map[string]func(orgID uuid.UUID) Role{
|
||||
// admin grants all actions to all resources.
|
||||
owner: func(_ string) Role {
|
||||
owner: func(_ uuid.UUID) Role {
|
||||
return ownerRole
|
||||
},
|
||||
|
||||
// member grants all actions to all resources owned by the user
|
||||
member: func(_ string) Role {
|
||||
member: func(_ uuid.UUID) Role {
|
||||
return memberRole
|
||||
},
|
||||
|
||||
// auditor provides all permissions required to effectively read and understand
|
||||
// audit log events.
|
||||
// TODO: Finish the auditor as we add resources.
|
||||
auditor: func(_ string) Role {
|
||||
auditor: func(_ uuid.UUID) Role {
|
||||
return auditorRole
|
||||
},
|
||||
|
||||
templateAdmin: func(_ string) Role {
|
||||
templateAdmin: func(_ uuid.UUID) Role {
|
||||
return templateAdminRole
|
||||
},
|
||||
|
||||
userAdmin: func(_ string) Role {
|
||||
userAdmin: func(_ uuid.UUID) Role {
|
||||
return userAdminRole
|
||||
},
|
||||
|
||||
// orgAdmin returns a role with all actions allows in a given
|
||||
// organization scope.
|
||||
orgAdmin: func(organizationID string) Role {
|
||||
orgAdmin: func(organizationID uuid.UUID) Role {
|
||||
return Role{
|
||||
Name: RoleName(orgAdmin, organizationID),
|
||||
Identifier: RoleIdentifier{Name: orgAdmin, OrganizationID: organizationID},
|
||||
DisplayName: "Organization Admin",
|
||||
Site: []Permission{},
|
||||
Org: map[string][]Permission{
|
||||
// Org admins should not have workspace exec perms.
|
||||
organizationID: append(allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant), Permissions(map[string][]policy.Action{
|
||||
organizationID.String(): append(allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant), Permissions(map[string][]policy.Action{
|
||||
ResourceWorkspaceDormant.Type: {policy.ActionRead, policy.ActionDelete, policy.ActionCreate, policy.ActionUpdate, policy.ActionWorkspaceStop},
|
||||
ResourceWorkspace.Type: slice.Omit(ResourceWorkspace.AvailableActions(), policy.ActionApplicationConnect, policy.ActionSSH),
|
||||
})...),
|
||||
@ -303,13 +361,13 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
|
||||
// orgMember has an empty set of permissions, this just implies their membership
|
||||
// in an organization.
|
||||
orgMember: func(organizationID string) Role {
|
||||
orgMember: func(organizationID uuid.UUID) Role {
|
||||
return Role{
|
||||
Name: RoleName(orgMember, organizationID),
|
||||
Identifier: RoleIdentifier{Name: orgMember, OrganizationID: organizationID},
|
||||
DisplayName: "",
|
||||
Site: []Permission{},
|
||||
Org: map[string][]Permission{
|
||||
organizationID: {
|
||||
organizationID.String(): {
|
||||
{
|
||||
// All org members can read the organization
|
||||
ResourceType: ResourceOrganization.Type,
|
||||
@ -370,7 +428,7 @@ var assignRoles = map[string]map[string]bool{
|
||||
}
|
||||
|
||||
// ExpandableRoles is any type that can be expanded into a []Role. This is implemented
|
||||
// as an interface so we can have RoleNames for user defined roles, and implement
|
||||
// as an interface so we can have RoleIdentifiers for user defined roles, and implement
|
||||
// custom ExpandableRoles for system type users (eg autostart/autostop system role).
|
||||
// We want a clear divide between the two types of roles so users have no codepath
|
||||
// to interact or assign system roles.
|
||||
@ -381,7 +439,7 @@ type ExpandableRoles interface {
|
||||
Expand() ([]Role, error)
|
||||
// Names is for logging and tracing purposes, we want to know the human
|
||||
// names of the expanded roles.
|
||||
Names() []string
|
||||
Names() []RoleIdentifier
|
||||
}
|
||||
|
||||
// Permission is the format passed into the rego.
|
||||
@ -424,7 +482,7 @@ func (perm Permission) Valid() error {
|
||||
// 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"`
|
||||
Identifier RoleIdentifier `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"`
|
||||
@ -474,10 +532,10 @@ func (roles Roles) Expand() ([]Role, error) {
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (roles Roles) Names() []string {
|
||||
names := make([]string, 0, len(roles))
|
||||
func (roles Roles) Names() []RoleIdentifier {
|
||||
names := make([]RoleIdentifier, 0, len(roles))
|
||||
for _, r := range roles {
|
||||
names = append(names, r.Name)
|
||||
names = append(names, r.Identifier)
|
||||
}
|
||||
return names
|
||||
}
|
||||
@ -485,32 +543,22 @@ func (roles Roles) Names() []string {
|
||||
// CanAssignRole is a helper function that returns true if the user can assign
|
||||
// the specified role. This also can be used for removing a role.
|
||||
// This is a simple implementation for now.
|
||||
func CanAssignRole(expandable ExpandableRoles, assignedRole string) bool {
|
||||
func CanAssignRole(subjectHasRoles ExpandableRoles, assignedRole RoleIdentifier) bool {
|
||||
// For CanAssignRole, we only care about the names of the roles.
|
||||
roles := expandable.Names()
|
||||
roles := subjectHasRoles.Names()
|
||||
|
||||
assigned, assignedOrg, err := RoleSplit(assignedRole)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, longRole := range roles {
|
||||
role, orgID, err := RoleSplit(longRole)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if orgID != "" && orgID != assignedOrg {
|
||||
for _, myRole := range roles {
|
||||
if myRole.OrganizationID != uuid.Nil && myRole.OrganizationID != assignedRole.OrganizationID {
|
||||
// Org roles only apply to the org they are assigned to.
|
||||
continue
|
||||
}
|
||||
|
||||
allowed, ok := assignRoles[role]
|
||||
allowedAssignList, ok := assignRoles[myRole.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if allowed[assigned] {
|
||||
if allowedAssignList[assignedRole.Name] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -523,29 +571,24 @@ func CanAssignRole(expandable ExpandableRoles, assignedRole string) bool {
|
||||
// This function is exported so that the Display name can be returned to the
|
||||
// api. We should maybe make an exported function that returns just the
|
||||
// human-readable content of the Role struct (name + display name).
|
||||
func RoleByName(name string) (Role, error) {
|
||||
roleName, orgID, err := RoleSplit(name)
|
||||
if err != nil {
|
||||
return Role{}, xerrors.Errorf("parse role name: %w", err)
|
||||
}
|
||||
|
||||
roleFunc, ok := builtInRoles[roleName]
|
||||
func RoleByName(name RoleIdentifier) (Role, error) {
|
||||
roleFunc, ok := builtInRoles[name.Name]
|
||||
if !ok {
|
||||
// No role found
|
||||
return Role{}, xerrors.Errorf("role %q not found", roleName)
|
||||
return Role{}, xerrors.Errorf("role %q not found", name.String())
|
||||
}
|
||||
|
||||
// Ensure all org roles are properly scoped a non-empty organization id.
|
||||
// This is just some defensive programming.
|
||||
role := roleFunc(orgID)
|
||||
if len(role.Org) > 0 && orgID == "" {
|
||||
return Role{}, xerrors.Errorf("expect a org id for role %q", roleName)
|
||||
role := roleFunc(name.OrganizationID)
|
||||
if len(role.Org) > 0 && name.OrganizationID == uuid.Nil {
|
||||
return Role{}, xerrors.Errorf("expect a org id for role %q", name.String())
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func rolesByNames(roleNames []string) ([]Role, error) {
|
||||
func rolesByNames(roleNames []RoleIdentifier) ([]Role, error) {
|
||||
roles := make([]Role, 0, len(roleNames))
|
||||
for _, n := range roleNames {
|
||||
r, err := RoleByName(n)
|
||||
@ -557,14 +600,6 @@ func rolesByNames(roleNames []string) ([]Role, error) {
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func IsOrgRole(roleName string) (string, bool) {
|
||||
_, orgID, err := RoleSplit(roleName)
|
||||
if err == nil && orgID != "" {
|
||||
return orgID, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// OrganizationRoles lists all roles that can be applied to an organization user
|
||||
// in the given organization. This is the list of available roles,
|
||||
// and specific to an organization.
|
||||
@ -574,13 +609,8 @@ func IsOrgRole(roleName string) (string, bool) {
|
||||
func OrganizationRoles(organizationID uuid.UUID) []Role {
|
||||
var roles []Role
|
||||
for _, roleF := range builtInRoles {
|
||||
role := roleF(organizationID.String())
|
||||
_, scope, err := RoleSplit(role.Name)
|
||||
if err != nil {
|
||||
// This should never happen
|
||||
continue
|
||||
}
|
||||
if scope == organizationID.String() {
|
||||
role := roleF(organizationID)
|
||||
if role.Identifier.OrganizationID == organizationID {
|
||||
roles = append(roles, role)
|
||||
}
|
||||
}
|
||||
@ -595,13 +625,9 @@ func OrganizationRoles(organizationID uuid.UUID) []Role {
|
||||
func SiteRoles() []Role {
|
||||
var roles []Role
|
||||
for _, roleF := range builtInRoles {
|
||||
role := roleF("random")
|
||||
_, scope, err := RoleSplit(role.Name)
|
||||
if err != nil {
|
||||
// This should never happen
|
||||
continue
|
||||
}
|
||||
if scope == "" {
|
||||
// Must provide some non-nil uuid to filter out org roles.
|
||||
role := roleF(uuid.New())
|
||||
if !role.Identifier.IsOrgRole() {
|
||||
roles = append(roles, role)
|
||||
}
|
||||
}
|
||||
@ -613,8 +639,8 @@ func SiteRoles() []Role {
|
||||
// removing roles. This set determines the changes, so that the appropriate
|
||||
// RBAC checks can be applied using "ActionCreate" and "ActionDelete" for
|
||||
// "added" and "removed" roles respectively.
|
||||
func ChangeRoleSet(from []string, to []string) (added []string, removed []string) {
|
||||
has := make(map[string]struct{})
|
||||
func ChangeRoleSet(from []RoleIdentifier, to []RoleIdentifier) (added []RoleIdentifier, removed []RoleIdentifier) {
|
||||
has := make(map[RoleIdentifier]struct{})
|
||||
for _, exists := range from {
|
||||
has[exists] = struct{}{}
|
||||
}
|
||||
@ -639,34 +665,6 @@ func ChangeRoleSet(from []string, to []string) (added []string, removed []string
|
||||
return added, removed
|
||||
}
|
||||
|
||||
// RoleName is a quick helper function to return
|
||||
//
|
||||
// role_name:scopeID
|
||||
//
|
||||
// If no scopeID is required, only 'role_name' is returned
|
||||
func RoleName(name string, orgID string) string {
|
||||
if orgID == "" {
|
||||
return name
|
||||
}
|
||||
return name + ":" + orgID
|
||||
}
|
||||
|
||||
func RoleSplit(role string) (name string, orgID string, err error) {
|
||||
arr := strings.Split(role, ":")
|
||||
if len(arr) > 2 {
|
||||
return "", "", xerrors.Errorf("too many colons in role name")
|
||||
}
|
||||
|
||||
if arr[0] == "" {
|
||||
return "", "", xerrors.Errorf("role cannot be the empty string")
|
||||
}
|
||||
|
||||
if len(arr) == 2 {
|
||||
return arr[0], arr[1], nil
|
||||
}
|
||||
return arr[0], "", nil
|
||||
}
|
||||
|
||||
// Permissions is just a helper function to make building roles that list out resources
|
||||
// and actions a bit easier.
|
||||
func Permissions(perms map[string][]policy.Action) []Permission {
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
// A possible large improvement would be to implement the ast.Value interface directly.
|
||||
func BenchmarkRBACValueAllocation(b *testing.B) {
|
||||
actor := Subject{
|
||||
Roles: RoleNames{ScopedRoleOrgMember(uuid.New()), ScopedRoleOrgAdmin(uuid.New()), RoleMember()},
|
||||
Roles: RoleIdentifiers{ScopedRoleOrgMember(uuid.New()), ScopedRoleOrgAdmin(uuid.New()), RoleMember()},
|
||||
ID: uuid.NewString(),
|
||||
Scope: ScopeAll,
|
||||
Groups: []string{uuid.NewString(), uuid.NewString(), uuid.NewString()},
|
||||
@ -73,7 +73,7 @@ func TestRegoInputValue(t *testing.T) {
|
||||
// Expand all roles and make sure we have a good copy.
|
||||
// This is because these tests modify the roles, and we don't want to
|
||||
// modify the original roles.
|
||||
roles, err := RoleNames{ScopedRoleOrgMember(uuid.New()), ScopedRoleOrgAdmin(uuid.New()), RoleMember()}.Expand()
|
||||
roles, err := RoleIdentifiers{ScopedRoleOrgMember(uuid.New()), ScopedRoleOrgAdmin(uuid.New()), RoleMember()}.Expand()
|
||||
require.NoError(t, err, "failed to expand roles")
|
||||
for i := range roles {
|
||||
// If all cached values are nil, then the role will not use
|
||||
@ -213,25 +213,25 @@ func TestRoleByName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Role Role
|
||||
}{
|
||||
{Role: builtInRoles[owner]("")},
|
||||
{Role: builtInRoles[member]("")},
|
||||
{Role: builtInRoles[templateAdmin]("")},
|
||||
{Role: builtInRoles[userAdmin]("")},
|
||||
{Role: builtInRoles[auditor]("")},
|
||||
{Role: builtInRoles[owner](uuid.Nil)},
|
||||
{Role: builtInRoles[member](uuid.Nil)},
|
||||
{Role: builtInRoles[templateAdmin](uuid.Nil)},
|
||||
{Role: builtInRoles[userAdmin](uuid.Nil)},
|
||||
{Role: builtInRoles[auditor](uuid.Nil)},
|
||||
|
||||
{Role: builtInRoles[orgAdmin]("4592dac5-0945-42fd-828d-a903957d3dbb")},
|
||||
{Role: builtInRoles[orgAdmin]("24c100c5-1920-49c0-8c38-1b640ac4b38c")},
|
||||
{Role: builtInRoles[orgAdmin]("4a00f697-0040-4079-b3ce-d24470281a62")},
|
||||
{Role: builtInRoles[orgAdmin](uuid.New())},
|
||||
{Role: builtInRoles[orgAdmin](uuid.New())},
|
||||
{Role: builtInRoles[orgAdmin](uuid.New())},
|
||||
|
||||
{Role: builtInRoles[orgMember]("3293c50e-fa5d-414f-a461-01112a4dfb6f")},
|
||||
{Role: builtInRoles[orgMember]("f88dd23d-bdbd-469d-b82e-36ee06c3d1e1")},
|
||||
{Role: builtInRoles[orgMember]("02cfd2a5-016c-4d8d-8290-301f5f18023d")},
|
||||
{Role: builtInRoles[orgMember](uuid.New())},
|
||||
{Role: builtInRoles[orgMember](uuid.New())},
|
||||
{Role: builtInRoles[orgMember](uuid.New())},
|
||||
}
|
||||
|
||||
for _, c := range testCases {
|
||||
c := c
|
||||
t.Run(c.Role.Name, func(t *testing.T) {
|
||||
role, err := RoleByName(c.Role.Name)
|
||||
t.Run(c.Role.Identifier.String(), func(t *testing.T) {
|
||||
role, err := RoleByName(c.Role.Identifier)
|
||||
require.NoError(t, err, "role exists")
|
||||
equalRoles(t, c.Role, role)
|
||||
})
|
||||
@ -242,20 +242,17 @@ func TestRoleByName(t *testing.T) {
|
||||
t.Run("Errors", func(t *testing.T) {
|
||||
var err error
|
||||
|
||||
_, err = RoleByName("")
|
||||
_, err = RoleByName(RoleIdentifier{})
|
||||
require.Error(t, err, "empty role")
|
||||
|
||||
_, err = RoleByName("too:many:colons")
|
||||
require.Error(t, err, "too many colons")
|
||||
|
||||
_, err = RoleByName(orgMember)
|
||||
_, err = RoleByName(RoleIdentifier{Name: orgMember})
|
||||
require.Error(t, err, "expect orgID")
|
||||
})
|
||||
}
|
||||
|
||||
// SameAs compares 2 roles for equality.
|
||||
func equalRoles(t *testing.T, a, b Role) {
|
||||
require.Equal(t, a.Name, b.Name, "role names")
|
||||
require.Equal(t, a.Identifier, b.Identifier, "role names")
|
||||
require.Equal(t, a.DisplayName, b.DisplayName, "role display names")
|
||||
require.ElementsMatch(t, a.Site, b.Site, "site permissions")
|
||||
require.ElementsMatch(t, a.User, b.User, "user permissions")
|
||||
|
@ -26,7 +26,7 @@ func TestBuiltInRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, r := range rbac.SiteRoles() {
|
||||
r := r
|
||||
t.Run(r.Name, func(t *testing.T) {
|
||||
t.Run(r.Identifier.String(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.NoError(t, r.Valid(), "invalid role")
|
||||
})
|
||||
@ -34,7 +34,7 @@ func TestBuiltInRoles(t *testing.T) {
|
||||
|
||||
for _, r := range rbac.OrganizationRoles(uuid.New()) {
|
||||
r := r
|
||||
t.Run(r.Name, func(t *testing.T) {
|
||||
t.Run(r.Identifier.String(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.NoError(t, r.Valid(), "invalid role")
|
||||
})
|
||||
@ -45,7 +45,7 @@ func TestBuiltInRoles(t *testing.T) {
|
||||
func TestOwnerExec(t *testing.T) {
|
||||
owner := rbac.Subject{
|
||||
ID: uuid.NewString(),
|
||||
Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOwner()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleOwner()},
|
||||
Scope: rbac.ScopeAll,
|
||||
}
|
||||
|
||||
@ -98,17 +98,17 @@ func TestRolePermissions(t *testing.T) {
|
||||
apiKeyID := uuid.New()
|
||||
|
||||
// Subjects to user
|
||||
memberMe := authSubject{Name: "member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleNames{rbac.RoleMember()}}}
|
||||
orgMemberMe := authSubject{Name: "org_member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID)}}}
|
||||
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)}}}
|
||||
|
||||
owner := authSubject{Name: "owner", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOwner()}}}
|
||||
orgAdmin := authSubject{Name: "org_admin", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID), rbac.ScopedRoleOrgAdmin(orgID)}}}
|
||||
owner := authSubject{Name: "owner", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleOwner()}}}
|
||||
orgAdmin := authSubject{Name: "org_admin", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID), rbac.ScopedRoleOrgAdmin(orgID)}}}
|
||||
|
||||
otherOrgMember := authSubject{Name: "org_member_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg)}}}
|
||||
otherOrgAdmin := authSubject{Name: "org_admin_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg), rbac.ScopedRoleOrgAdmin(otherOrg)}}}
|
||||
otherOrgMember := authSubject{Name: "org_member_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg)}}}
|
||||
otherOrgAdmin := authSubject{Name: "org_admin_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg), rbac.ScopedRoleOrgAdmin(otherOrg)}}}
|
||||
|
||||
templateAdmin := authSubject{Name: "template-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleTemplateAdmin()}}}
|
||||
userAdmin := authSubject{Name: "user-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleUserAdmin()}}}
|
||||
templateAdmin := authSubject{Name: "template-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleTemplateAdmin()}}}
|
||||
userAdmin := authSubject{Name: "user-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleUserAdmin()}}}
|
||||
|
||||
// requiredSubjects are required to be asserted in each test case. This is
|
||||
// to make sure one is not forgotten.
|
||||
@ -616,50 +616,40 @@ func TestIsOrgRole(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
RoleName string
|
||||
OrgRole bool
|
||||
OrgID string
|
||||
Identifier rbac.RoleIdentifier
|
||||
OrgRole bool
|
||||
OrgID uuid.UUID
|
||||
}{
|
||||
// Not org roles
|
||||
{RoleName: rbac.RoleOwner()},
|
||||
{RoleName: rbac.RoleMember()},
|
||||
{RoleName: "auditor"},
|
||||
|
||||
{Identifier: rbac.RoleOwner()},
|
||||
{Identifier: rbac.RoleMember()},
|
||||
{Identifier: rbac.RoleAuditor()},
|
||||
{
|
||||
RoleName: "a:bad:role",
|
||||
OrgRole: false,
|
||||
},
|
||||
{
|
||||
RoleName: "",
|
||||
OrgRole: false,
|
||||
Identifier: rbac.RoleIdentifier{},
|
||||
OrgRole: false,
|
||||
},
|
||||
|
||||
// Org roles
|
||||
{
|
||||
RoleName: rbac.ScopedRoleOrgAdmin(randomUUID),
|
||||
OrgRole: true,
|
||||
OrgID: randomUUID.String(),
|
||||
Identifier: rbac.ScopedRoleOrgAdmin(randomUUID),
|
||||
OrgRole: true,
|
||||
OrgID: randomUUID,
|
||||
},
|
||||
{
|
||||
RoleName: rbac.ScopedRoleOrgMember(randomUUID),
|
||||
OrgRole: true,
|
||||
OrgID: randomUUID.String(),
|
||||
},
|
||||
{
|
||||
RoleName: "test:example",
|
||||
OrgRole: true,
|
||||
OrgID: "example",
|
||||
Identifier: rbac.ScopedRoleOrgMember(randomUUID),
|
||||
OrgRole: true,
|
||||
OrgID: randomUUID,
|
||||
},
|
||||
}
|
||||
|
||||
// nolint:paralleltest
|
||||
for _, c := range testCases {
|
||||
c := c
|
||||
t.Run(c.RoleName, func(t *testing.T) {
|
||||
t.Run(c.Identifier.String(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
orgID, ok := rbac.IsOrgRole(c.RoleName)
|
||||
ok := c.Identifier.IsOrgRole()
|
||||
require.Equal(t, c.OrgRole, ok, "match expected org role")
|
||||
require.Equal(t, c.OrgID, orgID, "match expected org id")
|
||||
require.Equal(t, c.OrgID, c.Identifier.OrganizationID, "match expected org id")
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -670,7 +660,7 @@ func TestListRoles(t *testing.T) {
|
||||
siteRoles := rbac.SiteRoles()
|
||||
siteRoleNames := make([]string, 0, len(siteRoles))
|
||||
for _, role := range siteRoles {
|
||||
siteRoleNames = append(siteRoleNames, role.Name)
|
||||
siteRoleNames = append(siteRoleNames, role.Identifier.Name)
|
||||
}
|
||||
|
||||
// If this test is ever failing, just update the list to the roles
|
||||
@ -690,7 +680,7 @@ func TestListRoles(t *testing.T) {
|
||||
orgRoles := rbac.OrganizationRoles(orgID)
|
||||
orgRoleNames := make([]string, 0, len(orgRoles))
|
||||
for _, role := range orgRoles {
|
||||
orgRoleNames = append(orgRoleNames, role.Name)
|
||||
orgRoleNames = append(orgRoleNames, role.Identifier.String())
|
||||
}
|
||||
|
||||
require.ElementsMatch(t, []string{
|
||||
@ -738,13 +728,22 @@ func TestChangeSet(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
convert := func(s []string) rbac.RoleIdentifiers {
|
||||
tmp := make([]rbac.RoleIdentifier, 0, len(s))
|
||||
for _, e := range s {
|
||||
tmp = append(tmp, rbac.RoleIdentifier{Name: e})
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
for _, c := range testCases {
|
||||
c := c
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
add, remove := rbac.ChangeRoleSet(c.From, c.To)
|
||||
require.ElementsMatch(t, c.ExpAdd, add, "expect added")
|
||||
require.ElementsMatch(t, c.ExpRemove, remove, "expect removed")
|
||||
|
||||
add, remove := rbac.ChangeRoleSet(convert(c.From), convert(c.To))
|
||||
require.ElementsMatch(t, convert(c.ExpAdd), add, "expect added")
|
||||
require.ElementsMatch(t, convert(c.ExpRemove), remove, "expect removed")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -39,14 +39,14 @@ func roleCache(ctx context.Context) *syncmap.Map[string, rbac.Role] {
|
||||
}
|
||||
|
||||
// Expand will expand built in roles, and fetch custom roles from the database.
|
||||
func Expand(ctx context.Context, db database.Store, names []string) (rbac.Roles, error) {
|
||||
func Expand(ctx context.Context, db database.Store, names []rbac.RoleIdentifier) (rbac.Roles, error) {
|
||||
if len(names) == 0 {
|
||||
// That was easy
|
||||
return []rbac.Role{}, nil
|
||||
}
|
||||
|
||||
cache := roleCache(ctx)
|
||||
lookup := make([]string, 0)
|
||||
lookup := make([]rbac.RoleIdentifier, 0)
|
||||
roles := make([]rbac.Role, 0, len(names))
|
||||
|
||||
for _, name := range names {
|
||||
@ -58,7 +58,7 @@ func Expand(ctx context.Context, db database.Store, names []string) (rbac.Roles,
|
||||
}
|
||||
|
||||
// Check custom role cache
|
||||
customRole, ok := cache.Load(name)
|
||||
customRole, ok := cache.Load(name.String())
|
||||
if ok {
|
||||
roles = append(roles, customRole)
|
||||
continue
|
||||
@ -69,26 +69,11 @@ func Expand(ctx context.Context, db database.Store, names []string) (rbac.Roles,
|
||||
}
|
||||
|
||||
if len(lookup) > 0 {
|
||||
// The set of roles coming in are formatted as 'rolename[:<org_id>]'.
|
||||
// In the database, org roles are scoped with an organization column.
|
||||
lookupArgs := make([]database.NameOrganizationPair, 0, len(lookup))
|
||||
for _, name := range lookup {
|
||||
roleName, orgID, err := rbac.RoleSplit(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
parsedOrgID := uuid.Nil // Default to no org ID
|
||||
if orgID != "" {
|
||||
parsedOrgID, err = uuid.Parse(orgID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
lookupArgs = append(lookupArgs, database.NameOrganizationPair{
|
||||
Name: roleName,
|
||||
OrganizationID: parsedOrgID,
|
||||
Name: name.Name,
|
||||
OrganizationID: name.OrganizationID,
|
||||
})
|
||||
}
|
||||
|
||||
@ -111,7 +96,7 @@ func Expand(ctx context.Context, db database.Store, names []string) (rbac.Roles,
|
||||
return nil, xerrors.Errorf("convert db role %q: %w", dbrole.Name, err)
|
||||
}
|
||||
roles = append(roles, converted)
|
||||
cache.Store(dbrole.Name, converted)
|
||||
cache.Store(dbrole.RoleIdentifier().String(), converted)
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,12 +118,8 @@ func convertPermissions(dbPerms []database.CustomRolePermission) []rbac.Permissi
|
||||
// ConvertDBRole should not be used by any human facing apis. It is used
|
||||
// for authz purposes.
|
||||
func ConvertDBRole(dbRole database.CustomRole) (rbac.Role, error) {
|
||||
name := dbRole.Name
|
||||
if dbRole.OrganizationID.Valid {
|
||||
name = rbac.RoleName(dbRole.Name, dbRole.OrganizationID.UUID.String())
|
||||
}
|
||||
role := rbac.Role{
|
||||
Name: name,
|
||||
Identifier: dbRole.RoleIdentifier(),
|
||||
DisplayName: dbRole.DisplayName,
|
||||
Site: convertPermissions(dbRole.SitePermissions),
|
||||
Org: nil,
|
||||
|
@ -35,7 +35,7 @@ func TestExpandCustomRoleRoles(t *testing.T) {
|
||||
})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
roles, err := rolestore.Expand(ctx, db, []string{rbac.RoleName(roleName, org.ID.String())})
|
||||
roles, err := rolestore.Expand(ctx, db, []rbac.RoleIdentifier{{Name: roleName, OrganizationID: org.ID}})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, roles, 1, "role found")
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ var builtinScopes = map[ScopeName]Scope{
|
||||
// authorize checks it is usually not used directly and skips scope checks.
|
||||
ScopeAll: {
|
||||
Role: Role{
|
||||
Name: fmt.Sprintf("Scope_%s", ScopeAll),
|
||||
Identifier: RoleIdentifier{Name: fmt.Sprintf("Scope_%s", ScopeAll)},
|
||||
DisplayName: "All operations",
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
ResourceWildcard.Type: {policy.WildcardSymbol},
|
||||
@ -71,7 +71,7 @@ var builtinScopes = map[ScopeName]Scope{
|
||||
|
||||
ScopeApplicationConnect: {
|
||||
Role: Role{
|
||||
Name: fmt.Sprintf("Scope_%s", ScopeApplicationConnect),
|
||||
Identifier: RoleIdentifier{Name: fmt.Sprintf("Scope_%s", ScopeApplicationConnect)},
|
||||
DisplayName: "Ability to connect to applications",
|
||||
Site: Permissions(map[string][]policy.Action{
|
||||
ResourceWorkspace.Type: {policy.ActionApplicationConnect},
|
||||
@ -87,7 +87,7 @@ type ExpandableScope interface {
|
||||
Expand() (Scope, error)
|
||||
// Name is for logging and tracing purposes, we want to know the human
|
||||
// name of the scope.
|
||||
Name() string
|
||||
Name() RoleIdentifier
|
||||
}
|
||||
|
||||
type ScopeName string
|
||||
@ -96,8 +96,8 @@ func (name ScopeName) Expand() (Scope, error) {
|
||||
return ExpandScope(name)
|
||||
}
|
||||
|
||||
func (name ScopeName) Name() string {
|
||||
return string(name)
|
||||
func (name ScopeName) Name() RoleIdentifier {
|
||||
return RoleIdentifier{Name: string(name)}
|
||||
}
|
||||
|
||||
// Scope acts the exact same as a Role with the addition that is can also
|
||||
@ -114,8 +114,8 @@ func (s Scope) Expand() (Scope, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s Scope) Name() string {
|
||||
return s.Role.Name
|
||||
func (s Scope) Name() RoleIdentifier {
|
||||
return s.Role.Identifier
|
||||
}
|
||||
|
||||
func ExpandScope(scope ScopeName) (Scope, error) {
|
||||
|
@ -24,13 +24,13 @@ func TestSubjectEqual(t *testing.T) {
|
||||
Name: "Same",
|
||||
A: rbac.Subject{
|
||||
ID: "id",
|
||||
Roles: rbac.RoleNames{rbac.RoleMember()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.RoleMember()},
|
||||
Groups: []string{"group"},
|
||||
Scope: rbac.ScopeAll,
|
||||
},
|
||||
B: rbac.Subject{
|
||||
ID: "id",
|
||||
Roles: rbac.RoleNames{rbac.RoleMember()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.RoleMember()},
|
||||
Groups: []string{"group"},
|
||||
Scope: rbac.ScopeAll,
|
||||
},
|
||||
@ -49,7 +49,7 @@ func TestSubjectEqual(t *testing.T) {
|
||||
{
|
||||
Name: "RolesNilVs0",
|
||||
A: rbac.Subject{
|
||||
Roles: rbac.RoleNames{},
|
||||
Roles: rbac.RoleIdentifiers{},
|
||||
},
|
||||
B: rbac.Subject{
|
||||
Roles: nil,
|
||||
@ -69,20 +69,20 @@ func TestSubjectEqual(t *testing.T) {
|
||||
{
|
||||
Name: "DifferentRoles",
|
||||
A: rbac.Subject{
|
||||
Roles: rbac.RoleNames{rbac.RoleMember()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.RoleMember()},
|
||||
},
|
||||
B: rbac.Subject{
|
||||
Roles: rbac.RoleNames{rbac.RoleOwner()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.RoleOwner()},
|
||||
},
|
||||
Expected: false,
|
||||
},
|
||||
{
|
||||
Name: "Different#Roles",
|
||||
A: rbac.Subject{
|
||||
Roles: rbac.RoleNames{rbac.RoleMember()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.RoleMember()},
|
||||
},
|
||||
B: rbac.Subject{
|
||||
Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOwner()},
|
||||
Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleOwner()},
|
||||
},
|
||||
Expected: false,
|
||||
},
|
||||
|
@ -133,12 +133,12 @@ func assignableRoles(actorRoles rbac.ExpandableRoles, roles []rbac.Role, customR
|
||||
// The member role is implied, and not assignable.
|
||||
// If there is no display name, then the role is also unassigned.
|
||||
// This is not the ideal logic, but works for now.
|
||||
if role.Name == rbac.RoleMember() || (role.DisplayName == "") {
|
||||
if role.Identifier == rbac.RoleMember() || (role.DisplayName == "") {
|
||||
continue
|
||||
}
|
||||
assignable = append(assignable, codersdk.AssignableRoles{
|
||||
Role: db2sdk.RBACRole(role),
|
||||
Assignable: rbac.CanAssignRole(actorRoles, role.Name),
|
||||
Assignable: rbac.CanAssignRole(actorRoles, role.Identifier),
|
||||
BuiltIn: true,
|
||||
})
|
||||
}
|
||||
@ -146,7 +146,7 @@ func assignableRoles(actorRoles rbac.ExpandableRoles, roles []rbac.Role, customR
|
||||
for _, role := range customRoles {
|
||||
assignable = append(assignable, codersdk.AssignableRoles{
|
||||
Role: db2sdk.Role(role),
|
||||
Assignable: rbac.CanAssignRole(actorRoles, role.Name),
|
||||
Assignable: rbac.CanAssignRole(actorRoles, role.RoleIdentifier()),
|
||||
BuiltIn: false,
|
||||
})
|
||||
}
|
||||
|
@ -51,11 +51,11 @@ func TestListRoles(t *testing.T) {
|
||||
x, err := member.ListSiteRoles(ctx)
|
||||
return x, err
|
||||
},
|
||||
ExpectedRoles: convertRoles(map[string]bool{
|
||||
"owner": false,
|
||||
"auditor": false,
|
||||
"template-admin": false,
|
||||
"user-admin": false,
|
||||
ExpectedRoles: convertRoles(map[rbac.RoleIdentifier]bool{
|
||||
{Name: codersdk.RoleOwner}: false,
|
||||
{Name: codersdk.RoleAuditor}: false,
|
||||
{Name: codersdk.RoleTemplateAdmin}: false,
|
||||
{Name: codersdk.RoleUserAdmin}: false,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@ -63,8 +63,8 @@ func TestListRoles(t *testing.T) {
|
||||
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
|
||||
return member.ListOrganizationRoles(ctx, owner.OrganizationID)
|
||||
},
|
||||
ExpectedRoles: convertRoles(map[string]bool{
|
||||
rbac.ScopedRoleOrgAdmin(owner.OrganizationID): false,
|
||||
ExpectedRoles: convertRoles(map[rbac.RoleIdentifier]bool{
|
||||
{Name: codersdk.RoleOrganizationAdmin, OrganizationID: owner.OrganizationID}: false,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@ -80,11 +80,11 @@ func TestListRoles(t *testing.T) {
|
||||
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
|
||||
return orgAdmin.ListSiteRoles(ctx)
|
||||
},
|
||||
ExpectedRoles: convertRoles(map[string]bool{
|
||||
"owner": false,
|
||||
"auditor": false,
|
||||
"template-admin": false,
|
||||
"user-admin": false,
|
||||
ExpectedRoles: convertRoles(map[rbac.RoleIdentifier]bool{
|
||||
{Name: codersdk.RoleOwner}: false,
|
||||
{Name: codersdk.RoleAuditor}: false,
|
||||
{Name: codersdk.RoleTemplateAdmin}: false,
|
||||
{Name: codersdk.RoleUserAdmin}: false,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@ -92,8 +92,8 @@ func TestListRoles(t *testing.T) {
|
||||
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
|
||||
return orgAdmin.ListOrganizationRoles(ctx, owner.OrganizationID)
|
||||
},
|
||||
ExpectedRoles: convertRoles(map[string]bool{
|
||||
rbac.ScopedRoleOrgAdmin(owner.OrganizationID): true,
|
||||
ExpectedRoles: convertRoles(map[rbac.RoleIdentifier]bool{
|
||||
{Name: codersdk.RoleOrganizationAdmin, OrganizationID: owner.OrganizationID}: true,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@ -109,11 +109,11 @@ func TestListRoles(t *testing.T) {
|
||||
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
|
||||
return client.ListSiteRoles(ctx)
|
||||
},
|
||||
ExpectedRoles: convertRoles(map[string]bool{
|
||||
"owner": true,
|
||||
"auditor": true,
|
||||
"template-admin": true,
|
||||
"user-admin": true,
|
||||
ExpectedRoles: convertRoles(map[rbac.RoleIdentifier]bool{
|
||||
{Name: codersdk.RoleOwner}: true,
|
||||
{Name: codersdk.RoleAuditor}: true,
|
||||
{Name: codersdk.RoleTemplateAdmin}: true,
|
||||
{Name: codersdk.RoleUserAdmin}: true,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@ -121,8 +121,8 @@ func TestListRoles(t *testing.T) {
|
||||
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
|
||||
return client.ListOrganizationRoles(ctx, owner.OrganizationID)
|
||||
},
|
||||
ExpectedRoles: convertRoles(map[string]bool{
|
||||
rbac.ScopedRoleOrgAdmin(owner.OrganizationID): true,
|
||||
ExpectedRoles: convertRoles(map[rbac.RoleIdentifier]bool{
|
||||
{Name: codersdk.RoleOrganizationAdmin, OrganizationID: owner.OrganizationID}: true,
|
||||
}),
|
||||
},
|
||||
}
|
||||
@ -200,12 +200,12 @@ func TestListCustomRoles(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func convertRole(roleName string) codersdk.Role {
|
||||
func convertRole(roleName rbac.RoleIdentifier) codersdk.Role {
|
||||
role, _ := rbac.RoleByName(roleName)
|
||||
return db2sdk.RBACRole(role)
|
||||
}
|
||||
|
||||
func convertRoles(assignableRoles map[string]bool) []codersdk.AssignableRoles {
|
||||
func convertRoles(assignableRoles map[rbac.RoleIdentifier]bool) []codersdk.AssignableRoles {
|
||||
converted := make([]codersdk.AssignableRoles, 0, len(assignableRoles))
|
||||
for roleName, assignable := range assignableRoles {
|
||||
role := convertRole(roleName)
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/coderd/searchquery"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
@ -381,7 +380,7 @@ func TestSearchUsers(t *testing.T) {
|
||||
Expected: database.GetUsersParams{
|
||||
Search: "user-name",
|
||||
Status: []database.UserStatus{database.UserStatusActive},
|
||||
RbacRole: []string{rbac.RoleOwner()},
|
||||
RbacRole: []string{codersdk.RoleOwner},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -390,7 +389,7 @@ func TestSearchUsers(t *testing.T) {
|
||||
Expected: database.GetUsersParams{
|
||||
Search: "user name",
|
||||
Status: []database.UserStatus{database.UserStatusSuspended},
|
||||
RbacRole: []string{rbac.RoleMember()},
|
||||
RbacRole: []string{codersdk.RoleMember},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -399,7 +398,7 @@ func TestSearchUsers(t *testing.T) {
|
||||
Expected: database.GetUsersParams{
|
||||
Search: "user-name",
|
||||
Status: []database.UserStatus{database.UserStatusActive},
|
||||
RbacRole: []string{rbac.RoleOwner()},
|
||||
RbacRole: []string{codersdk.RoleOwner},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -240,9 +240,15 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
roleNames, err := roles.RoleNames()
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
userSubj := rbac.Subject{
|
||||
ID: user.ID.String(),
|
||||
Roles: rbac.RoleNames(roles.Roles),
|
||||
Roles: rbac.RoleIdentifiers(roleNames),
|
||||
Groups: roles.Groups,
|
||||
Scope: rbac.ScopeAll,
|
||||
}
|
||||
@ -1539,7 +1545,9 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
|
||||
ignored := make([]string, 0)
|
||||
filtered := make([]string, 0, len(params.Roles))
|
||||
for _, role := range params.Roles {
|
||||
if _, err := rbac.RoleByName(role); err == nil {
|
||||
// TODO: This only supports mapping deployment wide roles. Organization scoped roles
|
||||
// are unsupported.
|
||||
if _, err := rbac.RoleByName(rbac.RoleIdentifier{Name: role}); err == nil {
|
||||
filtered = append(filtered, role)
|
||||
} else {
|
||||
ignored = append(ignored, role)
|
||||
|
@ -223,7 +223,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
||||
// Add the admin role to this first user.
|
||||
//nolint:gocritic // needed to create first user
|
||||
_, err = api.Database.UpdateUserRoles(dbauthz.AsSystemRestricted(ctx), database.UpdateUserRolesParams{
|
||||
GrantedRoles: []string{rbac.RoleOwner()},
|
||||
GrantedRoles: []string{rbac.RoleOwner().String()},
|
||||
ID: user.ID,
|
||||
})
|
||||
if err != nil {
|
||||
@ -805,7 +805,7 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW
|
||||
Message: "You cannot suspend yourself.",
|
||||
})
|
||||
return
|
||||
case slice.Contains(user.RBACRoles, rbac.RoleOwner()):
|
||||
case slice.Contains(user.RBACRoles, rbac.RoleOwner().String()):
|
||||
// You may not suspend an owner
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: fmt.Sprintf("You cannot suspend a user with the %q role. You must remove the role first.", rbac.RoleOwner()),
|
||||
|
@ -994,7 +994,7 @@ func TestGrantSiteRoles(t *testing.T) {
|
||||
Name: "UserNotExists",
|
||||
Client: admin,
|
||||
AssignToUser: uuid.NewString(),
|
||||
Roles: []string{rbac.RoleOwner()},
|
||||
Roles: []string{codersdk.RoleOwner},
|
||||
Error: true,
|
||||
StatusCode: http.StatusBadRequest,
|
||||
},
|
||||
@ -1020,7 +1020,7 @@ func TestGrantSiteRoles(t *testing.T) {
|
||||
Client: admin,
|
||||
OrgID: first.OrganizationID,
|
||||
AssignToUser: codersdk.Me,
|
||||
Roles: []string{rbac.RoleOwner()},
|
||||
Roles: []string{codersdk.RoleOwner},
|
||||
Error: true,
|
||||
StatusCode: http.StatusBadRequest,
|
||||
},
|
||||
@ -1057,9 +1057,9 @@ func TestGrantSiteRoles(t *testing.T) {
|
||||
Name: "UserAdminMakeMember",
|
||||
Client: userAdmin,
|
||||
AssignToUser: newUser,
|
||||
Roles: []string{rbac.RoleMember()},
|
||||
Roles: []string{codersdk.RoleMember},
|
||||
ExpectedRoles: []string{
|
||||
rbac.RoleMember(),
|
||||
codersdk.RoleMember,
|
||||
},
|
||||
Error: false,
|
||||
},
|
||||
@ -1124,7 +1124,7 @@ func TestInitialRoles(t *testing.T) {
|
||||
roles, err := client.UserRoles(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, roles.Roles, []string{
|
||||
rbac.RoleOwner(),
|
||||
codersdk.RoleOwner,
|
||||
}, "should be a member and admin")
|
||||
|
||||
require.ElementsMatch(t, roles.OrganizationRoles[first.OrganizationID], []string{}, "should be a member")
|
||||
@ -1289,12 +1289,12 @@ func TestUsersFilter(t *testing.T) {
|
||||
users := make([]codersdk.User, 0)
|
||||
users = append(users, firstUser)
|
||||
for i := 0; i < 15; i++ {
|
||||
roles := []string{}
|
||||
roles := []rbac.RoleIdentifier{}
|
||||
if i%2 == 0 {
|
||||
roles = append(roles, rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin())
|
||||
}
|
||||
if i%3 == 0 {
|
||||
roles = append(roles, "auditor")
|
||||
roles = append(roles, rbac.RoleAuditor())
|
||||
}
|
||||
userClient, userData := coderdtest.CreateAnotherUser(t, client, first.OrganizationID, roles...)
|
||||
// Set the last seen for each user to a unique day
|
||||
@ -1379,12 +1379,12 @@ func TestUsersFilter(t *testing.T) {
|
||||
{
|
||||
Name: "Admins",
|
||||
Filter: codersdk.UsersRequest{
|
||||
Role: rbac.RoleOwner(),
|
||||
Role: codersdk.RoleOwner,
|
||||
Status: codersdk.UserStatusSuspended + "," + codersdk.UserStatusActive,
|
||||
},
|
||||
FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool {
|
||||
for _, r := range u.Roles {
|
||||
if r.Name == rbac.RoleOwner() {
|
||||
if r.Name == codersdk.RoleOwner {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1399,7 +1399,7 @@ func TestUsersFilter(t *testing.T) {
|
||||
},
|
||||
FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool {
|
||||
for _, r := range u.Roles {
|
||||
if r.Name == rbac.RoleOwner() {
|
||||
if r.Name == codersdk.RoleOwner {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1409,7 +1409,7 @@ func TestUsersFilter(t *testing.T) {
|
||||
{
|
||||
Name: "Members",
|
||||
Filter: codersdk.UsersRequest{
|
||||
Role: rbac.RoleMember(),
|
||||
Role: codersdk.RoleMember,
|
||||
Status: codersdk.UserStatusSuspended + "," + codersdk.UserStatusActive,
|
||||
},
|
||||
FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool {
|
||||
@ -1423,7 +1423,7 @@ func TestUsersFilter(t *testing.T) {
|
||||
},
|
||||
FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool {
|
||||
for _, r := range u.Roles {
|
||||
if r.Name == rbac.RoleOwner() {
|
||||
if r.Name == codersdk.RoleOwner {
|
||||
return (strings.ContainsAny(u.Username, "iI") || strings.ContainsAny(u.Email, "iI")) &&
|
||||
u.Status == codersdk.UserStatusActive
|
||||
}
|
||||
@ -1438,7 +1438,7 @@ func TestUsersFilter(t *testing.T) {
|
||||
},
|
||||
FilterF: func(_ codersdk.UsersRequest, u codersdk.User) bool {
|
||||
for _, r := range u.Roles {
|
||||
if r.Name == rbac.RoleOwner() {
|
||||
if r.Name == codersdk.RoleOwner {
|
||||
return (strings.ContainsAny(u.Username, "iI") || strings.ContainsAny(u.Email, "iI")) &&
|
||||
u.Status == codersdk.UserStatusActive
|
||||
}
|
||||
|
@ -555,7 +555,7 @@ func (api *API) verifyUserCanCancelWorkspaceBuilds(ctx context.Context, userID u
|
||||
if err != nil {
|
||||
return false, xerrors.New("user does not exist")
|
||||
}
|
||||
return slices.Contains(user.RBACRoles, rbac.RoleOwner()), nil // only user with "owner" role can cancel workspace builds
|
||||
return slices.Contains(user.RBACRoles, rbac.RoleOwner().String()), nil // only user with "owner" role can cancel workspace builds
|
||||
}
|
||||
|
||||
// @Summary Get build parameters for workspace build
|
||||
|
@ -224,7 +224,7 @@ func TestWorkspaceBuilds(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
first := coderdtest.CreateFirstUser(t, client)
|
||||
second, secondUser := coderdtest.CreateAnotherUser(t, client, first.OrganizationID, "owner")
|
||||
second, secondUser := coderdtest.CreateAnotherUser(t, client, first.OrganizationID, rbac.RoleOwner())
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
@ -484,7 +484,7 @@ func TestWorkspacesSortOrder(t *testing.T) {
|
||||
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
secondUserClient, secondUser := coderdtest.CreateAnotherUserMutators(t, client, firstUser.OrganizationID, []string{"owner"}, func(r *codersdk.CreateUserRequest) {
|
||||
secondUserClient, secondUser := coderdtest.CreateAnotherUserMutators(t, client, firstUser.OrganizationID, []rbac.RoleIdentifier{rbac.RoleOwner()}, func(r *codersdk.CreateUserRequest) {
|
||||
r.Username = "zzz"
|
||||
})
|
||||
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
@ -16,7 +17,6 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/database/pubsub"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/cryptorand"
|
||||
)
|
||||
|
||||
@ -177,7 +177,7 @@ func setupDeps(t *testing.T, store database.Store, ps pubsub.Pubsub) deps {
|
||||
_, err := store.InsertOrganizationMember(context.Background(), database.InsertOrganizationMemberParams{
|
||||
OrganizationID: org.ID,
|
||||
UserID: user.ID,
|
||||
Roles: []string{rbac.ScopedRoleOrgMember(org.ID)},
|
||||
Roles: []string{codersdk.RoleOrganizationMember},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
tv := dbgen.TemplateVersion(t, store, database.TemplateVersion{
|
||||
|
Reference in New Issue
Block a user