feat: expose grant schema for UI linting support (#6470)
This commit is contained in:
1 parent
0f9307c0ae
commit
8dae2ee45b
3 files changed
+188
No files matched your search
@@ -10,6 +10,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/boundary/internal/event"
|
||||||
|
"github.com/hashicorp/boundary/internal/perms"
|
||||||
"github.com/hashicorp/boundary/internal/ui"
|
"github.com/hashicorp/boundary/internal/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,6 +22,19 @@ func init() {
|
|||||||
// serveMetadata provides controller metadata to the UI for licensed versions of Boundary.
|
// serveMetadata provides controller metadata to the UI for licensed versions of Boundary.
|
||||||
var serveMetadata = func(ctx context.Context, w http.ResponseWriter) {}
|
var serveMetadata = func(ctx context.Context, w http.ResponseWriter) {}
|
||||||
|
|
||||||
|
// serveGrantSchema provides the grant schema to the UI for autocomplete and linting support.
|
||||||
|
var serveGrantSchema = func(ctx context.Context, w http.ResponseWriter) {
|
||||||
|
const op = "controller.serveGrantSchema"
|
||||||
|
data, err := perms.BuildGrantSchemaJSON(ctx)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
event.WriteError(ctx, op, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
func handleUiWithAssets(c *Controller) http.Handler {
|
func handleUiWithAssets(c *Controller) http.Handler {
|
||||||
var nextHandler http.Handler
|
var nextHandler http.Handler
|
||||||
if c.conf.RawConfig.DevUiPassthroughDir != "" {
|
if c.conf.RawConfig.DevUiPassthroughDir != "" {
|
||||||
@@ -46,6 +61,9 @@ func handleUiWithAssets(c *Controller) http.Handler {
|
|||||||
case "/metadata.json":
|
case "/metadata.json":
|
||||||
serveMetadata(c.baseContext, w)
|
serveMetadata(c.baseContext, w)
|
||||||
return
|
return
|
||||||
|
case "/grants-schema.json":
|
||||||
|
serveGrantSchema(c.baseContext, w)
|
||||||
|
return
|
||||||
default:
|
default:
|
||||||
for i := dotIndex + 1; i < len(r.URL.Path); i++ {
|
for i := dotIndex + 1; i < len(r.URL.Path); i++ {
|
||||||
intVal := r.URL.Path[i]
|
intVal := r.URL.Path[i]
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
// Copyright IBM Corp. 2020, 2025
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
package perms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/boundary/globals"
|
||||||
|
"github.com/hashicorp/boundary/internal/types/action"
|
||||||
|
"github.com/hashicorp/boundary/internal/types/resource"
|
||||||
|
"github.com/hashicorp/boundary/internal/types/scope"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GrantSchema represents the schema of grants in the system,
|
||||||
|
// including the resource types and their associated actions, scopes.
|
||||||
|
type GrantSchema struct {
|
||||||
|
ResourceTypes []ResourceTypeSchema `json:"resource_types"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResourceTypeSchema struct {
|
||||||
|
// Type is the string representation of the resource type
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// The collection actions that are valid for this resource type
|
||||||
|
CollectionActions []string `json:"collection_actions,omitempty"`
|
||||||
|
|
||||||
|
// The id actions that are valid for this resource type
|
||||||
|
IdActions []string `json:"id_actions,omitempty"`
|
||||||
|
|
||||||
|
// The scopes that are valid for this resource type
|
||||||
|
Scopes []string `json:"scopes,omitempty"`
|
||||||
|
|
||||||
|
// The ID prefixes that are valid for this resource type
|
||||||
|
IdPrefixes []string `json:"id_prefixes,omitempty"`
|
||||||
|
|
||||||
|
// The parent resource type, if any, omitted if no parent
|
||||||
|
ParentType string `json:"parent_type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildGrantSchema constructs the full grant schema from the registered
|
||||||
|
// resource types, actions, and scope definitions.
|
||||||
|
func BuildGrantSchema(ctx context.Context) (*GrantSchema, error) {
|
||||||
|
const op = "perms.BuildGrantSchema"
|
||||||
|
schema := &GrantSchema{}
|
||||||
|
|
||||||
|
for name, typ := range resource.Map {
|
||||||
|
// Skip resource types that aren't real grantable resources
|
||||||
|
// These types will fail further lookups and aren't useful to include in the schema
|
||||||
|
if typ == resource.Unknown || typ == resource.All || typ == resource.Controller {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all collection actions
|
||||||
|
colActions, err := action.CollectionActionSetForResource(typ)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: error getting collection actions for %q: %w", op, name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var colStrs []string
|
||||||
|
for a := range colActions {
|
||||||
|
colStrs = append(colStrs, a.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all id actions
|
||||||
|
idActions, err := action.IdActionSetForResource(typ)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: error getting id actions for %q: %w", op, name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var idStrs []string
|
||||||
|
for a := range idActions {
|
||||||
|
if a == action.NoOp {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
idStrs = append(idStrs, a.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all scopes that can be applied to this resource type
|
||||||
|
scopes, err := scope.AllowedIn(ctx, typ)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: error getting allowed scopes for %q: %w", op, name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var scopeStrs []string
|
||||||
|
for _, s := range scopes {
|
||||||
|
scopeStrs = append(scopeStrs, s.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent type is the resource's parent if one exists,
|
||||||
|
// otherwise this will be an empty string.
|
||||||
|
var parentType string
|
||||||
|
if parent := typ.Parent(); parent != typ {
|
||||||
|
parentType = parent.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.ResourceTypes = append(schema.ResourceTypes, ResourceTypeSchema{
|
||||||
|
Type: name,
|
||||||
|
CollectionActions: colStrs,
|
||||||
|
IdActions: idStrs,
|
||||||
|
Scopes: scopeStrs,
|
||||||
|
IdPrefixes: globals.ResourcePrefixesFromType(typ),
|
||||||
|
ParentType: parentType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildGrantSchemaJSON builds the grant schema and marshals it to JSON
|
||||||
|
func BuildGrantSchemaJSON(ctx context.Context) ([]byte, error) {
|
||||||
|
schema, err := BuildGrantSchema(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(schema)
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright IBM Corp. 2020, 2025
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
package perms_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/boundary/internal/perms"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
// Import to trigger init() registrations in all service handlers
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/accounts"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/aliases"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/authmethods"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/authtokens"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/billing"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentiallibraries"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentials"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentialstores"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/groups"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/host_catalogs"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/host_sets"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/hosts"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/managed_groups"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/policies"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/roles"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/scopes"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/session_recordings"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/sessions"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/storage_buckets"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/targets"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/users"
|
||||||
|
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/workers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildGrantSchema(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
schema, err := perms.BuildGrantSchema(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, schema)
|
||||||
|
assert.NotEmpty(t, schema.ResourceTypes, "resource types should not be empty")
|
||||||
|
|
||||||
|
// JSON serialization should produce valid JSON
|
||||||
|
data, err := perms.BuildGrantSchemaJSON(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, json.Valid(data), "output should be valid JSON")
|
||||||
|
}
|
||||||
Reference in new issue
Block a user