Skip to content

Commit

Permalink
Add GetCurrentUserCompartments() to v2; migrate GetAcctEntitlements()…
Browse files Browse the repository at this point in the history
… from v1
  • Loading branch information
rchowinfoblox committed Feb 26, 2024
1 parent b52fbf4 commit 408ef25
Show file tree
Hide file tree
Showing 14 changed files with 963 additions and 35 deletions.
8 changes: 8 additions & 0 deletions common/authorizer/authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ type OpaEvaluator func(ctx context.Context, decisionDocument string, opaReq, opa

type ClaimsVerifier func([]string, []string) (string, []error)

// AcctEntitlementsType is a convenience data type, returned by GetAcctEntitlements()
// (map of acct_id to map of service to array of features)
type AcctEntitlementsType map[string]map[string][]string

// Authorizer interface is implemented for making arbitrary requests to Opa.
type Authorizer interface {
// Evaluate evaluates the authorization policy for the given request.
Expand All @@ -23,4 +27,8 @@ type Authorizer interface {
OpaQuery(ctx context.Context, decisionDocument string, opaReq, opaResp interface{}) error

AffirmAuthorization(ctx context.Context, fullMethod string, eq interface{}) (context.Context, error)

GetAcctEntitlements(ctx context.Context, accountIDs, serviceNames []string) (*AcctEntitlementsType, error)

GetCurrentUserCompartments(ctx context.Context) ([]string, error)
}
6 changes: 6 additions & 0 deletions common/authorizer/literal.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ const (
// DefaultValidatePath is default OPA path to perform authz validation
DefaultValidatePath = "v1/data/authz/rbac/validate_v1"

// DefaultAcctEntitlementsApiPath is default OPA path to fetch acct entitlements
DefaultAcctEntitlementsApiPath = "v1/data/authz/rbac/acct_entitlements_api"

// DefaultCurrentUserCompartmentsPath is default OPA path to fetch current user's compartments
DefaultCurrentUserCompartmentsPath = "v1/data/authz/rbac/current_user_compartments"

REDACTED = "redacted"
TypeKey = ABACKey("ABACType")
VerbKey = ABACKey("ABACVerb")
Expand Down
30 changes: 30 additions & 0 deletions common/authorizer/mock_Authorizer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 0 additions & 6 deletions common/literal.go

This file was deleted.

57 changes: 57 additions & 0 deletions http_opa/acct_entitlements.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package httpopa

import (
"context"
"fmt"

"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
az "github.com/infobloxopen/atlas-authz-middleware/v2/common/authorizer"
"github.com/infobloxopen/atlas-authz-middleware/v2/common/opautil"
logrus "github.com/sirupsen/logrus"
)

// AcctEntitlementsApiInput is the input payload for acct_entitlements_api
type AcctEntitlementsApiInput struct {
AccountIDs []string `json:"acct_entitlements_acct_ids"`
ServiceNames []string `json:"acct_entitlements_services"`
}

// AcctEntitlementsApiResult is the data type json.Unmarshaled from OPA RESTAPI query to acct_entitlements_api
type AcctEntitlementsApiResult struct {
Result *az.AcctEntitlementsType `json:"result"`
}

// GetAcctEntitlements queries account entitled features data
// for the specified account-ids and entitled-services.
// If both account-ids and entitled-services are empty,
// then data for all entitled-services in all accounts are returned.
func (a *httpAuthorizer) GetAcctEntitlements(ctx context.Context, accountIDs, serviceNames []string) (*az.AcctEntitlementsType, error) {
lgNtry := ctxlogrus.Extract(ctx)
acctResult := AcctEntitlementsApiResult{}

if accountIDs == nil {
accountIDs = []string{}
}
if serviceNames == nil {
serviceNames = []string{}
}

opaReq := opautil.OPARequest{
Input: &AcctEntitlementsApiInput{
AccountIDs: accountIDs,
ServiceNames: serviceNames,
},
}

err := a.clienter.CustomQuery(ctx, a.acctEntitlementsApi, opaReq, &acctResult)
if err != nil {
lgNtry.WithError(err).Error("get_acct_entitlements_fail")
return nil, err
}

lgNtry.WithFields(logrus.Fields{
"acctResult": fmt.Sprintf("%#v", acctResult),
}).Trace("get_acct_entitlements_okay")

return acctResult.Result, nil
}
197 changes: 197 additions & 0 deletions http_opa/acct_entitlements_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package httpopa

import (
"context"
"io/ioutil"
"reflect"
"testing"

az "github.com/infobloxopen/atlas-authz-middleware/v2/common/authorizer"
"github.com/infobloxopen/atlas-authz-middleware/v2/pkg/opa_client"
"github.com/infobloxopen/atlas-authz-middleware/v2/utils_test"

"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
logrus "github.com/sirupsen/logrus"
)

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

done := make(chan struct{})
clienter := utils_test.StartOpa(ctx, t, done)
cli, ok := clienter.(*opa_client.Client)
if !ok {
t.Fatal("Unable to convert interface to (*Client)")
return
}

// Errors above here will leak containers
defer func() {
cancel()
// Wait for container to be shutdown
<-done
}()

policyRego, err := ioutil.ReadFile("testdata/mock_authz_policy.rego")
if err != nil {
t.Fatalf("ReadFile fatal err: %#v", err)
return
}

var resp interface{}
err = cli.UploadRegoPolicy(ctx, "mock_authz_policyid", policyRego, resp)
if err != nil {
t.Fatalf("OpaUploadPolicy fatal err: %#v", err)
return
}

auther := NewHttpAuthorizer("bogus_unused_application_value",
WithOpaClienter(cli),
)

actualSpecific, err := auther.GetAcctEntitlements(ctx,
[]string{"2001040", "2001230"}, []string{"powertrain", "wheel"})
if err != nil {
t.Errorf("FAIL: GetAcctEntitlements() unexpected err=%v", err)
}
t.Logf("actualSpecific=%#v", actualSpecific)

expectSpecific := &az.AcctEntitlementsType{
"2001040": {
"powertrain": {"automatic", "turbo"},
},
"2001230": {
"powertrain": {"manual", "v8"},
"wheel": {"run-flat"},
},
}
if !reflect.DeepEqual(actualSpecific, expectSpecific) {
t.Errorf("FAIL:\nactualSpecific: %#v\nexpectSpecific: %#v",
actualSpecific, expectSpecific)
}
}

func TestGetAcctEntitlementsMockOpaClient(t *testing.T) {
testMap := []struct {
name string
regoRespJSON string
expectErr bool
expectedVal *az.AcctEntitlementsType
}{
{
name: `valid result`,
regoRespJSON: `{ "result": {
"acct1": { "svc1a": [ "feat1a1", "feat1a2" ] },
"acct2": { "svc2a": [ "feat2a1", "feat2a2" ],
"svc2b": [ "feat2b1", "feat2b2" ] }
}}`,
expectErr: false,
expectedVal: &az.AcctEntitlementsType{
"acct1": {"svc1a": {"feat1a1", "feat1a2"}},
"acct2": {"svc2a": {"feat2a1", "feat2a2"},
"svc2b": {"feat2b1", "feat2b2"}},
},
},
{
name: `null result ok`,
regoRespJSON: `{ "result": null }`,
expectErr: false,
expectedVal: nil,
},
{
name: `null account entitled service ok`,
regoRespJSON: `{ "result": {
"acct1": { "svc1a": [ "feat1a1", "feat1a2" ] },
"acct2": null
}}`,
expectErr: false,
expectedVal: &az.AcctEntitlementsType{
"acct1": {"svc1a": {"feat1a1", "feat1a2"}},
"acct2": nil,
},
},
{
name: `null service entitled features ok`,
regoRespJSON: `{ "result": {
"acct2": { "svc2a": null,
"svc2b": [ "feat2b1", "feat2b2" ] }
}}`,
expectErr: false,
expectedVal: &az.AcctEntitlementsType{
"acct2": {"svc2a": nil,
"svc2b": {"feat2b1", "feat2b2"}},
},
},
{
name: `incorrect result type`,
regoRespJSON: `[ null ]`,
expectErr: true,
expectedVal: nil,
},
{
name: `no result key`,
regoRespJSON: `{ "rresult": null }`,
expectErr: false,
expectedVal: nil,
},
{
name: `invalid result array`,
regoRespJSON: `{ "result": [ 1, 2 ] }`,
expectErr: true,
expectedVal: nil,
},
{
name: `invalid account entitled service`,
regoRespJSON: `{ "result": {
"acct2": { "svc2a": [ "feat2a1", "feat2a2" ],
"svc2b": {} }
}}`,
expectErr: true,
expectedVal: nil,
},
{
name: `invalid service entitled feature`,
regoRespJSON: `{ "result": {
"acct2": { "svc2a": [ "feat2a1", "feat2a2" ],
"svc2b": [ "feat2b1", 31415926 ] }
}}`,
expectErr: true,
expectedVal: nil,
},
}

stdLoggr := logrus.StandardLogger()
ctx := context.WithValue(context.Background(), utils_test.TestingTContextKey, t)
ctx = ctxlogrus.ToContext(ctx, logrus.NewEntry(stdLoggr))

for nth, tm := range testMap {
mockOpaClienter := utils_test.MockOpaClienter{
Loggr: stdLoggr,
RegoRespJSON: tm.regoRespJSON,
}
auther := NewHttpAuthorizer("bogus_unused_application_value",
WithOpaClienter(&mockOpaClienter),
)

actualVal, actualErr := auther.GetAcctEntitlements(ctx, nil, nil)
t.Logf("%d: %q: actualErr=%#v, actualVal=%#v", nth, tm.name, actualVal, actualErr)

if tm.expectErr && actualErr == nil {
t.Errorf("%d: %q: FAIL: expected err, but got no err", nth, tm.name)
} else if !tm.expectErr && actualErr != nil {
t.Errorf("%d: %q: FAIL: got unexpected err=%s", nth, tm.name, actualErr)
}

if actualErr != nil && actualVal != nil {
t.Errorf("%d: %q: FAIL: returned val should be nil if err returned", nth, tm.name)
}

if !reflect.DeepEqual(actualVal, tm.expectedVal) {
t.Errorf("%d: %q: FAIL: expectedVal=%#v actualVal=%#v",
nth, tm.name, tm.expectedVal, actualVal)
}
}
}
Loading

0 comments on commit 408ef25

Please sign in to comment.