mirror of
https://github.com/minio/minio.git
synced 2024-12-23 21:55:53 -05:00
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:
parent
3314501f19
commit
5f7565762e
@ -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 successStatus == "" && successRedirect == "" {
|
||||
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)
|
||||
}
|
||||
}
|
||||
if successRedirect != "" {
|
||||
// Replace raw query params..
|
||||
redirectURL.RawQuery = getRedirectPostRawQuery(objInfo)
|
||||
writeRedirectSeeOther(w, redirectURL.String())
|
||||
return
|
||||
}
|
||||
|
||||
// Notify object created event.
|
||||
eventNotify(eventData{
|
||||
Type: ObjectCreatedPost,
|
||||
Bucket: bucket,
|
||||
ObjInfo: objInfo,
|
||||
ReqParams: map[string]string{
|
||||
"sourceIPAddress": r.RemoteAddr,
|
||||
},
|
||||
})
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// HeadBucketHandler - HEAD Bucket
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
// 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.
|
||||
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]
|
||||
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)
|
||||
}
|
||||
}
|
||||
// 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]
|
||||
}
|
||||
}
|
||||
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
|
||||
|
@ -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.
|
||||
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
@ -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: <ERROR> %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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user