Skip to content

Commit

Permalink
enhancement(sdk): Add support for user-defined outputs (#1620)
Browse files Browse the repository at this point in the history
SDK support for #1594

Fixes #1618

Signed-off-by: Charith Ellawala <charith@cerbos.dev>
  • Loading branch information
charithe committed Jun 2, 2023
1 parent f36cdee commit 7220e09
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 3 deletions.
26 changes: 25 additions & 1 deletion client/model.go
Expand Up @@ -415,7 +415,9 @@ func (crbr *CheckResourceBatchResponse) MarshalJSON() ([]byte, error) {

type ResourceResult struct {
*responsev1.CheckResourcesResponse_ResultEntry
err error
err error
outputMap map[string]*structpb.Value
outputOnce sync.Once
}

func (rr *ResourceResult) Err() error {
Expand All @@ -432,6 +434,28 @@ func (rr *ResourceResult) IsAllowed(action string) bool {
return false
}

func (rr *ResourceResult) buildOutputMap() {
rr.outputOnce.Do(func() {
if len(rr.GetOutputs()) == 0 {
return
}

rr.outputMap = make(map[string]*structpb.Value, len(rr.Outputs))
for _, o := range rr.Outputs {
rr.outputMap[o.GetSrc()] = o.GetVal()
}
})
}

func (rr *ResourceResult) Output(key string) *structpb.Value {
if rr == nil {
return nil
}

rr.buildOutputMap()
return rr.outputMap[key]
}

// MatchResource is a function that returns true if the given resource is of interest.
// This is useful when you have more than one resource with the same ID and need to distinguish
// between them in the response.
Expand Down
80 changes: 78 additions & 2 deletions client/tests.go
Expand Up @@ -12,12 +12,16 @@ import (
"testing"
"time"

enginev1 "github.com/cerbos/cerbos/api/genpb/cerbos/engine/v1"
"github.com/cerbos/cerbos/internal/test"
"github.com/google/go-cmp/cmp"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/structpb"

enginev1 "github.com/cerbos/cerbos/api/genpb/cerbos/engine/v1"
"github.com/cerbos/cerbos/internal/test"
)

const timeout = 30 * time.Second
Expand Down Expand Up @@ -272,6 +276,78 @@ func TestGRPCClient(c Client) func(*testing.T) {
})
})

t.Run("CheckResourcesOutput", func(t *testing.T) {
principal := NewPrincipal("john").
WithRoles("employee").
WithAttributes(map[string]any{
"department": "marketing",
"geography": "GB",
"team": "design",
})

resources := NewResourceBatch().Add(
NewResource("equipment_request", "XX125").
WithScope("acme").
WithAttributes(map[string]any{
"department": "marketing",
"geography": "GB",
"id": "XX125",
"owner": "john",
"team": "design",
}), "view:public", "approve", "create",
)

check := func(t *testing.T, have *CheckResourcesResponse, err error) {
t.Helper()
require.NoError(t, err)

haveXX125 := have.GetResource("XX125")
require.NoError(t, haveXX125.Err())
require.True(t, haveXX125.IsAllowed("view:public"))
require.False(t, haveXX125.IsAllowed("approve"))
require.True(t, haveXX125.IsAllowed("create"))
require.Equal(t, "acme", haveXX125.Resource.Scope)

wantStruct, err := structpb.NewStruct(map[string]any{
"id": "john",
"keys": "XX125",
"formatted_string": "id:john",
"some_bool": true,
"some_list": []any{"foo", "bar"},
"something_nested": map[string]any{
"nested_str": "foo",
"nested_bool": false,
"nested_list": []any{"nest_foo", 1.01},
"nested_formatted_string": "id:john",
},
})
require.NoError(t, err, "Failed to create wanted output")
wantOutput1 := structpb.NewStructValue(wantStruct)
haveOutput1 := haveXX125.Output("resource.equipment_request.vdefault#public-view")
require.Empty(t, cmp.Diff(wantOutput1, haveOutput1, protocmp.Transform()))

wantOutput2 := structpb.NewStringValue("create_allowed:john")
haveOutput2 := haveXX125.Output("resource.equipment_request.vdefault/acme#rule-001")
require.Empty(t, cmp.Diff(wantOutput2, haveOutput2, protocmp.Transform()))
}

t.Run("Direct", func(t *testing.T) {
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
defer cancelFunc()

have, err := c.CheckResources(ctx, principal, resources)
check(t, have, err)
})

t.Run("WithPrincipal", func(t *testing.T) {
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
defer cancelFunc()

have, err := c.WithPrincipal(principal).CheckResources(ctx, resources)
check(t, have, err)
})
})

t.Run("IsAllowed", func(t *testing.T) {
principal := NewPrincipal("john").
WithRoles("employee").
Expand Down

0 comments on commit 7220e09

Please sign in to comment.