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.
This commit is contained in:
Harshavardhana 2017-03-13 14:41:13 -07:00 committed by GitHub
parent 3314501f19
commit 5f7565762e
12 changed files with 222 additions and 162 deletions

View File

@ -19,7 +19,6 @@ package cmd
import ( import (
"encoding/base64" "encoding/base64"
"encoding/xml" "encoding/xml"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@ -327,9 +326,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
ObjInfo: ObjectInfo{ ObjInfo: ObjectInfo{
Name: dobj.ObjectName, Name: dobj.ObjectName,
}, },
ReqParams: map[string]string{ ReqParams: extractReqParams(r),
"sourceIPAddress": r.RemoteAddr,
},
}) })
} }
} }
@ -439,14 +436,25 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
defer fileBody.Close() defer fileBody.Close()
bucket := mux.Vars(r)["bucket"] 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 // S3 feature to replace ${filename} found in Key form field
// by the filename attribute passed in multipart // 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. // Verify policy signature.
apiErr := doesPolicySignatureMatch(formValues) apiErr := doesPolicySignatureMatch(formValues)
@ -455,7 +463,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
return return
} }
policyBytes, err := base64.StdEncoding.DecodeString(formValues["Policy"]) policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy"))
if err != nil { if err != nil {
writeErrorResponse(w, ErrMalformedPOSTRequest, r.URL) writeErrorResponse(w, ErrMalformedPOSTRequest, r.URL)
return return
@ -492,7 +500,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
// Extract metadata to be saved from received Form. // Extract metadata to be saved from received Form.
metadata := extractMetadataFromForm(formValues) metadata := extractMetadataFromForm(formValues)
sha256sum := "" sha256sum := ""
objectLock := globalNSMutex.NewNSLock(bucket, object) objectLock := globalNSMutex.NewNSLock(bucket, object)
@ -505,31 +512,33 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return
} }
w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"")
w.Header().Set("ETag", `"`+objInfo.MD5Sum+`"`)
w.Header().Set("Location", getObjectLocation(bucket, object)) w.Header().Set("Location", getObjectLocation(bucket, object))
successRedirect := formValues[http.CanonicalHeaderKey("success_action_redirect")] // Notify object created event.
successStatus := formValues[http.CanonicalHeaderKey("success_action_status")] defer eventNotify(eventData{
Type: ObjectCreatedPost,
Bucket: objInfo.Bucket,
ObjInfo: objInfo,
ReqParams: extractReqParams(r),
})
if successStatus == "" && successRedirect == "" {
writeSuccessNoContent(w)
} else {
if successRedirect != "" { if successRedirect != "" {
redirectURL := successRedirect + "?" + fmt.Sprintf("bucket=%s&key=%s&etag=%s", // Replace raw query params..
bucket, redirectURL.RawQuery = getRedirectPostRawQuery(objInfo)
getURLEncodedName(object), writeRedirectSeeOther(w, redirectURL.String())
getURLEncodedName("\""+objInfo.MD5Sum+"\"")) return
}
writeRedirectSeeOther(w, redirectURL)
} else {
// Decide what http response to send depending on success_action_status parameter // Decide what http response to send depending on success_action_status parameter
switch successStatus { switch successStatus {
case "201": case "201":
resp := encodeResponse(PostResponse{ resp := encodeResponse(PostResponse{
Bucket: bucket, Bucket: objInfo.Bucket,
Key: object, Key: objInfo.Name,
ETag: "\"" + objInfo.MD5Sum + "\"", ETag: `"` + objInfo.MD5Sum + `"`,
Location: getObjectLocation(bucket, object), Location: getObjectLocation(objInfo.Bucket, objInfo.Name),
}) })
writeResponse(w, http.StatusCreated, resp, "application/xml") writeResponse(w, http.StatusCreated, resp, "application/xml")
case "200": case "200":
@ -537,18 +546,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
default: default:
writeSuccessNoContent(w) writeSuccessNoContent(w)
} }
}
}
// Notify object created event.
eventNotify(eventData{
Type: ObjectCreatedPost,
Bucket: bucket,
ObjInfo: objInfo,
ReqParams: map[string]string{
"sourceIPAddress": r.RemoteAddr,
},
})
} }
// HeadBucketHandler - HEAD Bucket // HeadBucketHandler - HEAD Bucket

View File

@ -20,6 +20,7 @@ import (
"io" "io"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/url"
"strings" "strings"
) )
@ -105,6 +106,9 @@ func path2BucketAndObject(path string) (bucket, object string) {
// extractMetadataFromHeader extracts metadata from HTTP header. // extractMetadataFromHeader extracts metadata from HTTP header.
func extractMetadataFromHeader(header http.Header) map[string]string { func extractMetadataFromHeader(header http.Header) map[string]string {
if header == nil {
return nil
}
metadata := make(map[string]string) metadata := make(map[string]string)
// Save standard supported headers. // Save standard supported headers.
for _, supportedHeader := range supportedHeaders { for _, supportedHeader := range supportedHeaders {
@ -126,51 +130,67 @@ func extractMetadataFromHeader(header http.Header) map[string]string {
metadata[cKey] = header.Get(key) metadata[cKey] = header.Get(key)
} }
} }
// Return.
// Success.
return metadata return metadata
} }
// 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
}
// Success.
return map[string]string{
"sourceIPAddress": r.RemoteAddr,
// Add more fields here.
}
}
// extractMetadataFromForm extracts metadata from Post Form. // extractMetadataFromForm extracts metadata from Post Form.
func extractMetadataFromForm(formValues map[string]string) map[string]string { func extractMetadataFromForm(formValues http.Header) map[string]string {
metadata := make(map[string]string) return extractMetadataFromHeader(formValues)
// Save standard supported headers. }
for _, supportedHeader := range supportedHeaders {
canonicalHeader := http.CanonicalHeaderKey(supportedHeader) // Validate form field size for s3 specification requirement.
// Form field names are case insensitive, look for both canonical func validateFormFieldSize(formValues http.Header) error {
// and non canonical entries. // Iterate over form values
if _, ok := formValues[canonicalHeader]; ok { for k := range formValues {
metadata[supportedHeader] = formValues[canonicalHeader] // Check if value's field exceeds S3 limit
} else if _, ok := formValues[supportedHeader]; ok { if int64(len(formValues.Get(k))) > maxFormFieldSize {
metadata[supportedHeader] = formValues[canonicalHeader] return traceError(errSizeUnexpected)
} }
} }
// Go through all other form values for any additional headers that needs to be saved.
for key := range formValues { // Success.
cKey := http.CanonicalHeaderKey(key) return nil
if strings.HasPrefix(cKey, "X-Amz-Meta-") {
metadata[cKey] = formValues[key]
} else if strings.HasPrefix(cKey, "X-Minio-Meta-") {
metadata[cKey] = formValues[key]
}
}
return metadata
} }
// Extract form fields and file data from a HTTP POST Policy // 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 /// HTML Form values
formValues = make(map[string]string)
fileName = "" fileName = ""
// Iterate over form values // Canonicalize the form values into http.Header.
formValues = make(http.Header)
for k, v := range form.Value { for k, v := range form.Value {
canonicalFormName := http.CanonicalHeaderKey(k) formValues[http.CanonicalHeaderKey(k)] = v
// 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] // Validate form values.
if err = validateFormFieldSize(formValues); err != nil {
return nil, "", 0, nil, err
} }
// Iterator until we find a valid File field and break // Iterator until we find a valid File field and break

View File

@ -22,6 +22,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"reflect" "reflect"
"strings"
"testing" "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. // Tests validate metadata extraction from http headers.
func TestExtractMetadataHeaders(t *testing.T) { func TestExtractMetadataHeaders(t *testing.T) {
testCases := []struct { testCases := []struct {
@ -115,6 +154,11 @@ func TestExtractMetadataHeaders(t *testing.T) {
"X-Amz-Meta-Appid": "amz-meta", "X-Amz-Meta-Appid": "amz-meta",
"X-Minio-Meta-Appid": "minio-meta"}, "X-Minio-Meta-Appid": "minio-meta"},
}, },
// Empty header input returns empty metadata.
{
header: nil,
metadata: nil,
},
} }
// Validate if the extracting headers. // Validate if the extracting headers.

View File

@ -362,9 +362,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
Type: ObjectCreatedCopy, Type: ObjectCreatedCopy,
Bucket: dstBucket, Bucket: dstBucket,
ObjInfo: objInfo, ObjInfo: objInfo,
ReqParams: map[string]string{ ReqParams: extractReqParams(r),
"sourceIPAddress": r.RemoteAddr,
},
}) })
} }
@ -495,9 +493,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
Type: ObjectCreatedPut, Type: ObjectCreatedPut,
Bucket: bucket, Bucket: bucket,
ObjInfo: objInfo, ObjInfo: objInfo,
ReqParams: map[string]string{ ReqParams: extractReqParams(r),
"sourceIPAddress": r.RemoteAddr,
},
}) })
} }
@ -925,9 +921,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
Type: ObjectCreatedCompleteMultipartUpload, Type: ObjectCreatedCompleteMultipartUpload,
Bucket: bucket, Bucket: bucket,
ObjInfo: objInfo, ObjInfo: objInfo,
ReqParams: map[string]string{ ReqParams: extractReqParams(r),
"sourceIPAddress": r.RemoteAddr,
},
}) })
} }
@ -970,8 +964,6 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.
ObjInfo: ObjectInfo{ ObjInfo: ObjectInfo{
Name: object, Name: object,
}, },
ReqParams: map[string]string{ ReqParams: extractReqParams(r),
"sourceIPAddress": r.RemoteAddr,
},
}) })
} }

View File

@ -24,6 +24,7 @@ import (
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"testing" "testing"
"time" "time"
@ -444,7 +445,10 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t
targetObj := keyName + "/upload.txt" targetObj := keyName + "/upload.txt"
// The url of success_action_redirect field // 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. // Register the API end points with XL/FS object layer.
apiRouter := initTestAPIEndPoints(obj, []string{"PostPolicy"}) apiRouter := initTestAPIEndPoints(obj, []string{"PostPolicy"})
@ -464,7 +468,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
dates := []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)} 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 // Generate the final policy document
policy = fmt.Sprintf(policy, dates...) 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 // Create a new POST request with success_action_redirect field specified
req, perr := newPostRequestV4Generic("", bucketName, keyName, []byte("objData"), req, perr := newPostRequestV4Generic("", bucketName, keyName, []byte("objData"),
credentials.AccessKey, credentials.SecretKey, curTime, 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 { if perr != nil {
t.Fatalf("%s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", instanceType, perr) t.Fatalf("%s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", instanceType, perr)
@ -492,8 +496,8 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t
t.Error("Unexpected error: ", err) t.Error("Unexpected error: ", err)
} }
expectedLocation := fmt.Sprintf(redirectURL+"?bucket=%s&key=%s&etag=%s", redirectURL.RawQuery = getRedirectPostRawQuery(info)
bucketName, getURLEncodedName(targetObj), getURLEncodedName("\""+info.MD5Sum+"\"")) expectedLocation := redirectURL.String()
// Check the new location url // Check the new location url
if rec.HeaderMap.Get("Location") != expectedLocation { if rec.HeaderMap.Get("Location") != expectedLocation {

View File

@ -219,7 +219,7 @@ func checkPolicyCond(op string, input1, input2 string) bool {
// checkPostPolicy - apply policy conditions and validate input values. // checkPostPolicy - apply policy conditions and validate input values.
// (http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html) // (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 // Check if policy document expiry date is still not reached
if !postPolicyForm.Expiration.After(time.Now().UTC()) { if !postPolicyForm.Expiration.After(time.Now().UTC()) {
return ErrPolicyAlreadyExpired return ErrPolicyAlreadyExpired
@ -242,12 +242,12 @@ func checkPostPolicy(formValues map[string]string, postPolicyForm PostPolicyForm
return ErrAccessDenied return ErrAccessDenied
} }
// Check if current policy condition is satisfied // Check if current policy condition is satisfied
condPassed = checkPolicyCond(op, formValues[formCanonicalName], v.Value) condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), v.Value)
} else { } else {
// This covers all conditions X-Amz-Meta-* and X-Amz-* // 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(cond, "$x-amz-meta-") || strings.HasPrefix(cond, "$x-amz-") {
// Check if policy condition is satisfied // 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 // Check if current policy condition is satisfied, quit immediately otherwise

View File

@ -18,12 +18,12 @@ package cmd
import ( import (
"encoding/base64" "encoding/base64"
"net/http"
"testing" "testing"
) )
// Test Post Policy parsing and checking conditions // Test Post Policy parsing and checking conditions
func TestPostPolicyForm(t *testing.T) { func TestPostPolicyForm(t *testing.T) {
type testCase struct { type testCase struct {
Bucket string Bucket string
Key string Key string
@ -60,18 +60,18 @@ func TestPostPolicyForm(t *testing.T) {
} }
// Validate all the test cases. // Validate all the test cases.
for i, tt := range testCases { for i, tt := range testCases {
formValues := make(map[string]string) formValues := make(http.Header)
formValues["Bucket"] = tt.Bucket formValues.Set("Bucket", tt.Bucket)
formValues["Acl"] = tt.ACL formValues.Set("Acl", tt.ACL)
formValues["Key"] = tt.Key formValues.Set("Key", tt.Key)
formValues["X-Amz-Date"] = tt.XAmzDate formValues.Set("X-Amz-Date", tt.XAmzDate)
formValues["X-Amz-Meta-Uuid"] = tt.XAmzMetaUUID formValues.Set("X-Amz-Meta-Uuid", tt.XAmzMetaUUID)
formValues["X-Amz-Server-Side-Encryption"] = tt.XAmzServerSideEncryption formValues.Set("X-Amz-Server-Side-Encryption", tt.XAmzServerSideEncryption)
formValues["X-Amz-Algorithm"] = tt.XAmzAlgorithm formValues.Set("X-Amz-Algorithm", tt.XAmzAlgorithm)
formValues["X-Amz-Credential"] = tt.XAmzCredential formValues.Set("X-Amz-Credential", tt.XAmzCredential)
formValues["Content-Type"] = tt.ContentType formValues.Set("Content-Type", tt.ContentType)
formValues["Policy"] = tt.Policy formValues.Set("Policy", tt.Policy)
formValues["Success_action_redirect"] = tt.SuccessActionRedirect formValues.Set("Success_action_redirect", tt.SuccessActionRedirect)
policyBytes, err := base64.StdEncoding.DecodeString(tt.Policy) policyBytes, err := base64.StdEncoding.DecodeString(tt.Policy)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -64,14 +64,14 @@ var resourceList = []string{
"website", "website",
} }
func doesPolicySignatureV2Match(formValues map[string]string) APIErrorCode { func doesPolicySignatureV2Match(formValues http.Header) APIErrorCode {
cred := serverConfig.GetCredential() cred := serverConfig.GetCredential()
accessKey := formValues["Awsaccesskeyid"] accessKey := formValues.Get("AWSAccessKeyId")
if accessKey != cred.AccessKey { if accessKey != cred.AccessKey {
return ErrInvalidAccessKeyID return ErrInvalidAccessKeyID
} }
signature := formValues["Signature"] policy := formValues.Get("Policy")
policy := formValues["Policy"] signature := formValues.Get("Signature")
if signature != calculateSignatureV2(policy, cred.SecretKey) { if signature != calculateSignatureV2(policy, cred.SecretKey) {
return ErrSignatureDoesNotMatch return ErrSignatureDoesNotMatch
} }

View File

@ -208,10 +208,10 @@ func TestDoesPolicySignatureV2Match(t *testing.T) {
{creds.AccessKey, policy, calculateSignatureV2(policy, creds.SecretKey), ErrNone}, {creds.AccessKey, policy, calculateSignatureV2(policy, creds.SecretKey), ErrNone},
} }
for i, test := range testCases { for i, test := range testCases {
formValues := make(map[string]string) formValues := make(http.Header)
formValues["Awsaccesskeyid"] = test.accessKey formValues.Set("Awsaccesskeyid", test.accessKey)
formValues["Signature"] = test.signature formValues.Set("Signature", test.signature)
formValues["Policy"] = test.policy formValues.Set("Policy", test.policy)
errCode := doesPolicySignatureV2Match(formValues) errCode := doesPolicySignatureV2Match(formValues)
if errCode != test.errCode { if errCode != test.errCode {
t.Fatalf("(%d) expected to get %s, instead got %s", i+1, niceError(test.errCode), niceError(errCode)) t.Fatalf("(%d) expected to get %s, instead got %s", i+1, niceError(test.errCode), niceError(errCode))

View File

@ -147,9 +147,9 @@ func getSignature(signingKey []byte, stringToSign string) string {
} }
// Check to see if Policy is signed correctly. // 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 // For SignV2 - Signature field will be valid
if formValues["Signature"] != "" { if _, ok := formValues["Signature"]; ok {
return doesPolicySignatureV2Match(formValues) return doesPolicySignatureV2Match(formValues)
} }
return doesPolicySignatureV4Match(formValues) return doesPolicySignatureV4Match(formValues)
@ -158,7 +158,7 @@ func doesPolicySignatureMatch(formValues map[string]string) APIErrorCode {
// doesPolicySignatureMatch - Verify query headers with post policy // doesPolicySignatureMatch - Verify query headers with post policy
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html // - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
// returns ErrNone if the signature matches. // returns ErrNone if the signature matches.
func doesPolicySignatureV4Match(formValues map[string]string) APIErrorCode { func doesPolicySignatureV4Match(formValues http.Header) APIErrorCode {
// Access credentials. // Access credentials.
cred := serverConfig.GetCredential() cred := serverConfig.GetCredential()
@ -166,7 +166,7 @@ func doesPolicySignatureV4Match(formValues map[string]string) APIErrorCode {
region := serverConfig.GetRegion() region := serverConfig.GetRegion()
// Parse credential tag. // Parse credential tag.
credHeader, err := parseCredentialHeader("Credential=" + formValues["X-Amz-Credential"]) credHeader, err := parseCredentialHeader("Credential=" + formValues.Get("X-Amz-Credential"))
if err != ErrNone { if err != ErrNone {
return ErrMissingFields return ErrMissingFields
} }
@ -186,12 +186,14 @@ func doesPolicySignatureV4Match(formValues map[string]string) APIErrorCode {
signingKey := getSigningKey(cred.SecretKey, credHeader.scope.date, region) signingKey := getSigningKey(cred.SecretKey, credHeader.scope.date, region)
// Get signature. // Get signature.
newSignature := getSignature(signingKey, formValues["Policy"]) newSignature := getSignature(signingKey, formValues.Get("Policy"))
// Verify signature. // Verify signature.
if newSignature != formValues["X-Amz-Signature"] { if newSignature != formValues.Get("X-Amz-Signature") {
return ErrSignatureDoesNotMatch return ErrSignatureDoesNotMatch
} }
// Success.
return ErrNone return ErrNone
} }

View File

@ -39,45 +39,50 @@ func TestDoesPolicySignatureMatch(t *testing.T) {
accessKey := serverConfig.GetCredential().AccessKey accessKey := serverConfig.GetCredential().AccessKey
testCases := []struct { testCases := []struct {
form map[string]string form http.Header
expected APIErrorCode expected APIErrorCode
}{ }{
// (0) It should fail if 'X-Amz-Credential' is missing. // (0) It should fail if 'X-Amz-Credential' is missing.
{ {
form: map[string]string{}, form: http.Header{},
expected: ErrMissingFields, expected: ErrMissingFields,
}, },
// (1) It should fail if the access key is incorrect. // (1) It should fail if the access key is incorrect.
{ {
form: map[string]string{ form: http.Header{
"X-Amz-Credential": fmt.Sprintf(credentialTemplate, "EXAMPLEINVALIDEXAMPL", now.Format(yyyymmdd), globalMinioDefaultRegion), "X-Amz-Credential": []string{fmt.Sprintf(credentialTemplate, "EXAMPLEINVALIDEXAMPL", now.Format(yyyymmdd), globalMinioDefaultRegion)},
}, },
expected: ErrInvalidAccessKeyID, expected: ErrInvalidAccessKeyID,
}, },
// (2) It should fail if the region is invalid. // (2) It should fail if the region is invalid.
{ {
form: map[string]string{ form: http.Header{
"X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), "invalidregion"), "X-Amz-Credential": []string{fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), "invalidregion")},
}, },
expected: ErrInvalidRegion, expected: ErrInvalidRegion,
}, },
// (3) It should fail with a bad signature. // (3) It should fail with a bad signature.
{ {
form: map[string]string{ form: http.Header{
"X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion), "X-Amz-Credential": []string{fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion)},
"X-Amz-Date": now.Format(iso8601Format), "X-Amz-Date": []string{now.Format(iso8601Format)},
"X-Amz-Signature": "invalidsignature", "X-Amz-Signature": []string{"invalidsignature"},
"Policy": "policy", "Policy": []string{"policy"},
}, },
expected: ErrSignatureDoesNotMatch, expected: ErrSignatureDoesNotMatch,
}, },
// (4) It should succeed if everything is correct. // (4) It should succeed if everything is correct.
{ {
form: map[string]string{ form: http.Header{
"X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion), "X-Amz-Credential": []string{
"X-Amz-Date": now.Format(iso8601Format), fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion),
"X-Amz-Signature": getSignature(getSigningKey(serverConfig.GetCredential().SecretKey, now, globalMinioDefaultRegion), "policy"), },
"Policy": "policy", "X-Amz-Date": []string{now.Format(iso8601Format)},
"X-Amz-Signature": []string{
getSignature(getSigningKey(serverConfig.GetCredential().SecretKey, now,
globalMinioDefaultRegion), "policy"),
},
"Policy": []string{"policy"},
}, },
expected: ErrNone, expected: ErrNone,
}, },

View File

@ -292,9 +292,7 @@ objectLoop:
ObjInfo: ObjectInfo{ ObjInfo: ObjectInfo{
Name: objectName, Name: objectName,
}, },
ReqParams: map[string]string{ ReqParams: extractReqParams(r),
"sourceIPAddress": r.RemoteAddr,
},
}) })
} }
return err return err
@ -531,9 +529,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
Type: ObjectCreatedPut, Type: ObjectCreatedPut,
Bucket: bucket, Bucket: bucket,
ObjInfo: objInfo, ObjInfo: objInfo,
ReqParams: map[string]string{ ReqParams: extractReqParams(r),
"sourceIPAddress": r.RemoteAddr,
},
}) })
} }