diff options
Diffstat (limited to 'pkg/data')
-rw-r--r-- | pkg/data/data-handler.go | 179 | ||||
-rw-r--r-- | pkg/data/data-handler_test.go | 50 |
2 files changed, 111 insertions, 118 deletions
diff --git a/pkg/data/data-handler.go b/pkg/data/data-handler.go index b571010..673f247 100644 --- a/pkg/data/data-handler.go +++ b/pkg/data/data-handler.go @@ -21,8 +21,10 @@ package data import ( "context" "encoding/json" + "fmt" "github.com/google/uuid" openapi_types "github.com/oapi-codegen/runtime/types" + "github.com/open-policy-agent/opa/storage" "net/http" "path/filepath" "policy-opa-pdp/consts" @@ -30,11 +32,9 @@ import ( "policy-opa-pdp/pkg/metrics" "policy-opa-pdp/pkg/model/oapicodegen" "policy-opa-pdp/pkg/opasdk" + "policy-opa-pdp/pkg/policymap" "policy-opa-pdp/pkg/utils" "strings" - - "github.com/open-policy-agent/opa/storage" - "policy-opa-pdp/pkg/policymap" ) var ( @@ -78,60 +78,30 @@ func createOPADataUpdateExceptionResponse(statusCode int, errorMessage string, p } } -// Validate OPADataUpdateRequest function -func validateOPADataUpdateRequest(request *oapicodegen.OPADataUpdateRequest) []string { - var validationErrors []string - - // Check if required fields are populated - dateString := (request.CurrentDate).String() - if !(utils.IsValidCurrentDate(&dateString)) { - validationErrors = append(validationErrors, "CurrentDate is required") - } - - // Validate CurrentDateTime format - if !(utils.IsValidTime(request.CurrentDateTime)) { - validationErrors = append(validationErrors, "CurrentDateTime is invalid or missing") - } - - // Validate CurrentTime format - if !(utils.IsValidCurrentTime(request.CurrentTime)) { - validationErrors = append(validationErrors, "CurrentTime is invalid or missing") - } - - // Validate Data field (ensure it's not nil and has items) - if !(utils.IsValidData(request.Data)) { - validationErrors = append(validationErrors, "Data is required and cannot be empty") - } - - // Validate TimeOffset format (e.g., +02:00 or -05:00) - if !(utils.IsValidTimeOffset(request.TimeOffset)) { - validationErrors = append(validationErrors, "TimeOffset is invalid or missing") - } - - // Validate TimeZone format (e.g., 'America/New_York') - if !(utils.IsValidTimeZone(request.TimeZone)) { - validationErrors = append(validationErrors, "TimeZone is invalid or missing") - } - - // Optionally, check if 'OnapComponent', 'OnapInstance', 'OnapName', and 'PolicyName' are provided - if !(utils.IsValidString(request.OnapComponent)) { - validationErrors = append(validationErrors, "OnapComponent is required") - } +type Policy struct { + Data []string `json:"data"` + Policy []string `json:"policy"` + PolicyID string `json:"policy-id"` + PolicyVersion string `json:"policy-version"` +} - if !(utils.IsValidString(request.OnapInstance)) { - validationErrors = append(validationErrors, "OnapInstance is required") +// Function to extract the policy by policyId +func getPolicyByID(policiesMap string, policyId string) (*Policy, error) { + var policies struct { + DeployedPolicies []Policy `json:"deployed_policies_dict"` } - if !(utils.IsValidString(request.OnapName)) { - validationErrors = append(validationErrors, "OnapName is required") + if err := json.Unmarshal([]byte(policiesMap), &policies); err != nil { + return nil, fmt.Errorf("failed to unmarshal policies: %v", err) } - if !(utils.IsValidString(request.PolicyName)) { - validationErrors = append(validationErrors, "PolicyName is required and cannot be empty") + for _, policy := range policies.DeployedPolicies { + if policy.PolicyID == policyId { + return &policy, nil + } } - // Return all validation errors (if any) - return validationErrors + return nil, fmt.Errorf("policy '%s' not found", policyId) } func patchHandler(res http.ResponseWriter, req *http.Request) { @@ -144,13 +114,18 @@ func patchHandler(res http.ResponseWriter, req *http.Request) { log.Errorf(errMsg) return } - path := strings.TrimPrefix(req.URL.Path, "/policy/pdpo/v1/data/") + path := strings.TrimPrefix(req.URL.Path, "/policy/pdpo/v1/data") dirParts := strings.Split(path, "/") dataDir := filepath.Join(dirParts...) log.Infof("dataDir : %s", dataDir) // Validate the request - validationErrors := validateOPADataUpdateRequest(&requestBody) + validationErrors := utils.ValidateOPADataRequest(&requestBody) + + // Validate Data field (ensure it's not nil and has items) + if !(utils.IsValidData(requestBody.Data)) { + validationErrors = append(validationErrors, "Data is required and cannot be empty") + } // Print validation errors if len(validationErrors) > 0 { @@ -159,7 +134,7 @@ func patchHandler(res http.ResponseWriter, req *http.Request) { sendErrorResponse(res, errMsg, http.StatusBadRequest) return } else { - log.Errorf("All fields are valid!") + log.Debug("All fields are valid!") // Access the data part data := requestBody.Data log.Infof("data : %s", data) @@ -172,10 +147,46 @@ func patchHandler(res http.ResponseWriter, req *http.Request) { log.Errorf(errMsg) return } + + // Checking if the data operation is performed for a deployed policy with policymap.CheckIfPolicyAlreadyExists and getPolicyByID + // if a match is found, we will join the url path with dots and check for the data key from the policiesMap whether utl path is a + // prefix of data key. we will proceed for Patch Operation if this matches, else return error + if len(dirParts) > 0 && dirParts[0] == "" { + dirParts = dirParts[1:] + } + finalDirParts := strings.Join(dirParts, ".") + + policiesMap := policymap.LastDeployedPolicies + + matchedPolicy, err := getPolicyByID(policiesMap, *policyId) + if err != nil { + sendErrorResponse(res, err.Error(), http.StatusBadRequest) + log.Errorf(err.Error()) + return + } + + log.Infof("Matched policy: %+v", matchedPolicy) + + // Check if finalDirParts starts with any data key + matchFound := false + for _, dataKey := range matchedPolicy.Data { + if strings.HasPrefix(finalDirParts, dataKey) { + matchFound = true + break + } + } + + if !matchFound { + errMsg := fmt.Sprintf("Dynamic Data add/replace/remove for policy '%s' expected under url path '%v'", *policyId, matchedPolicy.Data) + sendErrorResponse(res, errMsg, http.StatusBadRequest) + log.Errorf(errMsg) + return + } + if err := patchData(dataDir, data, res); err != nil { - // Handle the error, for example, log it or return an appropriate response - log.Errorf("Error encoding JSON response: %s", err) - } + // Handle the error, for example, log it or return an appropriate response + log.Errorf("Error encoding JSON response: %s", err) + } } } @@ -215,7 +226,7 @@ func extractPatchInfo(res http.ResponseWriter, ops *[]map[string]interface{}, ro // PATCH request with add or replace opType, MUST contain a "value" member whose content specifies the value to be added / replaced. For remove opType, value does not required if optypeString == "add" || optypeString == "replace" { value, valueErr = op["value"] - if !valueErr { + if !valueErr || isEmpty(value) { valueErrMsg := "Error in getting data value. Value is not given in request body" sendErrorResponse(res, valueErrMsg, http.StatusInternalServerError) log.Errorf(valueErrMsg) @@ -225,7 +236,7 @@ func extractPatchInfo(res http.ResponseWriter, ops *[]map[string]interface{}, ro impl.Value = value opPath, opPathErr := op["path"].(string) - if !opPathErr { + if !opPathErr || len(opPath) == 0 { opPathErrMsg := "Error in getting data path. Path is not given in request body" sendErrorResponse(res, opPathErrMsg, http.StatusInternalServerError) log.Errorf(opPathErrMsg) @@ -243,6 +254,35 @@ func extractPatchInfo(res http.ResponseWriter, ops *[]map[string]interface{}, ro return result } +func isEmpty(data interface{}) bool { + if data == nil { + return true // Nil values are considered empty + } + + switch v := data.(type) { + case string: + return len(v) == 0 // Check if string is empty + case []interface{}: + return len(v) == 0 // Check if slice is empty + case map[string]interface{}: + return len(v) == 0 // Check if map is empty + case []byte: + return len(v) == 0 // Check if byte slice is empty + case int, int8, int16, int32, int64: + return v == 0 // Zero integers are considered empty + case uint, uint8, uint16, uint32, uint64: + return v == 0 // Zero unsigned integers are considered empty + case float32, float64: + return v == 0.0 // Zero floats are considered empty + case bool: + return !v // `false` is considered empty + case nil: + return true // Explicitly checking nil again + default: + return false // Other data types are not considered empty + } +} + func constructPath(opPath string, opType string, root string, res http.ResponseWriter) (storagePath storage.Path) { // Construct patch path. log.Debugf("root: %s", root) @@ -269,13 +309,10 @@ func constructPath(opPath string, opType string, root string, res http.ResponseW path = root + "/" + path } } else { - if opType == "remove" { - valueErrMsg := "Error in getting data path - Invalid path (/) is used." - sendErrorResponse(res, valueErrMsg, http.StatusInternalServerError) - log.Errorf(valueErrMsg) - return nil - } - path = root + valueErrMsg := "Error in getting data path - Invalid path (/) is used." + sendErrorResponse(res, valueErrMsg, http.StatusInternalServerError) + log.Errorf(valueErrMsg) + return nil } log.Infof("calling ParsePatchPathEscaped to check the path") @@ -386,7 +423,13 @@ func getDataInfo(res http.ResponseWriter, req *http.Request) { constructResponseHeader(res, req) urlPath := req.URL.Path - dataPath := strings.ReplaceAll(urlPath, "/policy/pdpo/v1/data", "") + + dataPath := strings.TrimPrefix(urlPath, "/policy/pdpo/v1/data") + + if len(strings.TrimSpace(dataPath)) == 0 { + // dataPath "/" is used to get entire data + dataPath = "/" + } log.Debugf("datapath to get Data : %s\n", dataPath) getData(res, dataPath) @@ -420,7 +463,7 @@ func getData(res http.ResponseWriter, dataPath string) { res.WriteHeader(http.StatusOK) if err := json.NewEncoder(res).Encode(dataResponse); err != nil { - // Handle the error, for example, log it or return an appropriate response - log.Errorf("Error encoding JSON response: %s", err) + // Handle the error, for example, log it or return an appropriate response + log.Errorf("Error encoding JSON response: %s", err) } } diff --git a/pkg/data/data-handler_test.go b/pkg/data/data-handler_test.go index 41be361..e8dcb17 100644 --- a/pkg/data/data-handler_test.go +++ b/pkg/data/data-handler_test.go @@ -56,56 +56,6 @@ func TestGetErrorResponseCodeForOPADataUpdate(t *testing.T) { } } -func TestValidateOPADataUpdateRequest(t *testing.T) { - ctime := "12:00:00" - timeZone := "America_New_York" - timeOffset := "$02:00" - onapComp := " " - onapIns := " " - onapName := " " - policyName := " " - var currentDate openapi_types.Date - currentDate = openapi_types.Date{} - var currentDateTime time.Time - currentDateTime = time.Time{} - - var data []map[string]interface{} - - data = nil - - inValidRequest := &oapicodegen.OPADataUpdateRequest{ - CurrentDate: ¤tDate, - CurrentDateTime: ¤tDateTime, - CurrentTime: &ctime, - TimeOffset: &timeOffset, - TimeZone: &timeZone, - OnapComponent: &onapComp, - OnapInstance: &onapIns, - OnapName: &onapName, - PolicyName: &policyName, - Data: &data, - } - - inValidErr := []string{"CurrentTime is invalid or missing", "Data is required and cannot be empty", "TimeOffset is invalid or missing", "TimeZone is invalid or missing", "OnapComponent is required", "OnapInstance is required", "OnapName is required", "PolicyName is required and cannot be empty"} - - tests := []struct { - name string - request *oapicodegen.OPADataUpdateRequest - expectedErr []string - }{ - {"Valid Request", inValidRequest, inValidErr}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - errors := validateOPADataUpdateRequest(tt.request) - fmt.Printf("error : %s", errors) - fmt.Printf("error len : %d", len(errors)) - assert.Equal(t, tt.expectedErr, errors) - }) - } -} - func TestPatchHandler_InvalidJSON(t *testing.T) { req := httptest.NewRequest("PATCH", "/policy/pdpo/v1/data/", bytes.NewBuffer([]byte("{invalid_json}"))) res := httptest.NewRecorder() |