Files
coder/scripts/apikeyscopesgen/main.go
T
Thomas Kosiewski 79126ab6c7 feat: implement composite API key scopes for workspaces and templates (#19945)
# Add Composite API Key Scopes

This PR adds high-level composite API key scopes to simplify token creation with common permission sets:

- `coder:workspaces.create` - Create and update workspaces
- `coder:workspaces.operate` - Read and update workspaces
- `coder:workspaces.delete` - Read and delete workspaces
- `coder:workspaces.access` - Read, SSH, and connect to workspace applications
- `coder:templates.build` - Read templates and create/read files
- `coder:templates.author` - Full template management with insights
- `coder:apikeys.manage_self` - Manage your own API keys

These composite scopes are persisted in the database and expanded during authorization, providing a more intuitive way to grant permissions compared to the granular resource:action scopes.
2025-09-29 13:17:08 +02:00

122 lines
3.4 KiB
Go

package main
import (
"bytes"
"fmt"
"go/format"
"os"
"slices"
"strings"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/rbac/policy"
)
func main() {
out, err := generate()
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "generate apikey scopes: %v\n", err)
os.Exit(1)
}
if _, err := fmt.Print(string(out)); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "write output: %v\n", err)
os.Exit(1)
}
}
func generate() ([]byte, error) {
names := rbac.ExternalScopeNames()
slices.Sort(names)
var b bytes.Buffer
if _, err := b.WriteString("// Code generated by scripts/apikeyscopesgen. DO NOT EDIT.\n"); err != nil {
return nil, err
}
if _, err := b.WriteString("package codersdk\n\n"); err != nil {
return nil, err
}
// NOTE: Keep all APIKeyScope constants in a single generated file.
// Some tooling (e.g. swaggo) can behave non-deterministically when
// enums are spread across multiple files:
// https://github.com/swaggo/swag/issues/2038
// We generate everything into codersdk/apikey_scopes_gen.go as the
// single source of truth so doc generation remains stable.
// Constants
if _, err := b.WriteString("const (\n"); err != nil {
return nil, err
}
// Always include legacy/deprecated aliases for backward compatibility.
// These are kept in generated code to ensure consistent availability
// across releases even if hand-written files change.
if _, err := b.WriteString("\t// Deprecated: use codersdk.APIKeyScopeCoderAll instead.\n"); err != nil {
return nil, err
}
if _, err := b.WriteString("\tAPIKeyScopeAll APIKeyScope = \"all\"\n"); err != nil {
return nil, err
}
if _, err := b.WriteString("\t// Deprecated: use codersdk.APIKeyScopeCoderApplicationConnect instead.\n"); err != nil {
return nil, err
}
if _, err := b.WriteString("\tAPIKeyScopeApplicationConnect APIKeyScope = \"application_connect\"\n"); err != nil {
return nil, err
}
for _, n := range names {
res, act := splitRA(n)
if act == policy.WildcardSymbol {
act = "All"
}
constName := fmt.Sprintf("APIKeyScope%s%s", pascal(res), pascal(act))
if _, err := fmt.Fprintf(&b, "\t%s APIKeyScope = \"%s\"\n", constName, n); err != nil {
return nil, err
}
}
if _, err := b.WriteString(")\n\n"); err != nil {
return nil, err
}
// Slices
if _, err := b.WriteString("// PublicAPIKeyScopes lists all public low-level API key scopes.\n"); err != nil {
return nil, err
}
if _, err := b.WriteString("var PublicAPIKeyScopes = []APIKeyScope{\n"); err != nil {
return nil, err
}
for _, n := range names {
res, act := splitRA(n)
if act == policy.WildcardSymbol {
act = "All"
}
constName := fmt.Sprintf("APIKeyScope%s%s", pascal(res), pascal(act))
if _, err := fmt.Fprintf(&b, "\t%s,\n", constName); err != nil {
return nil, err
}
}
if _, err := b.WriteString("}\n\n"); err != nil {
return nil, err
}
return format.Source(b.Bytes())
}
func splitRA(name string) (resource string, action string) {
parts := strings.SplitN(name, ":", 2)
if len(parts) != 2 {
return name, ""
}
return parts[0], parts[1]
}
func pascal(s string) string {
// Replace non-identifier separators with spaces, then Title-case and strip.
s = strings.ReplaceAll(s, "_", " ")
s = strings.ReplaceAll(s, "-", " ")
s = strings.ReplaceAll(s, ":", " ")
s = strings.ReplaceAll(s, ".", " ")
words := strings.Fields(s)
for i := range words {
words[i] = strings.ToUpper(words[i][:1]) + words[i][1:]
}
return strings.Join(words, "")
}