Files
coder/coderd/rbac/acl/updatevalidator.go
T

131 lines
4.1 KiB
Go

package acl
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/codersdk"
)
type UpdateValidator[Role codersdk.WorkspaceRole | codersdk.TemplateRole] interface {
// Users should return a map from user UUIDs (as strings) to the role they
// are being assigned. Additionally, it should return a string that will be
// used as the field name for the ValidationErrors returned from Validate.
Users() (map[string]Role, string)
// Groups should return a map from group UUIDs (as strings) to the role they
// are being assigned. Additionally, it should return a string that will be
// used as the field name for the ValidationErrors returned from Validate.
Groups() (map[string]Role, string)
// ValidateRole should return an error that will be used in the
// ValidationError if the role is invalid for the corresponding resource type.
ValidateRole(role Role) error
}
func Validate[Role codersdk.WorkspaceRole | codersdk.TemplateRole](
ctx context.Context,
db database.Store,
v UpdateValidator[Role],
) []codersdk.ValidationError {
// nolint:gocritic // Validate requires full read access to users and groups
ctx = dbauthz.AsSystemRestricted(ctx)
var validErrs []codersdk.ValidationError
groupRoles, groupsField := v.Groups()
groupIDs := make([]uuid.UUID, 0, len(groupRoles))
for idStr, role := range groupRoles {
// Validate the provided role names
if err := v.ValidateRole(role); err != nil {
validErrs = append(validErrs, codersdk.ValidationError{
Field: groupsField,
Detail: err.Error(),
})
}
// Validate that the IDs are UUIDs
id, err := uuid.Parse(idStr)
if err != nil {
validErrs = append(validErrs, codersdk.ValidationError{
Field: groupsField,
Detail: fmt.Sprintf("%v is not a valid UUID.", idStr),
})
continue
}
// Don't check if the ID exists when setting the role to
// WorkspaceRoleDeleted or TemplateRoleDeleted. They might've existing at
// some point and got deleted. If we report that as an error here then they
// can't be removed.
if string(role) == "" {
continue
}
groupIDs = append(groupIDs, id)
}
// Validate that the groups exist
groupValidation, err := db.ValidateGroupIDs(ctx, groupIDs)
if err != nil {
validErrs = append(validErrs, codersdk.ValidationError{
Field: groupsField,
Detail: fmt.Sprintf("failed to validate group IDs: %v", err.Error()),
})
}
if !groupValidation.Ok {
for _, id := range groupValidation.InvalidGroupIds {
validErrs = append(validErrs, codersdk.ValidationError{
Field: groupsField,
Detail: fmt.Sprintf("group with ID %v does not exist", id),
})
}
}
userRoles, usersField := v.Users()
userIDs := make([]uuid.UUID, 0, len(userRoles))
for idStr, role := range userRoles {
// Validate the provided role names
if err := v.ValidateRole(role); err != nil {
validErrs = append(validErrs, codersdk.ValidationError{
Field: usersField,
Detail: err.Error(),
})
}
// Validate that the IDs are UUIDs
id, err := uuid.Parse(idStr)
if err != nil {
validErrs = append(validErrs, codersdk.ValidationError{
Field: usersField,
Detail: fmt.Sprintf("%v is not a valid UUID.", idStr),
})
continue
}
// Don't check if the ID exists when setting the role to
// WorkspaceRoleDeleted or TemplateRoleDeleted. They might've existing at
// some point and got deleted. If we report that as an error here then they
// can't be removed.
if string(role) == "" {
continue
}
userIDs = append(userIDs, id)
}
// Validate that the groups exist
userValidation, err := db.ValidateUserIDs(ctx, userIDs)
if err != nil {
validErrs = append(validErrs, codersdk.ValidationError{
Field: usersField,
Detail: fmt.Sprintf("failed to validate user IDs: %v", err.Error()),
})
}
if !userValidation.Ok {
for _, id := range userValidation.InvalidUserIds {
validErrs = append(validErrs, codersdk.ValidationError{
Field: usersField,
Detail: fmt.Sprintf("user with ID %v does not exist", id),
})
}
}
return validErrs
}