Skip to content

Commit

Permalink
feat: Support constants
Browse files Browse the repository at this point in the history
Signed-off-by: Andrew Haines <[email protected]>
  • Loading branch information
haines committed Nov 1, 2024
1 parent 1e30995 commit 41d6475
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 2 deletions.
1 change: 1 addition & 0 deletions cerbos/grpc_admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ func TestAdminClient(t *testing.T) {
"derived_roles.apatr_common_roles": "derived_roles/common_roles.yaml",
"derived_roles.alpha": "derived_roles/derived_roles_01.yaml",
"derived_roles.beta": "derived_roles/derived_roles_02.yaml",
"export_constants.bazqux": "export_constants/export_constants_01.yaml",
"export_variables.foobar": "export_variables/export_variables_01.yaml",
"principal.donald_duck.vdefault": "principal_policies/policy_02.yaml",
"principal.donald_duck.vdefault/acme": "principal_policies/policy_02_acme.yaml",
Expand Down
121 changes: 120 additions & 1 deletion cerbos/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,21 @@ func (ps *PolicySet) AddDerivedRoles(policies ...*DerivedRoles) *PolicySet {
return ps
}

// AddExportConstants adds the given exported constants to the set.
func (ps *PolicySet) AddExportConstants(policies ...*ExportConstants) *PolicySet {
for _, p := range policies {
if p == nil {
continue
}

if err := ps.add(p); err != nil {
ps.err = multierr.Append(ps.err, fmt.Errorf("failed to add exported constants [%s]: %w", p.Obj.Name, err))
}
}

return ps
}

// AddExportVariables adds the given exported variables to the set.
func (ps *PolicySet) AddExportVariables(policies ...*ExportVariables) *PolicySet {
for _, p := range policies {
Expand Down Expand Up @@ -706,6 +721,7 @@ func NewResourcePolicy(resource, version string) *ResourcePolicy {
Obj: &policyv1.ResourcePolicy{
Resource: resource,
Version: version,
Constants: &policyv1.Constants{Local: make(map[string]*structpb.Value)},

Check failure on line 724 in cerbos/model.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: policyv1.Constants (typecheck)
Variables: &policyv1.Variables{Local: make(map[string]string)},
},
}
Expand Down Expand Up @@ -750,6 +766,23 @@ func (rp *ResourcePolicy) AddResourceRules(rules ...*ResourceRule) *ResourcePoli
return rp
}

// WithConstantsImports adds import statements for exported constants.
func (rp *ResourcePolicy) WithConstantsImports(name ...string) *ResourcePolicy {
rp.Obj.Constants.Import = append(rp.Obj.Constants.Import, name...)

Check failure on line 771 in cerbos/model.go

View workflow job for this annotation

GitHub Actions / Lint

rp.Obj.Constants undefined (type *policyv1.ResourcePolicy has no field or method Constants) (typecheck)
return rp
}

// WithConstant adds a constant definition for use in conditions.
// Valid value types are those that can be converted to a [google.protobuf.Value].
//
// [google.protobuf.Value]: https://pkg.go.dev/google.golang.org/protobuf/types/known/structpb#NewValue
func (rp *ResourcePolicy) WithConstant(name string, value any) *ResourcePolicy {
var err error
rp.Obj.Constants.Local[name], err = structpb.NewValue(value)

Check failure on line 781 in cerbos/model.go

View workflow job for this annotation

GitHub Actions / Lint

rp.Obj.Constants undefined (type *policyv1.ResourcePolicy has no field or method Constants) (typecheck)
multierr.Append(rp.err, err)
return rp
}

// WithVariablesImports adds import statements for exported variables.
func (rp *ResourcePolicy) WithVariablesImports(name ...string) *ResourcePolicy {
rp.Obj.Variables.Import = append(rp.Obj.Variables.Import, name...)
Expand Down Expand Up @@ -864,6 +897,7 @@ func NewPrincipalPolicy(principal, version string) *PrincipalPolicy {
Obj: &policyv1.PrincipalPolicy{
Principal: principal,
Version: version,
Constants: &policyv1.Constants{Local: make(map[string]*structpb.Value)},

Check failure on line 900 in cerbos/model.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: policyv1.Constants (typecheck)
Variables: &policyv1.Variables{Local: make(map[string]string)},
},
}
Expand Down Expand Up @@ -899,6 +933,23 @@ func (pp *PrincipalPolicy) WithVersion(version string) *PrincipalPolicy {
return pp
}

// WithConstantsImports adds import statements for exported constants.
func (pp *PrincipalPolicy) WithConstantsImports(name ...string) *PrincipalPolicy {
pp.Obj.Constants.Import = append(pp.Obj.Constants.Import, name...)

Check failure on line 938 in cerbos/model.go

View workflow job for this annotation

GitHub Actions / Lint

pp.Obj.Constants undefined (type *policyv1.PrincipalPolicy has no field or method Constants) (typecheck)
return pp
}

// WithConstant adds a constant definition for use in conditions.
// Valid value types are those that can be converted to a [google.protobuf.Value].
//
// [google.protobuf.Value]: https://pkg.go.dev/google.golang.org/protobuf/types/known/structpb#NewValue
func (pp *PrincipalPolicy) WithConstant(name string, value any) *PrincipalPolicy {
var err error
pp.Obj.Constants.Local[name], err = structpb.NewValue(value)

Check failure on line 948 in cerbos/model.go

View workflow job for this annotation

GitHub Actions / Lint

pp.Obj.Constants undefined (type *policyv1.PrincipalPolicy has no field or method Constants) (typecheck)
multierr.Append(pp.err, err)
return pp
}

// WithVariablesImports adds import statements for exported variables.
func (pp *PrincipalPolicy) WithVariablesImports(name ...string) *PrincipalPolicy {
pp.Obj.Variables.Import = append(pp.Obj.Variables.Import, name...)
Expand Down Expand Up @@ -996,13 +1047,15 @@ func (pr *PrincipalRule) Validate() error {
// DerivedRoles is a builder for derived roles.
type DerivedRoles struct {
Obj *policyv1.DerivedRoles
err error
}

// NewDerivedRoles creates a new derived roles set with the given name.
func NewDerivedRoles(name string) *DerivedRoles {
return &DerivedRoles{
Obj: &policyv1.DerivedRoles{
Name: name,
Constants: &policyv1.Constants{Local: make(map[string]*structpb.Value)},
Variables: &policyv1.Variables{Local: make(map[string]string)},
},
}
Expand All @@ -1024,6 +1077,23 @@ func (dr *DerivedRoles) addRoleDef(name string, parentRoles []string, comp *poli
return dr
}

// WithConstantsImports adds import statements for exported constants.
func (dr *DerivedRoles) WithConstantsImports(name ...string) *DerivedRoles {
dr.Obj.Constants.Import = append(dr.Obj.Constants.Import, name...)
return dr
}

// WithConstant adds a constant definition for use in conditions.
// Valid value types are those that can be converted to a [google.protobuf.Value].
//
// [google.protobuf.Value]: https://pkg.go.dev/google.golang.org/protobuf/types/known/structpb#NewValue
func (dr *DerivedRoles) WithConstant(name string, value any) *DerivedRoles {
var err error
dr.Obj.Constants.Local[name], err = structpb.NewValue(value)
multierr.Append(dr.err, err)
return dr
}

// WithVariablesImports adds import statements for exported variables.
func (dr *DerivedRoles) WithVariablesImports(name ...string) *DerivedRoles {
dr.Obj.Variables.Import = append(dr.Obj.Variables.Import, name...)
Expand All @@ -1038,7 +1108,7 @@ func (dr *DerivedRoles) WithVariable(name, expr string) *DerivedRoles {

// Err returns any errors accumulated during the construction of the derived roles.
func (dr *DerivedRoles) Err() error {
return nil
return dr.err
}

// Validate checks whether the derived roles are valid.
Expand All @@ -1058,6 +1128,55 @@ func (dr *DerivedRoles) build() (*policyv1.Policy, error) {
return p, internal.ValidatePolicy(p)
}

// ExportConstants is a builder for exported constants.
type ExportConstants struct {
Obj *policyv1.ExportConstants

Check failure on line 1133 in cerbos/model.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: policyv1.ExportConstants (typecheck)
err error
}

// NewExportConstants creates a new exported constants set with the given name.
func NewExportConstants(name string) *ExportConstants {
return &ExportConstants{
Obj: &policyv1.ExportConstants{
Name: name,
Definitions: make(map[string]*structpb.Value),
},
}
}

// AddConstant defines an exported constant with the given name to be the given value.
// Valid value types are those that can be converted to a [google.protobuf.Value].
//
// [google.protobuf.Value]: https://pkg.go.dev/google.golang.org/protobuf/types/known/structpb#NewValue
func (ec *ExportConstants) AddConstant(name string, value any) *ExportConstants {
var err error
ec.Obj.Definitions[name], err = structpb.NewValue(value)
ec.err = multierr.Append(ec.err, err)
return ec
}

// Err returns any errors accumulated during the construction of the exported constants.
func (ec *ExportConstants) Err() error {
return ec.err
}

// Validate checks whether the exported constants are valid.
func (ec *ExportConstants) Validate() error {
_, err := ec.build()
return err
}

func (ec *ExportConstants) build() (*policyv1.Policy, error) {
p := &policyv1.Policy{
ApiVersion: apiVersion,
PolicyType: &policyv1.Policy_ExportConstants{
ExportConstants: ec.Obj,
},
}

return p, internal.ValidatePolicy(p)
}

// ExportVariables is a builder for exported variables.
type ExportVariables struct {
Obj *policyv1.ExportVariables
Expand Down
29 changes: 29 additions & 0 deletions cerbos/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"

"github.com/cerbos/cerbos-sdk-go/cerbos"
effectv1 "github.com/cerbos/cerbos/api/genpb/cerbos/effect/v1"
Expand All @@ -19,9 +20,12 @@ import (
const (
actionApprove = "approve"
actionCreate = "create"
constantName = "bar"
constantValue = float64(9000)
id = "XX125"
kind = "leave_request"
derivedRolesName = "my_derived_roles"
exportConstantsName = "my_constants"
exportVariablesName = "my_variables"
ref = "cerbos:///principal.json"
roleName = "employee_that_owns_the_record"
Expand Down Expand Up @@ -65,6 +69,11 @@ func TestBuilders(t *testing.T) {
require.NoError(t, dr.Validate())
cmpDerivedRoles(t, dr)
})
t.Run("ExportConstants", func(t *testing.T) {
ec := newExportConstants(t)
require.NoError(t, ec.Validate())
cmpExportConstants(t, ec)
})
t.Run("ExportVariables", func(t *testing.T) {
ev := newExportVariables(t)
require.NoError(t, ev.Validate())
Expand Down Expand Up @@ -127,6 +136,13 @@ func cmpDerivedRoles(t *testing.T, dr *cerbos.DerivedRoles) {
require.Equal(t, map[string]string{variableName: variableExpr}, dr.Obj.Variables.Local)
}

func cmpExportConstants(t *testing.T, ec *cerbos.ExportConstants) {
t.Helper()

require.Equal(t, exportConstantsName, ec.Obj.Name)
require.Equal(t, map[string]any{constantName: constantValue}, (&structpb.Struct{Fields: ec.Obj.Definitions}).AsMap())
}

func cmpExportVariables(t *testing.T, ev *cerbos.ExportVariables) {
t.Helper()

Expand Down Expand Up @@ -242,11 +258,20 @@ func newDerivedRoles(t *testing.T) *cerbos.DerivedRoles {
t.Helper()

return cerbos.NewDerivedRoles(derivedRolesName).
WithConstantsImports(exportConstantsName).
WithConstant(constantName, constantValue).
WithVariablesImports(exportVariablesName).
WithVariable(variableName, variableExpr).
AddRole(roleName, roles)
}

func newExportConstants(t *testing.T) *cerbos.ExportConstants {
t.Helper()

return cerbos.NewExportConstants(exportConstantsName).
AddConstant(constantName, constantValue)
}

func newExportVariables(t *testing.T) *cerbos.ExportVariables {
t.Helper()

Expand Down Expand Up @@ -280,6 +305,8 @@ func newPrincipalPolicy(t *testing.T) *cerbos.PrincipalPolicy {

return cerbos.NewPrincipalPolicy(principal, version).
WithScope(scope).
WithConstantsImports(exportConstantsName).
WithConstant(constantName, constantValue).
WithVariablesImports(exportVariablesName).
WithVariable(variableName, variableExpr).
AddPrincipalRules(
Expand All @@ -292,6 +319,8 @@ func newResourcePolicy(t *testing.T) *cerbos.ResourcePolicy {

return cerbos.NewResourcePolicy(resource, version).
WithScope(scope).
WithConstantsImports(exportConstantsName).
WithConstant(constantName, constantValue).
WithVariablesImports(exportVariablesName).
WithVariable(variableName, variableExpr)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
apiVersion: api.cerbos.dev/v1
derivedRoles:
name: import_variables
constants:
import:
- bazqux
variables:
import:
- foobar
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# yaml-language-server: $schema=../../../../../schema/jsonschema/cerbos/policy/v1/Policy.schema.json
---
apiVersion: api.cerbos.dev/v1
exportConstants:
name: bazqux
definitions:
baz: 42
qux:
hello:
- world
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ apiVersion: api.cerbos.dev/v1
exportVariables:
name: foobar
definitions:
foo: "42"
foo: C.baz
bar: R.attr.foo == V.foo
11 changes: 11 additions & 0 deletions internal/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ func ValidatePolicy(p *policyv1.Policy) error {
return validatePrincipalPolicy(pt.PrincipalPolicy)
case *policyv1.Policy_DerivedRoles:
return validateDerivedRoles(pt.DerivedRoles)
case *policyv1.Policy_ExportConstants:

Check failure on line 75 in internal/validate.go

View workflow job for this annotation

GitHub Actions / Test on Go 1.21

undefined: policyv1.Policy_ExportConstants

Check failure on line 75 in internal/validate.go

View workflow job for this annotation

GitHub Actions / Test on Go 1.21

undefined: policyv1.Policy_ExportConstants

Check failure on line 75 in internal/validate.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: policyv1.Policy_ExportConstants (typecheck)

Check failure on line 75 in internal/validate.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: policyv1.Policy_ExportConstants) (typecheck)

Check failure on line 75 in internal/validate.go

View workflow job for this annotation

GitHub Actions / Test on Go 1.22

undefined: policyv1.Policy_ExportConstants

Check failure on line 75 in internal/validate.go

View workflow job for this annotation

GitHub Actions / Test on Go 1.22

undefined: policyv1.Policy_ExportConstants
return validateExportConstants(p)
case *policyv1.Policy_ExportVariables:
return validateExportVariables(p)
default:
Expand Down Expand Up @@ -155,6 +157,15 @@ func validateDerivedRoles(dr *policyv1.DerivedRoles) (err error) {
return
}

func validateExportConstants(p *policyv1.Policy) error {
//nolint:staticcheck
if len(p.Variables) > 0 {
return fmt.Errorf("export constants policies do not support the deprecated top-level variables field")
}

return nil
}

func validateExportVariables(p *policyv1.Policy) error {
//nolint:staticcheck
if len(p.Variables) > 0 {
Expand Down

0 comments on commit 41d6475

Please sign in to comment.