Files
coder/coderd/roles_test.go
Steven Masley 49d6d0f41b chore: add built in organization roles to match site (#13938)
* chore: add built in organization roles to match site

Added org user admin, org template admin, and org auditor
2024-07-19 15:44:18 -05:00

228 lines
7.4 KiB
Go

package coderd_test
import (
"context"
"net/http"
"slices"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/db2sdk"
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/rbac/policy"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
)
func TestListRoles(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
// Create owner, member, and org admin
owner := coderdtest.CreateFirstUser(t, client)
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
orgAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID))
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
t.Cleanup(cancel)
otherOrg, err := client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
Name: "other",
})
require.NoError(t, err, "create org")
const notFound = "Resource not found"
testCases := []struct {
Name string
Client *codersdk.Client
APICall func(context.Context) ([]codersdk.AssignableRoles, error)
ExpectedRoles []codersdk.AssignableRoles
AuthorizedError string
}{
{
// Members cannot assign any roles
Name: "MemberListSite",
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
x, err := member.ListSiteRoles(ctx)
return x, err
},
ExpectedRoles: convertRoles(map[rbac.RoleIdentifier]bool{
{Name: codersdk.RoleOwner}: false,
{Name: codersdk.RoleAuditor}: false,
{Name: codersdk.RoleTemplateAdmin}: false,
{Name: codersdk.RoleUserAdmin}: false,
}),
},
{
Name: "OrgMemberListOrg",
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
return member.ListOrganizationRoles(ctx, owner.OrganizationID)
},
ExpectedRoles: convertRoles(map[rbac.RoleIdentifier]bool{
{Name: codersdk.RoleOrganizationAdmin, OrganizationID: owner.OrganizationID}: false,
{Name: codersdk.RoleOrganizationAuditor, OrganizationID: owner.OrganizationID}: false,
{Name: codersdk.RoleOrganizationTemplateAdmin, OrganizationID: owner.OrganizationID}: false,
{Name: codersdk.RoleOrganizationUserAdmin, OrganizationID: owner.OrganizationID}: false,
}),
},
{
Name: "NonOrgMemberListOrg",
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
return member.ListOrganizationRoles(ctx, otherOrg.ID)
},
AuthorizedError: notFound,
},
// Org admin
{
Name: "OrgAdminListSite",
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
return orgAdmin.ListSiteRoles(ctx)
},
ExpectedRoles: convertRoles(map[rbac.RoleIdentifier]bool{
{Name: codersdk.RoleOwner}: false,
{Name: codersdk.RoleAuditor}: false,
{Name: codersdk.RoleTemplateAdmin}: false,
{Name: codersdk.RoleUserAdmin}: false,
}),
},
{
Name: "OrgAdminListOrg",
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
return orgAdmin.ListOrganizationRoles(ctx, owner.OrganizationID)
},
ExpectedRoles: convertRoles(map[rbac.RoleIdentifier]bool{
{Name: codersdk.RoleOrganizationAdmin, OrganizationID: owner.OrganizationID}: true,
{Name: codersdk.RoleOrganizationAuditor, OrganizationID: owner.OrganizationID}: true,
{Name: codersdk.RoleOrganizationTemplateAdmin, OrganizationID: owner.OrganizationID}: true,
{Name: codersdk.RoleOrganizationUserAdmin, OrganizationID: owner.OrganizationID}: true,
}),
},
{
Name: "OrgAdminListOtherOrg",
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
return orgAdmin.ListOrganizationRoles(ctx, otherOrg.ID)
},
AuthorizedError: notFound,
},
// Admin
{
Name: "AdminListSite",
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
return client.ListSiteRoles(ctx)
},
ExpectedRoles: convertRoles(map[rbac.RoleIdentifier]bool{
{Name: codersdk.RoleOwner}: true,
{Name: codersdk.RoleAuditor}: true,
{Name: codersdk.RoleTemplateAdmin}: true,
{Name: codersdk.RoleUserAdmin}: true,
}),
},
{
Name: "AdminListOrg",
APICall: func(ctx context.Context) ([]codersdk.AssignableRoles, error) {
return client.ListOrganizationRoles(ctx, owner.OrganizationID)
},
ExpectedRoles: convertRoles(map[rbac.RoleIdentifier]bool{
{Name: codersdk.RoleOrganizationAdmin, OrganizationID: owner.OrganizationID}: true,
{Name: codersdk.RoleOrganizationAuditor, OrganizationID: owner.OrganizationID}: true,
{Name: codersdk.RoleOrganizationTemplateAdmin, OrganizationID: owner.OrganizationID}: true,
{Name: codersdk.RoleOrganizationUserAdmin, OrganizationID: owner.OrganizationID}: true,
}),
},
}
for _, c := range testCases {
c := c
t.Run(c.Name, func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
roles, err := c.APICall(ctx)
if c.AuthorizedError != "" {
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
require.Contains(t, apiErr.Message, c.AuthorizedError)
} else {
require.NoError(t, err)
ignorePerms := func(f codersdk.AssignableRoles) codersdk.AssignableRoles {
return codersdk.AssignableRoles{
Role: codersdk.Role{
Name: f.Name,
DisplayName: f.DisplayName,
},
Assignable: f.Assignable,
BuiltIn: true,
}
}
expected := db2sdk.List(c.ExpectedRoles, ignorePerms)
found := db2sdk.List(roles, ignorePerms)
require.ElementsMatch(t, expected, found)
}
})
}
}
func TestListCustomRoles(t *testing.T) {
t.Parallel()
t.Run("Organizations", func(t *testing.T) {
t.Parallel()
client, db := coderdtest.NewWithDatabase(t, nil)
owner := coderdtest.CreateFirstUser(t, client)
const roleName = "random_role"
dbgen.CustomRole(t, db, database.CustomRole{
Name: roleName,
DisplayName: "Random Role",
OrganizationID: uuid.NullUUID{
UUID: owner.OrganizationID,
Valid: true,
},
SitePermissions: nil,
OrgPermissions: []database.CustomRolePermission{
{
Negate: false,
ResourceType: rbac.ResourceWorkspace.Type,
Action: policy.ActionRead,
},
},
UserPermissions: nil,
})
ctx := testutil.Context(t, testutil.WaitShort)
roles, err := client.ListOrganizationRoles(ctx, owner.OrganizationID)
require.NoError(t, err)
found := slices.ContainsFunc(roles, func(element codersdk.AssignableRoles) bool {
return element.Name == roleName && element.OrganizationID == owner.OrganizationID.String()
})
require.Truef(t, found, "custom organization role listed")
})
}
func convertRole(roleName rbac.RoleIdentifier) codersdk.Role {
role, _ := rbac.RoleByName(roleName)
return db2sdk.RBACRole(role)
}
func convertRoles(assignableRoles map[rbac.RoleIdentifier]bool) []codersdk.AssignableRoles {
converted := make([]codersdk.AssignableRoles, 0, len(assignableRoles))
for roleName, assignable := range assignableRoles {
role := convertRole(roleName)
converted = append(converted, codersdk.AssignableRoles{
Role: role,
Assignable: assignable,
})
}
return converted
}