From 29a8ae069b6e4e94df2d9143444fa10da8b3ffc0 Mon Sep 17 00:00:00 2001 From: rchowinfoblox Date: Thu, 5 Dec 2024 10:05:38 -0800 Subject: [PATCH] PTCI-830: Refactor Evaluate to expose new Validate method (v2 branch) --- common/authorizer/authorizer.go | 14 +++++-- common/authorizer/mock_Authorizer.go | 15 +++++++ http_opa/authorizer.go | 62 ++++++++++++++++++---------- 3 files changed, 66 insertions(+), 25 deletions(-) diff --git a/common/authorizer/authorizer.go b/common/authorizer/authorizer.go index 20d1de0..9209151 100644 --- a/common/authorizer/authorizer.go +++ b/common/authorizer/authorizer.go @@ -1,6 +1,8 @@ package authorizer -import "context" +import ( + "context" +) // OpaEvaluator implements calling OPA with a request and receiving the raw response type OpaEvaluator func(ctx context.Context, decisionDocument string, opaReq, opaResp interface{}) error @@ -21,10 +23,16 @@ type FilterCompartmentFeaturesType map[string][]string // Authorizer interface is implemented for making arbitrary requests to Opa. type Authorizer interface { + // Validate evaluates the authorization policy for the given request. + // It takes the context, full method name, request object, and an OpaEvaluator as input. + // Unlike Evaluate, it only returns the raw Opa response, it does not parse the results. + Validate(ctx context.Context, fullMethod string, req interface{}, opaEvaluator OpaEvaluator) (interface{}, error) + // Evaluate evaluates the authorization policy for the given request. // It takes the context, full method name, request object, and an OpaEvaluator as input. - // It returns a boolean indicating whether the request is authorized, a modified context, - // and an error if any. + // It parses the Opa response and returns a boolean indicating whether the request is authorized, + // a modified context, and an error if any. It also parses and adds the entitled_features and obligations + // from Opa response in the modified context returned. Evaluate(ctx context.Context, fullMethod string, req interface{}, opaEvaluator OpaEvaluator) (bool, context.Context, error) // OpaQuery executes a query against the OPA (Open Policy Agent) with the specified decision document. diff --git a/common/authorizer/mock_Authorizer.go b/common/authorizer/mock_Authorizer.go index 25bd116..4a72591 100644 --- a/common/authorizer/mock_Authorizer.go +++ b/common/authorizer/mock_Authorizer.go @@ -49,6 +49,21 @@ func (mr *MockAuthorizerMockRecorder) AffirmAuthorization(ctx, fullMethod, eq in return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AffirmAuthorization", reflect.TypeOf((*MockAuthorizer)(nil).AffirmAuthorization), ctx, fullMethod, eq) } +// Validate mocks base method. +func (m *MockAuthorizer) Validate(ctx context.Context, fullMethod string, req interface{}, opaEvaluator OpaEvaluator) (interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Validate", ctx, fullMethod, req, opaEvaluator) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Validate indicates an expected call of Validate. +func (mr *MockAuthorizerMockRecorder) Validate(ctx, fullMethod, req, opaEvaluator interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockAuthorizer)(nil).Validate), ctx, fullMethod, req, opaEvaluator) +} + // Evaluate mocks base method. func (m *MockAuthorizer) Evaluate(ctx context.Context, fullMethod string, req interface{}, opaEvaluator OpaEvaluator) (bool, context.Context, error) { m.ctrl.T.Helper() diff --git a/http_opa/authorizer.go b/http_opa/authorizer.go index 044cd37..7b75d31 100644 --- a/http_opa/authorizer.go +++ b/http_opa/authorizer.go @@ -82,11 +82,45 @@ func (a *httpAuthorizer) Evaluate(ctx context.Context, endpoint string, req inte "application": a.application, }) + rawResp, err := a.Validate(ctx, endpoint, req, opaEvaluator) + if err != nil { + return false, ctx, err + } + opaResp, ok := rawResp.(opautil.OPAResponse) + if !ok { + return false, ctx, exception.ErrUnknown + } + + // Add raw entitled_features data to the context + //REVIEW: is it needed for http? + ctx = opaResp.AddRawEntitledFeatures(ctx) + + // Add obligations data to the context + //REVIEW: is it needed for http? + ctx, err = opautil.AddObligations(ctx, opaResp) + if err != nil { + logger.WithField("opaResp", fmt.Sprintf("%#v", opaResp)).WithError(err).Error("parse_obligations_error") + } + + // Check if the authorization is allowed + if !opaResp.Allow() { + return false, ctx, exception.ErrForbidden + } + + return true, ctx, nil +} + +func (a *httpAuthorizer) Validate(ctx context.Context, endpoint string, req interface{}, opaEvaluator az.OpaEvaluator) (interface{}, error) { + // Extract the logger from the context + logger := ctxlogrus.Extract(ctx).WithFields(log.Fields{ + "application": a.application, + }) + // Get the bearer token from the request bearer, err := util.GetBearerFromRequest(req.(*http.Request)) if err != nil { logger.WithError(err).Error("get_bearer_from_request") - return false, ctx, exception.ErrInvalidArg + return nil, exception.ErrInvalidArg } // Verify the bearer token and get the raw JWT @@ -96,7 +130,7 @@ func (a *httpAuthorizer) Evaluate(ctx context.Context, endpoint string, req inte } rawJWT, errs := claimsVerifier([]string{bearer}, nil) if len(errs) > 0 { - return false, ctx, exception.NewHttpError( + return nil, exception.NewHttpError( exception.WithError(errors.Join(errs...)), exception.WithHttpStatus(http.StatusUnauthorized)) } @@ -126,7 +160,7 @@ func (a *httpAuthorizer) Evaluate(ctx context.Context, endpoint string, req inte logger.WithFields(log.Fields{ "endpoint": endpoint, }).WithError(err).Error("get_decision_input") - return false, ctx, exception.ErrInvalidArg + return nil, exception.ErrInvalidArg } opaReq.DecisionInput = *decisionInput @@ -137,7 +171,7 @@ func (a *httpAuthorizer) Evaluate(ctx context.Context, endpoint string, req inte // logger.WithFields(log.Fields{ // "opaReq": opaReq, // }).WithError(err).Error("opa_request_json_marshal") - // return false, ctx, exception.ErrInvalidArg + // return nil, exception.ErrInvalidArg // } now := time.Now() @@ -177,7 +211,7 @@ func (a *httpAuthorizer) Evaluate(ctx context.Context, endpoint string, req inte }).Debug("authorization_result") }() if err != nil { - return false, ctx, err + return nil, err } // Extract the nested result if present @@ -201,23 +235,7 @@ func (a *httpAuthorizer) Evaluate(ctx context.Context, endpoint string, req inte // }, "out") } - // Add raw entitled_features data to the context - //REVIEW: is it needed for http? - ctx = opaResp.AddRawEntitledFeatures(ctx) - - // Add obligations data to the context - //REVIEW: is it needed for http? - ctx, err = opautil.AddObligations(ctx, opaResp) - if err != nil { - logger.WithField("opaResp", fmt.Sprintf("%#v", opaResp)).WithError(err).Error("parse_obligations_error") - } - - // Check if the authorization is allowed - if !opaResp.Allow() { - return false, ctx, exception.ErrForbidden - } - - return true, ctx, nil + return opaResp, nil } // OpaQuery executes a custom OPA query with the given decision document, request, and response.