Files
RenewCMS/test/roles_permissions_enforcement_hygiene_test.go
T

198 lines
5.6 KiB
Go

package test
import (
"go/ast"
"go/parser"
"go/token"
"io/fs"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"testing"
"RenewCMS/internal/domain/role"
)
var legacyRoleSpots = map[string]bool{
"internal/infrastructure/useCases/GetMyAccess.go": true,
"internal/infrastructure/useCases/CreateUser.go": true,
"internal/infrastructure/useCases/AcceptInvite.go": true,
}
var legacyRoleComparison = regexp.MustCompile(`user\.Role(Owner|Admin)|\.Role\s*(==|!=)`)
func productionGoFiles(t *testing.T, roots ...string) map[string]string {
t.Helper()
files := map[string]string{}
for _, root := range roots {
err := filepath.WalkDir(filepath.Join("..", root), func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return err
}
files[strings.TrimPrefix(filepath.ToSlash(path), "../")] = string(content)
return nil
})
if err != nil {
t.Fatalf("walk %s: %v", root, err)
}
}
return files
}
func TestRolesPermissionsEnforcementHygiene(t *testing.T) {
t.Run("The owner and admin gates are gone from the code", func(t *testing.T) {
for name, content := range productionGoFiles(t, "api") {
if strings.Contains(content, "RequireOwner") {
t.Errorf("%s still mentions RequireOwner", name)
}
if strings.Contains(content, "OwnerRequiredErrorMessage") || strings.Contains(content, "OwnerOrAdminRequiredErrorMessage") {
t.Errorf("%s still mentions a removed owner message", name)
}
}
})
t.Run("No authorization decision reads the legacy user role", func(t *testing.T) {
for name, content := range productionGoFiles(t, "api", "internal/infrastructure/useCases") {
if legacyRoleSpots[name] {
continue
}
if match := legacyRoleComparison.FindString(content); match != "" {
t.Errorf("%s compares the legacy role: %q", name, match)
}
}
})
t.Run("The legacy role is read for resolution only in the allowed spots", func(t *testing.T) {
for name, content := range productionGoFiles(t, "internal/infrastructure/useCases") {
if strings.Contains(content, "CallerRole") || strings.Contains(content, "isAdminRole") {
t.Errorf("%s still carries a role-based caller", name)
}
}
})
}
var permissionGateNames = map[string]bool{"require": true, "requireAll": true}
func roleKeyConstants(t *testing.T) map[string]string {
t.Helper()
file, err := parser.ParseFile(token.NewFileSet(), filepath.Join("..", "internal", "domain", "role", "roleKeys.go"), nil, 0)
if err != nil {
t.Fatalf("parse roleKeys.go: %v", err)
}
constants := map[string]string{}
ast.Inspect(file, func(node ast.Node) bool {
spec, ok := node.(*ast.ValueSpec)
if !ok {
return true
}
for i, name := range spec.Names {
if i >= len(spec.Values) {
continue
}
if literal, ok := spec.Values[i].(*ast.BasicLit); ok && literal.Kind == token.STRING {
value, _ := strconv.Unquote(literal.Value)
constants[name.Name] = value
}
}
return true
})
return constants
}
func routerGateKeys(t *testing.T) []string {
t.Helper()
file, err := parser.ParseFile(token.NewFileSet(), filepath.Join("..", "api", "router.go"), nil, 0)
if err != nil {
t.Fatalf("parse router.go: %v", err)
}
constants := roleKeyConstants(t)
found := map[string]bool{}
ast.Inspect(file, func(node ast.Node) bool {
call, ok := node.(*ast.CallExpr)
if !ok || len(call.Args) != 1 {
return true
}
selector, ok := call.Fun.(*ast.SelectorExpr)
if !ok || !permissionGateNames[selector.Sel.Name] {
return true
}
switch arg := call.Args[0].(type) {
case *ast.BasicLit:
value, _ := strconv.Unquote(arg.Value)
found[value] = true
case *ast.SelectorExpr:
found[constants[arg.Sel.Name]] = true
default:
t.Errorf("gate %s called with an argument the test cannot resolve", selector.Sel.Name)
}
return true
})
keys := make([]string, 0, len(found))
for key := range found {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
func unknownCatalogueKeys(keys []string) []string {
var unknown []string
for _, key := range keys {
if _, err := role.Lookup(key); err != nil {
unknown = append(unknown, key)
}
}
return unknown
}
func TestRouterPermissionGateKeysAreInTheCatalogue(t *testing.T) {
t.Run("Every key passed to a router permission gate is in the catalogue", func(t *testing.T) {
keys := routerGateKeys(t)
if len(keys) == 0 {
t.Fatal("no permission gate keys found in api/router.go")
}
if unknown := unknownCatalogueKeys(keys); len(unknown) > 0 {
t.Errorf("router gates use keys missing from the catalogue: %v", unknown)
}
})
t.Run("Chained gates contribute both keys", func(t *testing.T) {
keys := routerGateKeys(t)
for _, want := range []string{"activity.export", "activity.view"} {
if sort.SearchStrings(keys, want) == len(keys) || keys[sort.SearchStrings(keys, want)] != want {
t.Errorf("expected gate key %q to be found", want)
}
}
})
t.Run("The checking function catches a misspelled key", func(t *testing.T) {
cases := []struct {
name string
keys []string
want int
}{
{"valid keys", []string{"activity.view", "activity.export"}, 0},
{"misspelled key", []string{"activity.veiw"}, 1},
{"one bad among good", []string{"activity.view", "apikeys.revok"}, 1},
{"empty key", []string{""}, 1},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := len(unknownCatalogueKeys(tc.keys)); got != tc.want {
t.Errorf("unknown = %d, want %d", got, tc.want)
}
})
}
})
}