From 5f7565762e4efeaceeefdcd191aae9dd79f96a98 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 13 Mar 2017 14:41:13 -0700 Subject: [PATCH] api: postPolicy cleanup. Simplify the code and re-use. (#3890) This change is cleanup of the postPolicyHandler code primarily to address the flow and also converting certain critical parts into self contained functions. --- cmd/bucket-handlers.go | 95 ++++++++++++++++++-------------------- cmd/handler-utils.go | 84 ++++++++++++++++++++------------- cmd/handler-utils_test.go | 44 ++++++++++++++++++ cmd/object-handlers.go | 34 ++++++-------- cmd/post-policy_test.go | 14 ++++-- cmd/postpolicyform.go | 6 +-- cmd/postpolicyform_test.go | 26 +++++------ cmd/signature-v2.go | 8 ++-- cmd/signature-v2_test.go | 8 ++-- cmd/signature-v4.go | 14 +++--- cmd/signature-v4_test.go | 37 ++++++++------- cmd/web-handlers.go | 14 ++---- 12 files changed, 222 insertions(+), 162 deletions(-) diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index a1d2d4a3d..a27552f69 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -19,7 +19,6 @@ package cmd import ( "encoding/base64" "encoding/xml" - "fmt" "io" "net/http" "net/url" @@ -327,9 +326,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, ObjInfo: ObjectInfo{ Name: dobj.ObjectName, }, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + ReqParams: extractReqParams(r), }) } } @@ -439,14 +436,25 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h defer fileBody.Close() bucket := mux.Vars(r)["bucket"] - formValues["Bucket"] = bucket + formValues.Set("Bucket", bucket) - if fileName != "" && strings.Contains(formValues["Key"], "${filename}") { + if fileName != "" && strings.Contains(formValues.Get("Key"), "${filename}") { // S3 feature to replace ${filename} found in Key form field // by the filename attribute passed in multipart - formValues["Key"] = strings.Replace(formValues["Key"], "${filename}", fileName, -1) + formValues.Set("Key", strings.Replace(formValues.Get("Key"), "${filename}", fileName, -1)) + } + object := formValues.Get("Key") + + successRedirect := formValues.Get("success_action_redirect") + successStatus := formValues.Get("success_action_status") + var redirectURL *url.URL + if successRedirect != "" { + redirectURL, err = url.Parse(successRedirect) + if err != nil { + writeErrorResponse(w, ErrMalformedPOSTRequest, r.URL) + return + } } - object := formValues["Key"] // Verify policy signature. apiErr := doesPolicySignatureMatch(formValues) @@ -455,7 +463,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h return } - policyBytes, err := base64.StdEncoding.DecodeString(formValues["Policy"]) + policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy")) if err != nil { writeErrorResponse(w, ErrMalformedPOSTRequest, r.URL) return @@ -492,7 +500,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h // Extract metadata to be saved from received Form. metadata := extractMetadataFromForm(formValues) - sha256sum := "" objectLock := globalNSMutex.NewNSLock(bucket, object) @@ -505,50 +512,40 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } - w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"") + + w.Header().Set("ETag", `"`+objInfo.MD5Sum+`"`) w.Header().Set("Location", getObjectLocation(bucket, object)) - successRedirect := formValues[http.CanonicalHeaderKey("success_action_redirect")] - successStatus := formValues[http.CanonicalHeaderKey("success_action_status")] + // Notify object created event. + defer eventNotify(eventData{ + Type: ObjectCreatedPost, + Bucket: objInfo.Bucket, + ObjInfo: objInfo, + ReqParams: extractReqParams(r), + }) + + if successRedirect != "" { + // Replace raw query params.. + redirectURL.RawQuery = getRedirectPostRawQuery(objInfo) + writeRedirectSeeOther(w, redirectURL.String()) + return + } - if successStatus == "" && successRedirect == "" { + // Decide what http response to send depending on success_action_status parameter + switch successStatus { + case "201": + resp := encodeResponse(PostResponse{ + Bucket: objInfo.Bucket, + Key: objInfo.Name, + ETag: `"` + objInfo.MD5Sum + `"`, + Location: getObjectLocation(objInfo.Bucket, objInfo.Name), + }) + writeResponse(w, http.StatusCreated, resp, "application/xml") + case "200": + writeSuccessResponseHeadersOnly(w) + default: writeSuccessNoContent(w) - } else { - if successRedirect != "" { - redirectURL := successRedirect + "?" + fmt.Sprintf("bucket=%s&key=%s&etag=%s", - bucket, - getURLEncodedName(object), - getURLEncodedName("\""+objInfo.MD5Sum+"\"")) - - writeRedirectSeeOther(w, redirectURL) - } else { - // Decide what http response to send depending on success_action_status parameter - switch successStatus { - case "201": - resp := encodeResponse(PostResponse{ - Bucket: bucket, - Key: object, - ETag: "\"" + objInfo.MD5Sum + "\"", - Location: getObjectLocation(bucket, object), - }) - writeResponse(w, http.StatusCreated, resp, "application/xml") - case "200": - writeSuccessResponseHeadersOnly(w) - default: - writeSuccessNoContent(w) - } - } } - - // Notify object created event. - eventNotify(eventData{ - Type: ObjectCreatedPost, - Bucket: bucket, - ObjInfo: objInfo, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, - }) } // HeadBucketHandler - HEAD Bucket diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go index 3b0e852e1..19319f6a4 100644 --- a/cmd/handler-utils.go +++ b/cmd/handler-utils.go @@ -20,6 +20,7 @@ import ( "io" "mime/multipart" "net/http" + "net/url" "strings" ) @@ -105,6 +106,9 @@ func path2BucketAndObject(path string) (bucket, object string) { // extractMetadataFromHeader extracts metadata from HTTP header. func extractMetadataFromHeader(header http.Header) map[string]string { + if header == nil { + return nil + } metadata := make(map[string]string) // Save standard supported headers. for _, supportedHeader := range supportedHeaders { @@ -126,51 +130,67 @@ func extractMetadataFromHeader(header http.Header) map[string]string { metadata[cKey] = header.Get(key) } } - // Return. + + // Success. return metadata } -// extractMetadataFromForm extracts metadata from Post Form. -func extractMetadataFromForm(formValues map[string]string) map[string]string { - metadata := make(map[string]string) - // Save standard supported headers. - for _, supportedHeader := range supportedHeaders { - canonicalHeader := http.CanonicalHeaderKey(supportedHeader) - // Form field names are case insensitive, look for both canonical - // and non canonical entries. - if _, ok := formValues[canonicalHeader]; ok { - metadata[supportedHeader] = formValues[canonicalHeader] - } else if _, ok := formValues[supportedHeader]; ok { - metadata[supportedHeader] = formValues[canonicalHeader] - } +// The Query string for the redirect URL the client is +// redirected on successful upload. +func getRedirectPostRawQuery(objInfo ObjectInfo) string { + redirectValues := make(url.Values) + redirectValues.Set("bucket", objInfo.Bucket) + redirectValues.Set("key", objInfo.Name) + redirectValues.Set("etag", "\""+objInfo.MD5Sum+"\"") + return redirectValues.Encode() +} + +// Extract request params to be sent with event notifiation. +func extractReqParams(r *http.Request) map[string]string { + if r == nil { + return nil } - // Go through all other form values for any additional headers that needs to be saved. - for key := range formValues { - cKey := http.CanonicalHeaderKey(key) - if strings.HasPrefix(cKey, "X-Amz-Meta-") { - metadata[cKey] = formValues[key] - } else if strings.HasPrefix(cKey, "X-Minio-Meta-") { - metadata[cKey] = formValues[key] + + // Success. + return map[string]string{ + "sourceIPAddress": r.RemoteAddr, + // Add more fields here. + } +} + +// extractMetadataFromForm extracts metadata from Post Form. +func extractMetadataFromForm(formValues http.Header) map[string]string { + return extractMetadataFromHeader(formValues) +} + +// Validate form field size for s3 specification requirement. +func validateFormFieldSize(formValues http.Header) error { + // Iterate over form values + for k := range formValues { + // Check if value's field exceeds S3 limit + if int64(len(formValues.Get(k))) > maxFormFieldSize { + return traceError(errSizeUnexpected) } } - return metadata + + // Success. + return nil } // Extract form fields and file data from a HTTP POST Policy -func extractPostPolicyFormValues(form *multipart.Form) (filePart io.ReadCloser, fileName string, fileSize int64, formValues map[string]string, err error) { +func extractPostPolicyFormValues(form *multipart.Form) (filePart io.ReadCloser, fileName string, fileSize int64, formValues http.Header, err error) { /// HTML Form values - formValues = make(map[string]string) fileName = "" - // Iterate over form values + // Canonicalize the form values into http.Header. + formValues = make(http.Header) for k, v := range form.Value { - canonicalFormName := http.CanonicalHeaderKey(k) - // Check if value's field exceeds S3 limit - if int64(len(v[0])) > maxFormFieldSize { - return nil, "", 0, nil, traceError(errSizeUnexpected) - } - // Set the form value - formValues[canonicalFormName] = v[0] + formValues[http.CanonicalHeaderKey(k)] = v + } + + // Validate form values. + if err = validateFormFieldSize(formValues); err != nil { + return nil, "", 0, nil, err } // Iterator until we find a valid File field and break diff --git a/cmd/handler-utils_test.go b/cmd/handler-utils_test.go index 0bf34b3c5..3c0b4c2a8 100644 --- a/cmd/handler-utils_test.go +++ b/cmd/handler-utils_test.go @@ -22,6 +22,7 @@ import ( "io/ioutil" "net/http" "reflect" + "strings" "testing" ) @@ -83,6 +84,44 @@ func TestIsValidLocationContraint(t *testing.T) { } } +// Test validate form field size. +func TestValidateFormFieldSize(t *testing.T) { + testCases := []struct { + header http.Header + err error + }{ + // Empty header returns error as nil, + { + header: nil, + err: nil, + }, + // Valid header returns error as nil. + { + header: http.Header{ + "Content-Type": []string{"image/png"}, + }, + err: nil, + }, + // Invalid header value > maxFormFieldSize+1 + { + header: http.Header{ + "Garbage": []string{strings.Repeat("a", int(maxFormFieldSize)+1)}, + }, + err: errSizeUnexpected, + }, + } + + // Run validate form field size check under all test cases. + for i, testCase := range testCases { + err := validateFormFieldSize(testCase.header) + if err != nil { + if errorCause(err).Error() != testCase.err.Error() { + t.Errorf("Test %d: Expected error %s, got %s", i+1, testCase.err, err) + } + } + } +} + // Tests validate metadata extraction from http headers. func TestExtractMetadataHeaders(t *testing.T) { testCases := []struct { @@ -115,6 +154,11 @@ func TestExtractMetadataHeaders(t *testing.T) { "X-Amz-Meta-Appid": "amz-meta", "X-Minio-Meta-Appid": "minio-meta"}, }, + // Empty header input returns empty metadata. + { + header: nil, + metadata: nil, + }, } // Validate if the extracting headers. diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index e0e6c1beb..3e2037ed2 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -359,12 +359,10 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re // Notify object created event. eventNotify(eventData{ - Type: ObjectCreatedCopy, - Bucket: dstBucket, - ObjInfo: objInfo, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + Type: ObjectCreatedCopy, + Bucket: dstBucket, + ObjInfo: objInfo, + ReqParams: extractReqParams(r), }) } @@ -492,12 +490,10 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req // Notify object created event. eventNotify(eventData{ - Type: ObjectCreatedPut, - Bucket: bucket, - ObjInfo: objInfo, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + Type: ObjectCreatedPut, + Bucket: bucket, + ObjInfo: objInfo, + ReqParams: extractReqParams(r), }) } @@ -922,12 +918,10 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite // Notify object created event. eventNotify(eventData{ - Type: ObjectCreatedCompleteMultipartUpload, - Bucket: bucket, - ObjInfo: objInfo, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + Type: ObjectCreatedCompleteMultipartUpload, + Bucket: bucket, + ObjInfo: objInfo, + ReqParams: extractReqParams(r), }) } @@ -970,8 +964,6 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http. ObjInfo: ObjectInfo{ Name: object, }, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + ReqParams: extractReqParams(r), }) } diff --git a/cmd/post-policy_test.go b/cmd/post-policy_test.go index 14018979a..29df94acc 100644 --- a/cmd/post-policy_test.go +++ b/cmd/post-policy_test.go @@ -24,6 +24,7 @@ import ( "mime/multipart" "net/http" "net/http/httptest" + "net/url" "testing" "time" @@ -444,7 +445,10 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t targetObj := keyName + "/upload.txt" // The url of success_action_redirect field - redirectURL := "http://www.google.com" + redirectURL, err := url.Parse("http://www.google.com") + if err != nil { + t.Fatal(err) + } // Register the API end points with XL/FS object layer. apiRouter := initTestAPIEndPoints(obj, []string{"PostPolicy"}) @@ -464,7 +468,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t rec := httptest.NewRecorder() dates := []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)} - policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"]]}` + policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL.String() + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"]]}` // Generate the final policy document policy = fmt.Sprintf(policy, dates...) @@ -472,7 +476,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t // Create a new POST request with success_action_redirect field specified req, perr := newPostRequestV4Generic("", bucketName, keyName, []byte("objData"), credentials.AccessKey, credentials.SecretKey, curTime, - []byte(policy), map[string]string{"success_action_redirect": redirectURL}, false, false) + []byte(policy), map[string]string{"success_action_redirect": redirectURL.String()}, false, false) if perr != nil { t.Fatalf("%s: Failed to create HTTP request for PostPolicyHandler: %v", instanceType, perr) @@ -492,8 +496,8 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t t.Error("Unexpected error: ", err) } - expectedLocation := fmt.Sprintf(redirectURL+"?bucket=%s&key=%s&etag=%s", - bucketName, getURLEncodedName(targetObj), getURLEncodedName("\""+info.MD5Sum+"\"")) + redirectURL.RawQuery = getRedirectPostRawQuery(info) + expectedLocation := redirectURL.String() // Check the new location url if rec.HeaderMap.Get("Location") != expectedLocation { diff --git a/cmd/postpolicyform.go b/cmd/postpolicyform.go index 799f5f818..6c90a294e 100644 --- a/cmd/postpolicyform.go +++ b/cmd/postpolicyform.go @@ -219,7 +219,7 @@ func checkPolicyCond(op string, input1, input2 string) bool { // checkPostPolicy - apply policy conditions and validate input values. // (http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html) -func checkPostPolicy(formValues map[string]string, postPolicyForm PostPolicyForm) APIErrorCode { +func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) APIErrorCode { // Check if policy document expiry date is still not reached if !postPolicyForm.Expiration.After(time.Now().UTC()) { return ErrPolicyAlreadyExpired @@ -242,12 +242,12 @@ func checkPostPolicy(formValues map[string]string, postPolicyForm PostPolicyForm return ErrAccessDenied } // Check if current policy condition is satisfied - condPassed = checkPolicyCond(op, formValues[formCanonicalName], v.Value) + condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), v.Value) } else { // This covers all conditions X-Amz-Meta-* and X-Amz-* if strings.HasPrefix(cond, "$x-amz-meta-") || strings.HasPrefix(cond, "$x-amz-") { // Check if policy condition is satisfied - condPassed = checkPolicyCond(op, formValues[formCanonicalName], v.Value) + condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), v.Value) } } // Check if current policy condition is satisfied, quit immediately otherwise diff --git a/cmd/postpolicyform_test.go b/cmd/postpolicyform_test.go index 11bb712d4..d2c35c43f 100644 --- a/cmd/postpolicyform_test.go +++ b/cmd/postpolicyform_test.go @@ -18,12 +18,12 @@ package cmd import ( "encoding/base64" + "net/http" "testing" ) // Test Post Policy parsing and checking conditions func TestPostPolicyForm(t *testing.T) { - type testCase struct { Bucket string Key string @@ -60,18 +60,18 @@ func TestPostPolicyForm(t *testing.T) { } // Validate all the test cases. for i, tt := range testCases { - formValues := make(map[string]string) - formValues["Bucket"] = tt.Bucket - formValues["Acl"] = tt.ACL - formValues["Key"] = tt.Key - formValues["X-Amz-Date"] = tt.XAmzDate - formValues["X-Amz-Meta-Uuid"] = tt.XAmzMetaUUID - formValues["X-Amz-Server-Side-Encryption"] = tt.XAmzServerSideEncryption - formValues["X-Amz-Algorithm"] = tt.XAmzAlgorithm - formValues["X-Amz-Credential"] = tt.XAmzCredential - formValues["Content-Type"] = tt.ContentType - formValues["Policy"] = tt.Policy - formValues["Success_action_redirect"] = tt.SuccessActionRedirect + formValues := make(http.Header) + formValues.Set("Bucket", tt.Bucket) + formValues.Set("Acl", tt.ACL) + formValues.Set("Key", tt.Key) + formValues.Set("X-Amz-Date", tt.XAmzDate) + formValues.Set("X-Amz-Meta-Uuid", tt.XAmzMetaUUID) + formValues.Set("X-Amz-Server-Side-Encryption", tt.XAmzServerSideEncryption) + formValues.Set("X-Amz-Algorithm", tt.XAmzAlgorithm) + formValues.Set("X-Amz-Credential", tt.XAmzCredential) + formValues.Set("Content-Type", tt.ContentType) + formValues.Set("Policy", tt.Policy) + formValues.Set("Success_action_redirect", tt.SuccessActionRedirect) policyBytes, err := base64.StdEncoding.DecodeString(tt.Policy) if err != nil { t.Fatal(err) diff --git a/cmd/signature-v2.go b/cmd/signature-v2.go index 107dbe8c5..e1eecbc90 100644 --- a/cmd/signature-v2.go +++ b/cmd/signature-v2.go @@ -64,14 +64,14 @@ var resourceList = []string{ "website", } -func doesPolicySignatureV2Match(formValues map[string]string) APIErrorCode { +func doesPolicySignatureV2Match(formValues http.Header) APIErrorCode { cred := serverConfig.GetCredential() - accessKey := formValues["Awsaccesskeyid"] + accessKey := formValues.Get("AWSAccessKeyId") if accessKey != cred.AccessKey { return ErrInvalidAccessKeyID } - signature := formValues["Signature"] - policy := formValues["Policy"] + policy := formValues.Get("Policy") + signature := formValues.Get("Signature") if signature != calculateSignatureV2(policy, cred.SecretKey) { return ErrSignatureDoesNotMatch } diff --git a/cmd/signature-v2_test.go b/cmd/signature-v2_test.go index fc5b2dee9..3a2b778aa 100644 --- a/cmd/signature-v2_test.go +++ b/cmd/signature-v2_test.go @@ -208,10 +208,10 @@ func TestDoesPolicySignatureV2Match(t *testing.T) { {creds.AccessKey, policy, calculateSignatureV2(policy, creds.SecretKey), ErrNone}, } for i, test := range testCases { - formValues := make(map[string]string) - formValues["Awsaccesskeyid"] = test.accessKey - formValues["Signature"] = test.signature - formValues["Policy"] = test.policy + formValues := make(http.Header) + formValues.Set("Awsaccesskeyid", test.accessKey) + formValues.Set("Signature", test.signature) + formValues.Set("Policy", test.policy) errCode := doesPolicySignatureV2Match(formValues) if errCode != test.errCode { t.Fatalf("(%d) expected to get %s, instead got %s", i+1, niceError(test.errCode), niceError(errCode)) diff --git a/cmd/signature-v4.go b/cmd/signature-v4.go index f6a5ea2fb..d32d22435 100644 --- a/cmd/signature-v4.go +++ b/cmd/signature-v4.go @@ -147,9 +147,9 @@ func getSignature(signingKey []byte, stringToSign string) string { } // Check to see if Policy is signed correctly. -func doesPolicySignatureMatch(formValues map[string]string) APIErrorCode { +func doesPolicySignatureMatch(formValues http.Header) APIErrorCode { // For SignV2 - Signature field will be valid - if formValues["Signature"] != "" { + if _, ok := formValues["Signature"]; ok { return doesPolicySignatureV2Match(formValues) } return doesPolicySignatureV4Match(formValues) @@ -158,7 +158,7 @@ func doesPolicySignatureMatch(formValues map[string]string) APIErrorCode { // doesPolicySignatureMatch - Verify query headers with post policy // - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html // returns ErrNone if the signature matches. -func doesPolicySignatureV4Match(formValues map[string]string) APIErrorCode { +func doesPolicySignatureV4Match(formValues http.Header) APIErrorCode { // Access credentials. cred := serverConfig.GetCredential() @@ -166,7 +166,7 @@ func doesPolicySignatureV4Match(formValues map[string]string) APIErrorCode { region := serverConfig.GetRegion() // Parse credential tag. - credHeader, err := parseCredentialHeader("Credential=" + formValues["X-Amz-Credential"]) + credHeader, err := parseCredentialHeader("Credential=" + formValues.Get("X-Amz-Credential")) if err != ErrNone { return ErrMissingFields } @@ -186,12 +186,14 @@ func doesPolicySignatureV4Match(formValues map[string]string) APIErrorCode { signingKey := getSigningKey(cred.SecretKey, credHeader.scope.date, region) // Get signature. - newSignature := getSignature(signingKey, formValues["Policy"]) + newSignature := getSignature(signingKey, formValues.Get("Policy")) // Verify signature. - if newSignature != formValues["X-Amz-Signature"] { + if newSignature != formValues.Get("X-Amz-Signature") { return ErrSignatureDoesNotMatch } + + // Success. return ErrNone } diff --git a/cmd/signature-v4_test.go b/cmd/signature-v4_test.go index 12137d1e1..41422671d 100644 --- a/cmd/signature-v4_test.go +++ b/cmd/signature-v4_test.go @@ -39,45 +39,50 @@ func TestDoesPolicySignatureMatch(t *testing.T) { accessKey := serverConfig.GetCredential().AccessKey testCases := []struct { - form map[string]string + form http.Header expected APIErrorCode }{ // (0) It should fail if 'X-Amz-Credential' is missing. { - form: map[string]string{}, + form: http.Header{}, expected: ErrMissingFields, }, // (1) It should fail if the access key is incorrect. { - form: map[string]string{ - "X-Amz-Credential": fmt.Sprintf(credentialTemplate, "EXAMPLEINVALIDEXAMPL", now.Format(yyyymmdd), globalMinioDefaultRegion), + form: http.Header{ + "X-Amz-Credential": []string{fmt.Sprintf(credentialTemplate, "EXAMPLEINVALIDEXAMPL", now.Format(yyyymmdd), globalMinioDefaultRegion)}, }, expected: ErrInvalidAccessKeyID, }, // (2) It should fail if the region is invalid. { - form: map[string]string{ - "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), "invalidregion"), + form: http.Header{ + "X-Amz-Credential": []string{fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), "invalidregion")}, }, expected: ErrInvalidRegion, }, // (3) It should fail with a bad signature. { - form: map[string]string{ - "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion), - "X-Amz-Date": now.Format(iso8601Format), - "X-Amz-Signature": "invalidsignature", - "Policy": "policy", + form: http.Header{ + "X-Amz-Credential": []string{fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion)}, + "X-Amz-Date": []string{now.Format(iso8601Format)}, + "X-Amz-Signature": []string{"invalidsignature"}, + "Policy": []string{"policy"}, }, expected: ErrSignatureDoesNotMatch, }, // (4) It should succeed if everything is correct. { - form: map[string]string{ - "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion), - "X-Amz-Date": now.Format(iso8601Format), - "X-Amz-Signature": getSignature(getSigningKey(serverConfig.GetCredential().SecretKey, now, globalMinioDefaultRegion), "policy"), - "Policy": "policy", + form: http.Header{ + "X-Amz-Credential": []string{ + fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion), + }, + "X-Amz-Date": []string{now.Format(iso8601Format)}, + "X-Amz-Signature": []string{ + getSignature(getSigningKey(serverConfig.GetCredential().SecretKey, now, + globalMinioDefaultRegion), "policy"), + }, + "Policy": []string{"policy"}, }, expected: ErrNone, }, diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index c139e6f27..e5a189358 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -292,9 +292,7 @@ objectLoop: ObjInfo: ObjectInfo{ Name: objectName, }, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + ReqParams: extractReqParams(r), }) } return err @@ -528,12 +526,10 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { // Notify object created event. eventNotify(eventData{ - Type: ObjectCreatedPut, - Bucket: bucket, - ObjInfo: objInfo, - ReqParams: map[string]string{ - "sourceIPAddress": r.RemoteAddr, - }, + Type: ObjectCreatedPut, + Bucket: bucket, + ObjInfo: objInfo, + ReqParams: extractReqParams(r), }) }