generated from coulomb/repo-seed
Define Markitect action vocabulary
This commit is contained in:
117
internal/markitect/actions.go
Normal file
117
internal/markitect/actions.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package markitect
|
||||
|
||||
import "github.com/netkingdom/flex-auth/pkg/api"
|
||||
|
||||
const (
|
||||
ActionRead = "read"
|
||||
ActionQuery = "query"
|
||||
ActionSearch = "search"
|
||||
ActionPackage = "package"
|
||||
ActionActivateContext = "activate_context"
|
||||
ActionExport = "export"
|
||||
ActionWorkflowRun = "workflow_run"
|
||||
ActionAdmin = "admin"
|
||||
)
|
||||
|
||||
// ActionMapping describes the Markitect policy-gateway action contract.
|
||||
type ActionMapping struct {
|
||||
Action string `json:"action"`
|
||||
Capabilities []api.Capability `json:"capabilities"`
|
||||
Planes []api.Plane `json:"planes"`
|
||||
ExposureModes []api.ExposureMode `json:"exposure_modes"`
|
||||
AllowedEffects []api.DecisionEffect `json:"allowed_effects"`
|
||||
RequiredContext []api.Condition `json:"required_context,omitempty"`
|
||||
}
|
||||
|
||||
// ActionVocabulary is the canonical Markitect action vocabulary.
|
||||
var ActionVocabulary = []ActionMapping{
|
||||
{
|
||||
Action: ActionRead,
|
||||
Capabilities: []api.Capability{api.CapabilityView},
|
||||
Planes: []api.Plane{api.PlaneData},
|
||||
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModeMasked, api.ExposureModePlaintext},
|
||||
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectRedact},
|
||||
},
|
||||
{
|
||||
Action: ActionQuery,
|
||||
Capabilities: []api.Capability{api.CapabilityViewCollection, api.CapabilityObserve},
|
||||
Planes: []api.Plane{api.PlaneData},
|
||||
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModeAggregated, api.ExposureModeMasked},
|
||||
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectRedact},
|
||||
},
|
||||
{
|
||||
Action: ActionSearch,
|
||||
Capabilities: []api.Capability{api.CapabilityViewCollection, api.CapabilityObserve},
|
||||
Planes: []api.Plane{api.PlaneData},
|
||||
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModeAggregated, api.ExposureModeMasked},
|
||||
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectRedact},
|
||||
},
|
||||
{
|
||||
Action: ActionPackage,
|
||||
Capabilities: []api.Capability{api.CapabilityCreate, api.CapabilityBind, api.CapabilityViewCollection},
|
||||
Planes: []api.Plane{api.PlaneIntent, api.PlaneData},
|
||||
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModeMasked},
|
||||
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectAuditOnly},
|
||||
RequiredContext: []api.Condition{api.ConditionLogged},
|
||||
},
|
||||
{
|
||||
Action: ActionActivateContext,
|
||||
Capabilities: []api.Capability{api.CapabilityUse, api.CapabilityExecute},
|
||||
Planes: []api.Plane{api.PlaneIntent, api.PlanePolicy},
|
||||
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModeMasked},
|
||||
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectAuditOnly},
|
||||
RequiredContext: []api.Condition{api.ConditionLogged, api.ConditionPurposeBound},
|
||||
},
|
||||
{
|
||||
Action: ActionExport,
|
||||
Capabilities: []api.Capability{api.CapabilityExport},
|
||||
Planes: []api.Plane{api.PlaneData, api.PlaneAudit},
|
||||
ExposureModes: []api.ExposureMode{api.ExposureModeExportable, api.ExposureModePlaintext},
|
||||
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectAuditOnly},
|
||||
RequiredContext: []api.Condition{api.ConditionMFARequired, api.ConditionLogged},
|
||||
},
|
||||
{
|
||||
Action: ActionWorkflowRun,
|
||||
Capabilities: []api.Capability{api.CapabilityExecute, api.CapabilityOperate},
|
||||
Planes: []api.Plane{api.PlaneExecution, api.PlaneData, api.PlaneAudit},
|
||||
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModeMasked, api.ExposureModePlaintext},
|
||||
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectAuditOnly},
|
||||
RequiredContext: []api.Condition{api.ConditionLogged},
|
||||
},
|
||||
{
|
||||
Action: ActionAdmin,
|
||||
Capabilities: []api.Capability{api.CapabilityConfigure, api.CapabilityGrant, api.CapabilityRevoke, api.CapabilityAudit},
|
||||
Planes: []api.Plane{api.PlaneConfiguration, api.PlaneIdentity, api.PlanePolicy, api.PlaneAudit},
|
||||
ExposureModes: []api.ExposureMode{api.ExposureModeMetadata, api.ExposureModePlaintext},
|
||||
AllowedEffects: []api.DecisionEffect{api.DecisionEffectAllow, api.DecisionEffectDeny, api.DecisionEffectAuditOnly},
|
||||
RequiredContext: []api.Condition{api.ConditionMFARequired, api.ConditionLogged},
|
||||
},
|
||||
}
|
||||
|
||||
// LookupAction returns the canonical mapping for an action.
|
||||
func LookupAction(action string) (ActionMapping, bool) {
|
||||
for _, mapping := range ActionVocabulary {
|
||||
if mapping.Action == action {
|
||||
return mapping, true
|
||||
}
|
||||
}
|
||||
return ActionMapping{}, false
|
||||
}
|
||||
|
||||
// ActionDefinitions returns protected-system action definitions for Markitect.
|
||||
func ActionDefinitions() []api.ActionDefinition {
|
||||
out := make([]api.ActionDefinition, 0, len(ActionVocabulary))
|
||||
for _, mapping := range ActionVocabulary {
|
||||
out = append(out, api.ActionDefinition{
|
||||
Name: mapping.Action,
|
||||
Capabilities: append([]api.Capability(nil), mapping.Capabilities...),
|
||||
Planes: append([]api.Plane(nil), mapping.Planes...),
|
||||
ExposureModes: append([]api.ExposureMode(nil), mapping.ExposureModes...),
|
||||
Metadata: map[string]any{
|
||||
"allowed_effects": append([]api.DecisionEffect(nil), mapping.AllowedEffects...),
|
||||
"required_context": append([]api.Condition(nil), mapping.RequiredContext...),
|
||||
},
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
55
internal/markitect/actions_test.go
Normal file
55
internal/markitect/actions_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package markitect_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/netkingdom/flex-auth/internal/markitect"
|
||||
"github.com/netkingdom/flex-auth/pkg/api"
|
||||
)
|
||||
|
||||
func TestActionVocabularySeparatesReadFromExport(t *testing.T) {
|
||||
read, ok := markitect.LookupAction(markitect.ActionRead)
|
||||
if !ok {
|
||||
t.Fatal("read action not found")
|
||||
}
|
||||
if slices.Contains(read.Capabilities, api.CapabilityExport) {
|
||||
t.Fatalf("read capabilities = %v; must not include Export", read.Capabilities)
|
||||
}
|
||||
if !slices.Contains(read.ExposureModes, api.ExposureModeMetadata) ||
|
||||
!slices.Contains(read.ExposureModes, api.ExposureModeMasked) ||
|
||||
!slices.Contains(read.ExposureModes, api.ExposureModePlaintext) {
|
||||
t.Fatalf("read exposure modes = %v; want metadata, masked, and plaintext", read.ExposureModes)
|
||||
}
|
||||
|
||||
export, ok := markitect.LookupAction(markitect.ActionExport)
|
||||
if !ok {
|
||||
t.Fatal("export action not found")
|
||||
}
|
||||
if !slices.Contains(export.Capabilities, api.CapabilityExport) {
|
||||
t.Fatalf("export capabilities = %v; want Export", export.Capabilities)
|
||||
}
|
||||
if !slices.Contains(export.ExposureModes, api.ExposureModeExportable) {
|
||||
t.Fatalf("export exposure modes = %v; want Exportable", export.ExposureModes)
|
||||
}
|
||||
if !slices.Contains(export.RequiredContext, api.ConditionMFARequired) {
|
||||
t.Fatalf("export required context = %v; want MFARequired", export.RequiredContext)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionDefinitionsMirrorVocabulary(t *testing.T) {
|
||||
definitions := markitect.ActionDefinitions()
|
||||
if len(definitions) != len(markitect.ActionVocabulary) {
|
||||
t.Fatalf("definitions len = %d; want %d", len(definitions), len(markitect.ActionVocabulary))
|
||||
}
|
||||
|
||||
for i, mapping := range markitect.ActionVocabulary {
|
||||
definition := definitions[i]
|
||||
if definition.Name != mapping.Action {
|
||||
t.Fatalf("definitions[%d].Name = %q; want %q", i, definition.Name, mapping.Action)
|
||||
}
|
||||
if !slices.Equal(definition.Capabilities, mapping.Capabilities) {
|
||||
t.Fatalf("%s capabilities = %v; want %v", mapping.Action, definition.Capabilities, mapping.Capabilities)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user