Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WithExtraInputFields() option #26

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions grpc_opa/authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,13 @@ func NewDefaultAuthorizer(application string, opts ...Option) *DefaultAuthorizer
claimsVerifier: cfg.claimsVerifier,
entitledServices: cfg.entitledServices,
acctEntitlementsApi: cfg.acctEntitlementsApi,
extraInputFields: cfg.extraInputFields,
}
return &a
}

type ExtraInputFields map[string]interface{}

type DefaultAuthorizer struct {
application string
clienter opa_client.Clienter
Expand All @@ -156,6 +159,7 @@ type DefaultAuthorizer struct {
claimsVerifier ClaimsVerifier
entitledServices []string
acctEntitlementsApi string
extraInputFields ExtraInputFields
}

type Config struct {
Expand All @@ -170,6 +174,7 @@ type Config struct {
claimsVerifier ClaimsVerifier
entitledServices []string
acctEntitlementsApi string
extraInputFields ExtraInputFields
}

type ClaimsVerifier func([]string, []string) (string, []error)
Expand Down Expand Up @@ -221,6 +226,7 @@ func (a *DefaultAuthorizer) Evaluate(ctx context.Context, fullMethod string, grp
JWT: redactJWT(rawJWT),
RequestID: reqID,
EntitledServices: a.entitledServices,
ExtraInputFields: a.extraInputFields,
}

decisionInput, err := a.decisionInputHandler.GetDecisionInput(ctx, fullMethod, grpcReq)
Expand Down Expand Up @@ -407,6 +413,7 @@ type Payload struct {
RequestID string `json:"request_id"`
EntitledServices []string `json:"entitled_services"`
DecisionInput
ExtraInputFields `json:"extra,omitempty"`
}

// OPARequest is used to query OPA
Expand Down
120 changes: 120 additions & 0 deletions grpc_opa/authorizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,102 @@ func TestDebugLogging(t *testing.T) {
}
}

func TestInputPayload(t *testing.T) {
stdLoggr := logrus.StandardLogger()
ctx := context.WithValue(context.Background(), utils_test.TestingTContextKey, t)
ctx = ctxlogrus.ToContext(ctx, logrus.NewEntry(stdLoggr))

newMockOpaClienterFn := func(expectInputJSON string) *MockOpaClienter {
return &MockOpaClienter{
Loggr: stdLoggr,
RegoRespJSON: `{"allow": true}`,
VerifyInput: true,
ExpectInputJSON: expectInputJSON,
}
}

testMap := []struct {
name string
authzr *DefaultAuthorizer
}{
{
name: `no-options`,
authzr: NewDefaultAuthorizer("fakeapp",
WithClaimsVerifier(NullClaimsVerifier),
WithOpaClienter(newMockOpaClienterFn(`{
"endpoint": "FakeMethod",
"application": "fakeapp",
"full_method": "FakeMethod",
"jwt": "redacted",
"request_id": "no-request-uuid",
"entitled_services": null,
"type": "",
"verb": "",
"ctx": null
}`)),
),
},
{
name: `with-one-extra-input-field`,
authzr: NewDefaultAuthorizer("fakeapp",
WithClaimsVerifier(NullClaimsVerifier),
WithExtraInputField("my extra field 1", "my extra value 1"),
WithOpaClienter(newMockOpaClienterFn(`{
"endpoint": "FakeMethod",
"application": "fakeapp",
"full_method": "FakeMethod",
"jwt": "redacted",
"request_id": "no-request-uuid",
"entitled_services": null,
"type": "",
"verb": "",
"ctx": null,
"extra": {
"my extra field 1": "my extra value 1"
}
}`)),
),
},
{
name: `with-mult-extra-input-field`,
authzr: NewDefaultAuthorizer("fakeapp",
WithClaimsVerifier(NullClaimsVerifier),
WithExtraInputField("my extra field 1", "my extra value 1"),
WithExtraInputField("my extra field 2", true),
WithExtraInputField("my extra field 3", 123),
WithOpaClienter(newMockOpaClienterFn(`{
"endpoint": "FakeMethod",
"application": "fakeapp",
"full_method": "FakeMethod",
"jwt": "redacted",
"request_id": "no-request-uuid",
"entitled_services": null,
"type": "",
"verb": "",
"ctx": null,
"extra": {
"my extra field 1": "my extra value 1",
"my extra field 2": true,
"my extra field 3": 123
}
}`)),
),
},
}

for nth, tm := range testMap {
tcCtx := context.WithValue(ctx, utils_test.TestCaseIndexContextKey, nth)
tcCtx = context.WithValue(tcCtx, utils_test.TestCaseNameContextKey, tm.name)
tm.authzr.AffirmAuthorization(tcCtx, "FakeMethod", nil)
}
}

type MockOpaClienter struct {
Loggr *logrus.Logger
RegoRespJSON string

VerifyInput bool
ExpectInputJSON string
}

func (m MockOpaClienter) String() string {
Expand Down Expand Up @@ -452,6 +545,33 @@ func (m MockOpaClienter) CustomQueryBytes(ctx context.Context, document string,
}

func (m MockOpaClienter) CustomQuery(ctx context.Context, document string, reqData, resp interface{}) error {
if m.VerifyInput {
t, _ := ctx.Value(utils_test.TestingTContextKey).(*testing.T)
tcIdx, _ := ctx.Value(utils_test.TestCaseIndexContextKey).(int)
tcName, _ := ctx.Value(utils_test.TestCaseNameContextKey).(string)
payload, _ := reqData.(Payload)
payloadJSON, _ := json.MarshalIndent(payload, "", " ")
t.Logf("%d: %s: payload=%#v", tcIdx, tcName, payload)
actualInput := map[string]interface{}{}
expectInput := map[string]interface{}{}
err := json.Unmarshal(payloadJSON, &actualInput)
if err != nil {
t.Errorf("%d: %s: FAIL: json.Unmarshal err=%s\npayloadJSON=%s",
tcIdx, tcName, err, string(payloadJSON))
}
err = json.Unmarshal([]byte(m.ExpectInputJSON), &expectInput)
if err != nil {
t.Errorf("%d: %s: FAIL: json.Unmarshal err=%s\nExpectInputJSON=%s",
tcIdx, tcName, err, m.ExpectInputJSON)
}
if !reflect.DeepEqual(actualInput, expectInput) {
t.Errorf("%d: %s: FAIL:\npayloadJSON=%s\nExpectInputJSON=%s",
tcIdx, tcName, string(payloadJSON), m.ExpectInputJSON)
t.Errorf("%d: %s: FAIL:\nactualInput=%#v\nexpectInput=%#v",
tcIdx, tcName, actualInput, expectInput)
}
}

err := json.Unmarshal([]byte(m.RegoRespJSON), resp)
m.Loggr.Debugf("CustomQuery: resp=%#v", resp)
return err
Expand Down
20 changes: 20 additions & 0 deletions grpc_opa/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,23 @@ func WithAcctEntitlementsApiPath(acctEntitlementsApi string) Option {
c.acctEntitlementsApi = acctEntitlementsApi
}
}

// WithExtraInputFields merges extra input fields, can be called multiple times
func WithExtraInputFields(extra ExtraInputFields) Option {
return func(c *Config) {
if extra == nil || len(extra) <= 0 {
return
}
if c.extraInputFields == nil {
c.extraInputFields = ExtraInputFields{}
}
for key, val := range extra {
c.extraInputFields[key] = val
}
}
}

// WithExtraInputField merges extra input field, can be called multiple times
func WithExtraInputField(name string, value interface{}) Option {
return WithExtraInputFields(ExtraInputFields{name: value})
}
94 changes: 83 additions & 11 deletions grpc_opa/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,37 @@ func Test_WithEntitledServices_payload(t *testing.T) {
var uninitializedStrSlice []string
withEntitledServicesTests := []struct {
name string
inputEntitledServices interface{}
configEntitledServices interface{}
expectEntitledServices []string
}{
{
name: `dont-call-WithEntitledServices`,
inputEntitledServices: `dont-call-WithEntitledServices`,
configEntitledServices: `dont-call-WithEntitledServices`,
expectEntitledServices: nil,
},
{
name: `WithEntitledServices(nil)`,
inputEntitledServices: nil,
configEntitledServices: nil,
expectEntitledServices: nil,
},
{
name: `WithEntitledServices(uninitializedStrSlice)`,
inputEntitledServices: uninitializedStrSlice,
configEntitledServices: uninitializedStrSlice,
expectEntitledServices: nil,
},
{
name: `WithEntitledServices([])`,
inputEntitledServices: []string{},
configEntitledServices: []string{},
expectEntitledServices: []string{},
},
{
name: `WithEntitledServices(["lic"])`,
inputEntitledServices: []string{"lic"},
configEntitledServices: []string{"lic"},
expectEntitledServices: []string{"lic"},
},
{
name: `WithEntitledServices(["lic","rpz"])`,
inputEntitledServices: []string{"lic", "rpz"},
configEntitledServices: []string{"lic", "rpz"},
expectEntitledServices: []string{"lic", "rpz"},
},
}
Expand All @@ -66,14 +66,79 @@ func Test_WithEntitledServices_payload(t *testing.T) {
WithClaimsVerifier(NullClaimsVerifier),
)

inputEntitledServices, ok := tstc.inputEntitledServices.([]string)
if tstc.inputEntitledServices == nil || ok {
configEntitledServices, ok := tstc.configEntitledServices.([]string)
if tstc.configEntitledServices == nil || ok {
t.Logf("tst#%d: name=%s; calling option WithEntitledServices(%#v)",
idx, tstc.name, inputEntitledServices)
idx, tstc.name, configEntitledServices)
auther = NewDefaultAuthorizer("app",
WithOpaClienter(&mockOpaClienter),
WithClaimsVerifier(NullClaimsVerifier),
WithEntitledServices(inputEntitledServices...),
WithEntitledServices(configEntitledServices...),
)
}

auther.AffirmAuthorization(tcCtx, "FakeMethod", nil)
}
}

func Test_WithExtraInputFields_payload(t *testing.T) {
var uninitializedExtraInputFields ExtraInputFields
withExtraInputFieldsTests := []struct {
name string
configExtraInputFields interface{}
expectExtraInputFields ExtraInputFields
}{
{
name: `dont-call-WithExtraInputFields`,
configExtraInputFields: `dont-call-WithExtraInputFields`,
expectExtraInputFields: nil,
},
{
name: `WithExtraInputFields(nil)`,
configExtraInputFields: nil,
expectExtraInputFields: nil,
},
{
name: `WithExtraInputFields(uninitializedExtraInputFields)`,
configExtraInputFields: uninitializedExtraInputFields,
expectExtraInputFields: nil,
},
{
name: `WithExtraInputFields(ExtraInputFields{})`,
configExtraInputFields: ExtraInputFields{},
expectExtraInputFields: nil,
},
{
name: `WithExtraInputFields(ExtraInputFields{name:val})`,
configExtraInputFields: ExtraInputFields{"k1": "v1", "k2": true, "k3": 123},
expectExtraInputFields: ExtraInputFields{"k1": "v1", "k2": true, "k3": 123},
},
}

testingTCtx := context.WithValue(context.Background(), utils_test.TestingTContextKey, t)

for idx, tstc := range withExtraInputFieldsTests {
tcCtx := context.WithValue(testingTCtx, utils_test.TestCaseIndexContextKey, idx)
tcCtx = context.WithValue(tcCtx, utils_test.TestCaseNameContextKey, tstc.name)

mockOpaClienter := optionsMockOpaClienter{
VerifyExtraInputFields: true,
ExpectExtraInputFields: tstc.expectExtraInputFields,
}

auther := NewDefaultAuthorizer("app",
WithOpaClienter(&mockOpaClienter),
WithClaimsVerifier(NullClaimsVerifier),
)

configExtraInputFields, ok := tstc.configExtraInputFields.(ExtraInputFields)
if tstc.configExtraInputFields == nil || ok {
t.Logf("tst#%d: name=%s; calling option WithExtraInputFields(%#v)",
idx, tstc.name, configExtraInputFields)
auther = NewDefaultAuthorizer("app",
WithOpaClienter(&mockOpaClienter),
WithClaimsVerifier(NullClaimsVerifier),
WithExtraInputFields(configExtraInputFields),
)
}

Expand All @@ -84,6 +149,9 @@ func Test_WithEntitledServices_payload(t *testing.T) {
type optionsMockOpaClienter struct {
VerifyEntitledServices bool
ExpectEntitledServices []string

VerifyExtraInputFields bool
ExpectExtraInputFields ExtraInputFields
}

func (m optionsMockOpaClienter) String() string {
Expand Down Expand Up @@ -120,5 +188,9 @@ func (m optionsMockOpaClienter) CustomQuery(ctx context.Context, document string
t.Errorf("tst#%d: FAIL: name=%s; not equal: payload.EntitledServices=%#v; m.ExpectEntitledServices=%#v",
tcIdx, tcName, payload.EntitledServices, m.ExpectEntitledServices)
}
if m.VerifyExtraInputFields && !reflect.DeepEqual(payload.ExtraInputFields, m.ExpectExtraInputFields) {
t.Errorf("tst#%d: FAIL: name=%s; not equal: payload.ExtraInputFields=%#v; m.ExpectExtraInputFields=%#v",
tcIdx, tcName, payload.ExtraInputFields, m.ExpectExtraInputFields)
}
return json.Unmarshal([]byte(`{"allow": true}`), resp)
}