coder/enterprise/coderd/templates_test.go

959 lines
30 KiB
Go

package coderd_test
import (
"context"
"net/http"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/audit"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/rbac"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/cryptorand"
"github.com/coder/coder/enterprise/coderd/coderdenttest"
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/testutil"
)
func TestTemplateACL(t *testing.T) {
t.Parallel()
t.Run("UserRoles", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
_, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
_, user3 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, _ := testutil.Context(t)
err := client.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user2.ID.String(): codersdk.TemplateRoleUse,
user3.ID.String(): codersdk.TemplateRoleAdmin,
},
})
require.NoError(t, err)
acl, err := client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
templateUser2 := codersdk.TemplateUser{
User: user2,
Role: codersdk.TemplateRoleUse,
}
templateUser3 := codersdk.TemplateUser{
User: user3,
Role: codersdk.TemplateRoleAdmin,
}
require.Len(t, acl.Users, 2)
require.Contains(t, acl.Users, templateUser2)
require.Contains(t, acl.Users, templateUser3)
})
t.Run("allUsersGroup", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
_, user1 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
acl, err := client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Len(t, acl.Groups, 1)
require.Len(t, acl.Groups[0].Members, 2)
require.Contains(t, acl.Groups[0].Members, user1)
require.Len(t, acl.Users, 0)
})
t.Run("NoGroups", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
client1, _ := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
acl, err := client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Len(t, acl.Groups, 1)
require.Len(t, acl.Users, 0)
// User should be able to read template due to allUsers group.
_, err = client1.Template(ctx, template.ID)
require.NoError(t, err)
allUsers := acl.Groups[0]
err = client.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{
GroupPerms: map[string]codersdk.TemplateRole{
allUsers.ID.String(): codersdk.TemplateRoleDeleted,
},
})
require.NoError(t, err)
acl, err = client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Len(t, acl.Groups, 0)
require.Len(t, acl.Users, 0)
// User should not be able to read template due to allUsers group being deleted.
_, err = client1.Template(ctx, template.ID)
require.Error(t, err)
cerr, ok := codersdk.AsError(err)
require.True(t, ok)
require.Equal(t, http.StatusNotFound, cerr.StatusCode())
})
// Test that we do not return deleted users.
t.Run("FilterDeletedUsers", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
_, user1 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, _ := testutil.Context(t)
err := client.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user1.ID.String(): codersdk.TemplateRoleUse,
},
})
require.NoError(t, err)
acl, err := client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Contains(t, acl.Users, codersdk.TemplateUser{
User: user1,
Role: codersdk.TemplateRoleUse,
})
err = client.DeleteUser(ctx, user1.ID)
require.NoError(t, err)
acl, err = client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Len(t, acl.Users, 0, "deleted users should be filtered")
})
// Test that we do not return suspended users.
t.Run("FilterSuspendedUsers", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
_, user1 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, _ := testutil.Context(t)
err := client.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user1.ID.String(): codersdk.TemplateRoleUse,
},
})
require.NoError(t, err)
acl, err := client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Contains(t, acl.Users, codersdk.TemplateUser{
User: user1,
Role: codersdk.TemplateRoleUse,
})
_, err = client.UpdateUserStatus(ctx, user1.ID.String(), codersdk.UserStatusSuspended)
require.NoError(t, err)
acl, err = client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Len(t, acl.Users, 0, "suspended users should be filtered")
})
// Test that we do not return deleted groups.
t.Run("FilterDeletedGroups", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, _ := testutil.Context(t)
group, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{
Name: "test",
})
require.NoError(t, err)
err = client.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{
GroupPerms: map[string]codersdk.TemplateRole{
group.ID.String(): codersdk.TemplateRoleUse,
},
})
require.NoError(t, err)
acl, err := client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
// Length should be 2 for test group and the implicit allUsers group.
require.Len(t, acl.Groups, 2)
require.Contains(t, acl.Groups, codersdk.TemplateGroup{
Group: group,
Role: codersdk.TemplateRoleUse,
})
err = client.DeleteGroup(ctx, group.ID)
require.NoError(t, err)
acl, err = client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
// Length should be 1 for the allUsers group.
require.Len(t, acl.Groups, 1)
require.NotContains(t, acl.Groups, codersdk.TemplateGroup{
Group: group,
Role: codersdk.TemplateRoleUse,
})
})
t.Run("AdminCanPushVersions", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
client1, user1 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, _ := testutil.Context(t)
err := client.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user1.ID.String(): codersdk.TemplateRoleUse,
},
})
require.NoError(t, err)
data, err := echo.Tar(nil)
require.NoError(t, err)
file, err := client1.Upload(context.Background(), codersdk.ContentTypeTar, data)
require.NoError(t, err)
_, err = client1.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{
Name: "testme",
TemplateID: template.ID,
FileID: file.ID,
StorageMethod: codersdk.ProvisionerStorageMethodFile,
Provisioner: codersdk.ProvisionerTypeEcho,
})
require.Error(t, err)
err = client.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user1.ID.String(): codersdk.TemplateRoleAdmin,
},
})
require.NoError(t, err)
_, err = client1.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{
Name: "testme",
TemplateID: template.ID,
FileID: file.ID,
StorageMethod: codersdk.ProvisionerStorageMethodFile,
Provisioner: codersdk.ProvisionerTypeEcho,
})
require.NoError(t, err)
})
}
func TestUpdateTemplateACL(t *testing.T) {
t.Parallel()
t.Run("UserPerms", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
_, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
_, user3 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
err := client.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user2.ID.String(): codersdk.TemplateRoleUse,
user3.ID.String(): codersdk.TemplateRoleAdmin,
},
})
require.NoError(t, err)
acl, err := client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
templateUser2 := codersdk.TemplateUser{
User: user2,
Role: codersdk.TemplateRoleUse,
}
templateUser3 := codersdk.TemplateUser{
User: user3,
Role: codersdk.TemplateRoleAdmin,
}
require.Len(t, acl.Users, 2)
require.Contains(t, acl.Users, templateUser2)
require.Contains(t, acl.Users, templateUser3)
})
t.Run("Audit", func(t *testing.T) {
t.Parallel()
auditor := audit.NewMock()
client := coderdenttest.New(t, &coderdenttest.Options{
AuditLogging: true,
Options: &coderdtest.Options{
IncludeProvisionerDaemon: true,
Auditor: auditor,
},
})
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
AuditLog: true,
})
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, _ := testutil.Context(t)
numLogs := len(auditor.AuditLogs)
req := codersdk.UpdateTemplateACL{
GroupPerms: map[string]codersdk.TemplateRole{
user.OrganizationID.String(): codersdk.TemplateRoleDeleted,
},
}
err := client.UpdateTemplateACL(ctx, template.ID, req)
require.NoError(t, err)
numLogs++
require.Len(t, auditor.AuditLogs, numLogs)
require.Equal(t, database.AuditActionWrite, auditor.AuditLogs[numLogs-1].Action)
require.Equal(t, template.ID, auditor.AuditLogs[numLogs-1].ResourceID)
})
t.Run("DeleteUser", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
_, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
_, user3 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
req := codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user2.ID.String(): codersdk.TemplateRoleUse,
user3.ID.String(): codersdk.TemplateRoleAdmin,
},
}
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
err := client.UpdateTemplateACL(ctx, template.ID, req)
require.NoError(t, err)
acl, err := client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Contains(t, acl.Users, codersdk.TemplateUser{
User: user2,
Role: codersdk.TemplateRoleUse,
})
require.Contains(t, acl.Users, codersdk.TemplateUser{
User: user3,
Role: codersdk.TemplateRoleAdmin,
})
req = codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user2.ID.String(): codersdk.TemplateRoleAdmin,
user3.ID.String(): codersdk.TemplateRoleDeleted,
},
}
err = client.UpdateTemplateACL(ctx, template.ID, req)
require.NoError(t, err)
acl, err = client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Contains(t, acl.Users, codersdk.TemplateUser{
User: user2,
Role: codersdk.TemplateRoleAdmin,
})
require.NotContains(t, acl.Users, codersdk.TemplateUser{
User: user3,
Role: codersdk.TemplateRoleAdmin,
})
})
t.Run("InvalidUUID", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
req := codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
"hi": "admin",
},
}
ctx, _ := testutil.Context(t)
err := client.UpdateTemplateACL(ctx, template.ID, req)
require.Error(t, err)
cerr, _ := codersdk.AsError(err)
require.Equal(t, http.StatusBadRequest, cerr.StatusCode())
})
t.Run("InvalidUser", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
req := codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
uuid.NewString(): "admin",
},
}
ctx, _ := testutil.Context(t)
err := client.UpdateTemplateACL(ctx, template.ID, req)
require.Error(t, err)
cerr, _ := codersdk.AsError(err)
require.Equal(t, http.StatusBadRequest, cerr.StatusCode())
})
t.Run("InvalidRole", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
_, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
req := codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user2.ID.String(): "updater",
},
}
ctx, _ := testutil.Context(t)
err := client.UpdateTemplateACL(ctx, template.ID, req)
require.Error(t, err)
cerr, _ := codersdk.AsError(err)
require.Equal(t, http.StatusBadRequest, cerr.StatusCode())
})
t.Run("RegularUserCannotUpdatePerms", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
client2, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
req := codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user2.ID.String(): codersdk.TemplateRoleUse,
},
}
ctx, _ := testutil.Context(t)
err := client.UpdateTemplateACL(ctx, template.ID, req)
require.NoError(t, err)
req = codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user2.ID.String(): codersdk.TemplateRoleAdmin,
},
}
err = client2.UpdateTemplateACL(ctx, template.ID, req)
require.Error(t, err)
cerr, _ := codersdk.AsError(err)
require.Equal(t, http.StatusNotFound, cerr.StatusCode())
})
t.Run("RegularUserWithAdminCanUpdate", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
client2, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
_, user3 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
req := codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user2.ID.String(): codersdk.TemplateRoleAdmin,
},
}
ctx, _ := testutil.Context(t)
err := client.UpdateTemplateACL(ctx, template.ID, req)
require.NoError(t, err)
req = codersdk.UpdateTemplateACL{
UserPerms: map[string]codersdk.TemplateRole{
user3.ID.String(): codersdk.TemplateRoleUse,
},
}
err = client2.UpdateTemplateACL(ctx, template.ID, req)
require.NoError(t, err)
acl, err := client2.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Contains(t, acl.Users, codersdk.TemplateUser{
User: user3,
Role: codersdk.TemplateRoleUse,
})
})
t.Run("allUsersGroup", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
acl, err := client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Len(t, acl.Groups, 1)
require.Len(t, acl.Users, 0)
})
t.Run("CustomGroupHasAccess", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
client1, user1 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, _ := testutil.Context(t)
// Create a group to add to the template.
group, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{
Name: "test",
})
require.NoError(t, err)
// Check that the only current group is the allUsers group.
acl, err := client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Len(t, acl.Groups, 1)
// Update the template to only allow access to the 'test' group.
err = client.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{
GroupPerms: map[string]codersdk.TemplateRole{
// The allUsers group shares the same ID as the organization.
user.OrganizationID.String(): codersdk.TemplateRoleDeleted,
group.ID.String(): codersdk.TemplateRoleUse,
},
})
require.NoError(t, err)
// Get the ACL list for the template and assert the test group is
// present.
acl, err = client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Len(t, acl.Groups, 1)
require.Len(t, acl.Users, 0)
require.Equal(t, group.ID, acl.Groups[0].ID)
// Try to get the template as the regular user. This should
// fail since we haven't been added to the template yet.
_, err = client1.Template(ctx, template.ID)
require.Error(t, err)
cerr, ok := codersdk.AsError(err)
require.True(t, ok)
require.Equal(t, http.StatusNotFound, cerr.StatusCode())
// Patch the group to add the regular user.
group, err = client.PatchGroup(ctx, group.ID, codersdk.PatchGroupRequest{
AddUsers: []string{user1.ID.String()},
})
require.NoError(t, err)
require.Len(t, group.Members, 1)
require.Equal(t, user1.ID, group.Members[0].ID)
// Fetching the template should succeed since our group has view access.
_, err = client1.Template(ctx, template.ID)
require.NoError(t, err)
})
t.Run("NoAccess", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
client1, _ := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
acl, err := client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Len(t, acl.Groups, 1)
require.Len(t, acl.Users, 0)
// User should be able to read template due to allUsers group.
_, err = client1.Template(ctx, template.ID)
require.NoError(t, err)
allUsers := acl.Groups[0]
err = client.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{
GroupPerms: map[string]codersdk.TemplateRole{
allUsers.ID.String(): codersdk.TemplateRoleDeleted,
},
})
require.NoError(t, err)
acl, err = client.TemplateACL(ctx, template.ID)
require.NoError(t, err)
require.Len(t, acl.Groups, 0)
require.Len(t, acl.Users, 0)
// User should not be able to read template due to allUsers group being deleted.
_, err = client1.Template(ctx, template.ID)
require.Error(t, err)
cerr, ok := codersdk.AsError(err)
require.True(t, ok)
require.Equal(t, http.StatusNotFound, cerr.StatusCode())
})
}
// TestTemplateAccess tests the rego -> sql conversion. We need to implement
// this test on at least 1 table type to ensure that the conversion is correct.
// The rbac tests only assert against static SQL queries.
// This is a full rbac test of many of the common role combinations.
//
//nolint:tparallel
func TestTemplateAccess(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
t.Cleanup(cancel)
ownerClient := coderdenttest.New(t, nil)
owner := coderdtest.CreateFirstUser(t, ownerClient)
_ = coderdenttest.AddLicense(t, ownerClient, coderdenttest.LicenseOptions{
TemplateRBAC: true,
})
type coderUser struct {
*codersdk.Client
User codersdk.User
}
type orgSetup struct {
Admin coderUser
MemberInGroup coderUser
MemberNoGroup coderUser
DefaultTemplate codersdk.Template
AllRead codersdk.Template
UserACL codersdk.Template
GroupACL codersdk.Template
Group codersdk.Group
Org codersdk.Organization
}
// Create the following users
// - owner: Site wide owner
// - template-admin
// - org-admin (org 1)
// - org-admin (org 2)
// - org-member (org 1)
// - org-member (org 2)
// Create the following templates in each org
// - template 1, default acls
// - template 2, all_user read
// - template 3, user_acl read for member
// - template 4, group_acl read for groupMember
templateAdmin := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
makeTemplate := func(t *testing.T, client *codersdk.Client, orgID uuid.UUID, acl codersdk.UpdateTemplateACL) codersdk.Template {
version := coderdtest.CreateTemplateVersion(t, client, orgID, nil)
template := coderdtest.CreateTemplate(t, client, orgID, version.ID)
err := client.UpdateTemplateACL(ctx, template.ID, acl)
require.NoError(t, err, "failed to update template acl")
return template
}
makeOrg := func(t *testing.T) orgSetup {
// Make org
orgName, err := cryptorand.String(5)
require.NoError(t, err, "org name")
// Make users
newOrg, err := ownerClient.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{Name: orgName})
require.NoError(t, err, "failed to create org")
adminCli, adminUsr := coderdtest.CreateAnotherUserWithUser(t, ownerClient, newOrg.ID, rbac.RoleOrgAdmin(newOrg.ID))
groupMemCli, groupMemUsr := coderdtest.CreateAnotherUserWithUser(t, ownerClient, newOrg.ID, rbac.RoleOrgMember(newOrg.ID))
memberCli, memberUsr := coderdtest.CreateAnotherUserWithUser(t, ownerClient, newOrg.ID, rbac.RoleOrgMember(newOrg.ID))
// Make group
group, err := adminCli.CreateGroup(ctx, newOrg.ID, codersdk.CreateGroupRequest{
Name: "SingleUser",
})
require.NoError(t, err, "failed to create group")
group, err = adminCli.PatchGroup(ctx, group.ID, codersdk.PatchGroupRequest{
AddUsers: []string{groupMemUsr.ID.String()},
})
require.NoError(t, err, "failed to add user to group")
// Make templates
return orgSetup{
Admin: coderUser{Client: adminCli, User: adminUsr},
MemberInGroup: coderUser{Client: groupMemCli, User: groupMemUsr},
MemberNoGroup: coderUser{Client: memberCli, User: memberUsr},
Org: newOrg,
Group: group,
DefaultTemplate: makeTemplate(t, adminCli, newOrg.ID, codersdk.UpdateTemplateACL{
GroupPerms: map[string]codersdk.TemplateRole{
newOrg.ID.String(): codersdk.TemplateRoleDeleted,
},
}),
AllRead: makeTemplate(t, adminCli, newOrg.ID, codersdk.UpdateTemplateACL{
GroupPerms: map[string]codersdk.TemplateRole{
newOrg.ID.String(): codersdk.TemplateRoleUse,
},
}),
UserACL: makeTemplate(t, adminCli, newOrg.ID, codersdk.UpdateTemplateACL{
GroupPerms: map[string]codersdk.TemplateRole{
newOrg.ID.String(): codersdk.TemplateRoleDeleted,
},
UserPerms: map[string]codersdk.TemplateRole{
memberUsr.ID.String(): codersdk.TemplateRoleUse,
},
}),
GroupACL: makeTemplate(t, adminCli, newOrg.ID, codersdk.UpdateTemplateACL{
GroupPerms: map[string]codersdk.TemplateRole{
group.ID.String(): codersdk.TemplateRoleUse,
newOrg.ID.String(): codersdk.TemplateRoleDeleted,
},
}),
}
}
// Make 2 organizations
orgs := []orgSetup{
makeOrg(t),
makeOrg(t),
}
testTemplateRead := func(t *testing.T, org orgSetup, usr *codersdk.Client, read []codersdk.Template) {
found, err := usr.TemplatesByOrganization(ctx, org.Org.ID)
require.NoError(t, err, "failed to get templates")
exp := make(map[uuid.UUID]codersdk.Template)
for _, tmpl := range read {
exp[tmpl.ID] = tmpl
}
for _, f := range found {
if _, ok := exp[f.ID]; !ok {
t.Errorf("found unexpected template %q", f.Name)
}
delete(exp, f.ID)
}
require.Len(t, exp, 0, "expected templates not found")
}
// nolint:paralleltest
t.Run("OwnerReadAll", func(t *testing.T) {
for _, o := range orgs {
// Owners can read all templates in all orgs
exp := []codersdk.Template{o.DefaultTemplate, o.AllRead, o.UserACL, o.GroupACL}
testTemplateRead(t, o, ownerClient, exp)
}
})
// nolint:paralleltest
t.Run("TemplateAdminReadAll", func(t *testing.T) {
for _, o := range orgs {
// Template Admins can read all templates in all orgs
exp := []codersdk.Template{o.DefaultTemplate, o.AllRead, o.UserACL, o.GroupACL}
testTemplateRead(t, o, templateAdmin, exp)
}
})
// nolint:paralleltest
t.Run("OrgAdminReadAllTheirs", func(t *testing.T) {
for i, o := range orgs {
cli := o.Admin.Client
// Only read their own org
exp := []codersdk.Template{o.DefaultTemplate, o.AllRead, o.UserACL, o.GroupACL}
testTemplateRead(t, o, cli, exp)
other := orgs[(i+1)%len(orgs)]
require.NotEqual(t, other.Org.ID, o.Org.ID, "this test needs at least 2 orgs")
testTemplateRead(t, other, cli, []codersdk.Template{})
}
})
// nolint:paralleltest
t.Run("TestMemberNoGroup", func(t *testing.T) {
for i, o := range orgs {
cli := o.MemberNoGroup.Client
// Only read their own org
exp := []codersdk.Template{o.AllRead, o.UserACL}
testTemplateRead(t, o, cli, exp)
other := orgs[(i+1)%len(orgs)]
require.NotEqual(t, other.Org.ID, o.Org.ID, "this test needs at least 2 orgs")
testTemplateRead(t, other, cli, []codersdk.Template{})
}
})
// nolint:paralleltest
t.Run("TestMemberInGroup", func(t *testing.T) {
for i, o := range orgs {
cli := o.MemberInGroup.Client
// Only read their own org
exp := []codersdk.Template{o.AllRead, o.GroupACL}
testTemplateRead(t, o, cli, exp)
other := orgs[(i+1)%len(orgs)]
require.NotEqual(t, other.Org.ID, o.Org.ID, "this test needs at least 2 orgs")
testTemplateRead(t, other, cli, []codersdk.Template{})
}
})
}