aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/decision/decision-provider.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/decision/decision-provider.go')
-rw-r--r--pkg/decision/decision-provider.go154
1 files changed, 95 insertions, 59 deletions
diff --git a/pkg/decision/decision-provider.go b/pkg/decision/decision-provider.go
index 12896c3..035fb0f 100644
--- a/pkg/decision/decision-provider.go
+++ b/pkg/decision/decision-provider.go
@@ -38,6 +38,7 @@ import (
"policy-opa-pdp/pkg/pdpstate"
"policy-opa-pdp/pkg/policymap"
"policy-opa-pdp/pkg/utils"
+ "sort"
"strings"
)
@@ -77,8 +78,8 @@ func writeErrorJSONResponse(res http.ResponseWriter, status int, errorDescriptio
// creates a success decision response
func createSuccessDecisionResponse(policyName string, output map[string]interface{}) *oapicodegen.OPADecisionResponse {
return &oapicodegen.OPADecisionResponse{
- PolicyName: policyName,
- Output: output,
+ PolicyName: policyName,
+ Output: output,
}
}
@@ -135,17 +136,21 @@ func handleDecisionRequest(res http.ResponseWriter, req *http.Request, errorDtls
return
}
- if decisionReq.PolicyName == "" {
- *errorDtls = "Policy Name is nil which is invalid."
- *httpStatus = http.StatusBadRequest
- return
- }
+ // Validate the request body
+ validationErrors := utils.ValidateOPADataRequest(decisionReq)
if decisionReq.PolicyFilter == nil || len(decisionReq.PolicyFilter) == 0 {
- *errorDtls = "Policy Filter is nil."
+ validationErrors = append(validationErrors, "PolicyFilter is required")
+ }
+ if len(validationErrors) > 0 {
+ *errorDtls = strings.Join(validationErrors, ", ")
+ log.Errorf("Facing validation error in requestbody - %s", *errorDtls)
*httpStatus = http.StatusBadRequest
return
}
+ log.Debugf("Validation successful for request fields")
+ // If validation passes, handle the decision request
+
decisionReq.PolicyName = strings.ReplaceAll(decisionReq.PolicyName, ".", "/")
handlePolicyValidation(res, decisionReq, errorDtls, httpStatus, policyId)
}
@@ -195,7 +200,7 @@ func policyExists(policyName string, extractedPolicies []model.ToscaConceptIdent
return false
}
-//This function processes the request headers
+// This function processes the request headers
func processRequestHeaders(req *http.Request, res http.ResponseWriter) (string, *oapicodegen.DecisionParams) {
requestId := req.Header.Get("X-ONAP-RequestID")
var parsedUUID *uuid.UUID
@@ -229,7 +234,7 @@ func isSystemActive() bool {
return pdpstate.GetCurrentState() == model.Active
}
-//This method parses the body and checks whether it is properly formatted JSON or not
+// This method parses the body and checks whether it is properly formatted JSON or not
func parseRequestBody(req *http.Request) (*oapicodegen.OPADecisionRequest, error) {
var decisionReq oapicodegen.OPADecisionRequest
if err := json.NewDecoder(req.Body).Decode(&decisionReq); err != nil {
@@ -238,7 +243,7 @@ func parseRequestBody(req *http.Request) (*oapicodegen.OPADecisionRequest, error
return &decisionReq, nil
}
-//This function sends the error response
+// This function sends the error response
func sendDecisionErrorResponse(msg string, res http.ResponseWriter, httpStatus int, policyName string) {
log.Warnf("%s", msg)
decisionExc := createDecisionExceptionResponse(httpStatus, msg, policyName)
@@ -247,29 +252,33 @@ func sendDecisionErrorResponse(msg string, res http.ResponseWriter, httpStatus i
writeErrorJSONResponse(res, httpStatus, msg, *decisionExc)
}
-
type OPASingletonInstanceFunc func() (*sdk.OPA, error)
+
var OPASingletonInstance OPASingletonInstanceFunc = opasdk.GetOPASingletonInstance
-//This function returns the opasdk instance
+// This function returns the opasdk instance
func getOpaInstance() (*sdk.OPA, error) {
return OPASingletonInstance()
}
-
-
type OPADecisionFunc func(opa *sdk.OPA, ctx context.Context, options sdk.DecisionOptions) (*sdk.DecisionResult, error)
+
var OPADecision OPADecisionFunc = (*sdk.OPA).Decision
-//This function processes the OPA decision
+// This function processes the OPA decision
func processOpaDecision(res http.ResponseWriter, opa *sdk.OPA, decisionReq *oapicodegen.OPADecisionRequest) {
ctx := context.Background()
log.Debugf("SDK making a decision")
- var decisionRes *oapicodegen.OPADecisionResponse
+ var decisionRes *oapicodegen.OPADecisionResponse
//OPA is seding success with a warning message if "input" parameter is missing, so we need to send success response
- if (decisionReq.Input == nil) {
- statusMessage := "{\"warning\":{\"code\":\"api_usage_warning\",\"message\":\"'input' key missing from the request\"}}"
- decisionRes = createSuccessDecisionResponseWithStatus(decisionReq.PolicyName, nil, statusMessage)
+ inputBytes, err := json.Marshal(decisionReq.Input)
+ if err != nil {
+ log.Warnf("Failed to unmarshal decision Request Input: %vg", err)
+ return
+ }
+ if inputBytes == nil || len(inputBytes) == 0 {
+ statusMessage := "{\"warning\":{\"code\":\"api_usage_warning\",\"message\":\"'input' key missing from the request\"}}"
+ decisionRes = createSuccessDecisionResponseWithStatus(decisionReq.PolicyName, nil, statusMessage)
} else {
options := sdk.DecisionOptions{Path: decisionReq.PolicyName, Input: decisionReq.Input}
decisionResult, decisionErr := OPADecision(opa, ctx, options)
@@ -280,20 +289,22 @@ func processOpaDecision(res http.ResponseWriter, opa *sdk.OPA, decisionReq *oapi
}
log.Debugf("RAW opa Decision output:\n%s\n", string(jsonOutput))
+ //while making decision . is replaced by /. reverting back.
+ decisionReq.PolicyName = strings.ReplaceAll(decisionReq.PolicyName, "/", ".")
+
if decisionErr != nil {
- handleOpaDecisionError(res, decisionErr, decisionReq.PolicyName)
+ sendDecisionErrorResponse(decisionErr.Error(), res, http.StatusInternalServerError, decisionReq.PolicyName)
return
}
-
var policyFilter []string
if decisionReq.PolicyFilter != nil {
policyFilter = decisionReq.PolicyFilter
}
result, _ := decisionResult.Result.(map[string]interface{})
- outputMap, unmatchedFilters := processPolicyFilter(result, policyFilter)
+ outputMap, unmatchedFilters, validPolicyFilters := processPolicyFilter(result, policyFilter)
if len(unmatchedFilters) > 0 {
- message := fmt.Sprintf("Policy Filter(s) not matching: [%s]", strings.Join(unmatchedFilters, ", "))
+ message := fmt.Sprintf("Policy Filter(s) not matching, Valid Filter(s) are: [%s]", strings.Join(validPolicyFilters, ", "))
decisionRes = createSuccessDecisionResponseWithStatus(decisionReq.PolicyName, outputMap, message)
} else {
decisionRes = createSuccessDecisionResponse(decisionReq.PolicyName, outputMap)
@@ -303,52 +314,77 @@ func processOpaDecision(res http.ResponseWriter, opa *sdk.OPA, decisionReq *oapi
writeOpaJSONResponse(res, http.StatusOK, *decisionRes)
}
-//This function validates the errors during decision process
-func handleOpaDecisionError(res http.ResponseWriter, err error, policyName string) {
- //As per the opa documentation in https://www.openpolicyagent.org/docs/latest/rest-api/#get-a-document-with-input
- //when the path refers to an undefined document it will return 200 with no result.
- //opasdk is returning opa_undefined_error for such case, so need to give sucess for such case and
- //for other cases we have to send error response
- if strings.Contains(err.Error(), string(oapicodegen.OpaUndefinedError)) {
- decisionExc := createSuccessDecisionResponse(policyName, nil)
- metrics.IncrementDecisionSuccessCount()
- writeOpaJSONResponse(res, http.StatusOK, *decisionExc)
- } else {
- sendDecisionErrorResponse(err.Error(), res, http.StatusInternalServerError, policyName)
- }
-}
-
-//This function processes the policy filters
-func processPolicyFilter(result map[string]interface{}, policyFilter []string) (map[string]interface{}, []string) {
+// This function processes the policy filters
+func processPolicyFilter(result map[string]interface{}, policyFilter []string) (map[string]interface{}, []string, []string) {
if len(policyFilter) > 0 {
- filteredResult, unmatchedFilters := applyPolicyFilter(result, policyFilter)
+ filteredResult, unmatchedFilters, validfilters := applyPolicyFilter(result, policyFilter)
if len(filteredResult) > 0 {
- return filteredResult, unmatchedFilters
+ return filteredResult, unmatchedFilters, validfilters
}
}
- return nil, policyFilter
+ return nil, policyFilter, getValidPolicyFilters(result)
}
-// Function to apply policy filter to decision result
-func applyPolicyFilter(result map[string]interface{}, filters []string) (map[string]interface{}, []string) {
+// Get Valid Filters and collects unmatched filters
+func applyPolicyFilter(result map[string]interface{}, filters []string) (map[string]interface{}, []string, []string) {
filteredOutput := make(map[string]interface{})
- unmatchedFilters := make(map[string]struct{})
+ unmatchedFilters := []string{}
+
+ validFilters := getValidPolicyFilters(result)
for _, filter := range filters {
- unmatchedFilters[filter] = struct{}{}
+ if filter == "" {
+ // when filter is "" empty, the entire resultant data will be reported
+ return result, nil, validFilters
+ }
+ // Try to find the value in the result map
+ if value := findNestedValue(result, strings.Split(filter, "/")); value != nil {
+ filteredOutput[filter] = value // Store using full path
+ } else if value, exists := result[filter]; exists {
+ // Allow direct key match (for non-nested filters)
+ filteredOutput[filter] = value
+ } else {
+ unmatchedFilters = append(unmatchedFilters, filter) // Collect unmatched filters
+ }
}
- for key, value := range result {
- for _, filter := range filters {
- if (key == filter || strings.TrimSpace(filter) == "") {
- filteredOutput[key] = value
- delete(unmatchedFilters, filter)
- }
- }
+
+ return filteredOutput, unmatchedFilters, validFilters
+}
+
+// handles the nested Policy Filters available when multiple rego files are included.
+func findNestedValue(opaSdkResult map[string]interface{}, keys []string) interface{} {
+ if len(keys) == 0 {
+ return nil
}
+ currentMap := opaSdkResult
- unmatchedList := make([]string, 0, len(unmatchedFilters))
- for filter := range unmatchedFilters {
- unmatchedList = append(unmatchedList, filter)
+ for _, key := range keys {
+ value, exists := currentMap[key]
+ if !exists {
+ return nil // Key doesn't exist
+ }
+
+ // If it's a nested map, continue traversal
+ if nextNestedMap, ok := value.(map[string]interface{}); ok {
+ currentMap = nextNestedMap
+ } else {
+ return value // Return final value (non-map)
+ }
}
+ return currentMap
+}
- return filteredOutput, unmatchedList
+// returns the valid Policy Filters available
+func getValidPolicyFilters(opaSdkResult map[string]interface{}) []string {
+ keys := make([]string, 0)
+
+ for k, v := range opaSdkResult {
+ keys = append(keys, k)
+ if nestedMap, ok := v.(map[string]interface{}); ok {
+ for nestedKey := range nestedMap {
+ keys = append(keys, k+"/"+nestedKey)
+ }
+ }
+ }
+ sort.Strings(keys)
+ return keys
}