diff --git a/cmd/api-errors.go b/cmd/api-errors.go index 257bf7f97..153070a25 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -315,6 +315,7 @@ const ( ErrAdminProfilerNotEnabled ErrInvalidDecompressedSize ErrAddUserInvalidArgument + ErrPostPolicyConditionInvalidFormat ) type errorCodeMap map[APIErrorCode]APIError @@ -1496,6 +1497,11 @@ var errorCodes = errorCodeMap{ Description: "User is not allowed to be same as admin access key", HTTPStatusCode: http.StatusConflict, }, + ErrPostPolicyConditionInvalidFormat: { + Code: "PostPolicyInvalidKeyName", + Description: "Invalid according to Policy: Policy Condition failed", + HTTPStatusCode: http.StatusForbidden, + }, // Add your error structure here. } diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index 2bb8b9975..99aebe75c 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -656,9 +656,10 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h // Handle policy if it is set. if len(policyBytes) > 0 { + postPolicyForm, err := parsePostPolicyForm(string(policyBytes)) if err != nil { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r)) + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPostPolicyConditionInvalidFormat), r.URL, guessIsBrowserReq(r)) return } diff --git a/cmd/post-policy_test.go b/cmd/post-policy_test.go index 4b943467c..a3561d4c2 100644 --- a/cmd/post-policy_test.go +++ b/cmd/post-policy_test.go @@ -314,7 +314,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr { objectName: "test", data: []byte("Hello, World"), - expectedRespStatus: http.StatusBadRequest, + expectedRespStatus: http.StatusForbidden, accessKey: credentials.AccessKey, secretKey: credentials.SecretKey, dates: []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, diff --git a/cmd/postpolicyform.go b/cmd/postpolicyform.go index b9ea60b10..5b32464c9 100644 --- a/cmd/postpolicyform.go +++ b/cmd/postpolicyform.go @@ -101,8 +101,9 @@ type contentLengthRange struct { type PostPolicyForm struct { Expiration time.Time // Expiration date and time of the POST policy. Conditions struct { // Conditional policy structure. - Policies map[string]struct { + Policies []struct { Operator string + Key string Value string } ContentLengthRange contentLengthRange @@ -130,10 +131,6 @@ func parsePostPolicyForm(policy string) (ppf PostPolicyForm, e error) { if err != nil { return ppf, err } - parsedPolicy.Conditions.Policies = make(map[string]struct { - Operator string - Value string - }) // Parse conditions. for _, val := range rawPolicy.Conditions { @@ -146,13 +143,13 @@ func parsePostPolicyForm(policy string) (ppf PostPolicyForm, e error) { } // {"acl": "public-read" } is an alternate way to indicate - [ "eq", "$acl", "public-read" ] // In this case we will just collapse this into "eq" for all use cases. - parsedPolicy.Conditions.Policies["$"+strings.ToLower(k)] = struct { + parsedPolicy.Conditions.Policies = append(parsedPolicy.Conditions.Policies, struct { Operator string + Key string Value string }{ - Operator: policyCondEqual, - Value: toString(v), - } + policyCondEqual, "$" + strings.ToLower(k), toString(v), + }) } case []interface{}: // Handle array types. if len(condt) != 3 { // Return error if we have insufficient elements. @@ -167,13 +164,16 @@ func parsePostPolicyForm(policy string) (ppf PostPolicyForm, e error) { } } operator, matchType, value := toLowerString(condt[0]), toLowerString(condt[1]), toString(condt[2]) - parsedPolicy.Conditions.Policies[matchType] = struct { + if !strings.HasPrefix(matchType, "$") { + return parsedPolicy, fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]", operator, matchType, value) + } + parsedPolicy.Conditions.Policies = append(parsedPolicy.Conditions.Policies, struct { Operator string + Key string Value string }{ - Operator: operator, - Value: value, - } + operator, matchType, value, + }) case policyCondContentLength: min, err := toInteger(condt[1]) if err != nil { @@ -224,10 +224,10 @@ func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) erro } // map to store the metadata metaMap := make(map[string]string) - for cond, v := range postPolicyForm.Conditions.Policies { - if strings.HasPrefix(cond, "$x-amz-meta-") { - formCanonicalName := http.CanonicalHeaderKey(strings.TrimPrefix(cond, "$")) - metaMap[formCanonicalName] = v.Value + for _, policy := range postPolicyForm.Conditions.Policies { + if strings.HasPrefix(policy.Key, "$x-amz-meta-") { + formCanonicalName := http.CanonicalHeaderKey(strings.TrimPrefix(policy.Key, "$")) + metaMap[formCanonicalName] = policy.Value } } // Check if any extra metadata field is passed as input @@ -243,30 +243,30 @@ func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) erro condPassed := true // Iterate over policy conditions and check them against received form fields - for cond, v := range postPolicyForm.Conditions.Policies { + for _, policy := range postPolicyForm.Conditions.Policies { // Form fields names are in canonical format, convert conditions names // to canonical for simplification purpose, so `$key` will become `Key` - formCanonicalName := http.CanonicalHeaderKey(strings.TrimPrefix(cond, "$")) + formCanonicalName := http.CanonicalHeaderKey(strings.TrimPrefix(policy.Key, "$")) // Operator for the current policy condition - op := v.Operator + op := policy.Operator // If the current policy condition is known - if startsWithSupported, condFound := startsWithConds[cond]; condFound { + if startsWithSupported, condFound := startsWithConds[policy.Key]; condFound { // Check if the current condition supports starts-with operator if op == policyCondStartsWith && !startsWithSupported { return fmt.Errorf("Invalid according to Policy: Policy Condition failed") } // Check if current policy condition is satisfied - condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), v.Value) + condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value) if !condPassed { return fmt.Errorf("Invalid according to Policy: Policy Condition failed") } } else { // This covers all conditions X-Amz-Meta-* and X-Amz-* - if strings.HasPrefix(cond, "$x-amz-meta-") || strings.HasPrefix(cond, "$x-amz-") { + if strings.HasPrefix(policy.Key, "$x-amz-meta-") || strings.HasPrefix(policy.Key, "$x-amz-") { // Check if policy condition is satisfied - condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), v.Value) + condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value) if !condPassed { - return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]", op, cond, v.Value) + return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]", op, policy.Key, policy.Value) } } }