mirror of https://github.com/minio/minio.git
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 (
|
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,50 +512,40 @@ 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 == "" {
|
if successRedirect != "" {
|
||||||
writeSuccessNoContent(w)
|
// Replace raw query params..
|
||||||
} else {
|
redirectURL.RawQuery = getRedirectPostRawQuery(objInfo)
|
||||||
if successRedirect != "" {
|
writeRedirectSeeOther(w, redirectURL.String())
|
||||||
redirectURL := successRedirect + "?" + fmt.Sprintf("bucket=%s&key=%s&etag=%s",
|
return
|
||||||
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.
|
// Decide what http response to send depending on success_action_status parameter
|
||||||
eventNotify(eventData{
|
switch successStatus {
|
||||||
Type: ObjectCreatedPost,
|
case "201":
|
||||||
Bucket: bucket,
|
resp := encodeResponse(PostResponse{
|
||||||
ObjInfo: objInfo,
|
Bucket: objInfo.Bucket,
|
||||||
ReqParams: map[string]string{
|
Key: objInfo.Name,
|
||||||
"sourceIPAddress": r.RemoteAddr,
|
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
|
// HeadBucketHandler - HEAD Bucket
|
||||||
|
|
|
@ -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)
|
// Validate form values.
|
||||||
}
|
if err = validateFormFieldSize(formValues); err != nil {
|
||||||
// Set the form value
|
return nil, "", 0, nil, err
|
||||||
formValues[canonicalFormName] = v[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterator until we find a valid File field and break
|
// Iterator until we find a valid File field and break
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -359,12 +359,10 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||||
|
|
||||||
// Notify object created event.
|
// Notify object created event.
|
||||||
eventNotify(eventData{
|
eventNotify(eventData{
|
||||||
Type: ObjectCreatedCopy,
|
Type: ObjectCreatedCopy,
|
||||||
Bucket: dstBucket,
|
Bucket: dstBucket,
|
||||||
ObjInfo: objInfo,
|
ObjInfo: objInfo,
|
||||||
ReqParams: map[string]string{
|
ReqParams: extractReqParams(r),
|
||||||
"sourceIPAddress": r.RemoteAddr,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,12 +490,10 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||||
|
|
||||||
// Notify object created event.
|
// Notify object created event.
|
||||||
eventNotify(eventData{
|
eventNotify(eventData{
|
||||||
Type: ObjectCreatedPut,
|
Type: ObjectCreatedPut,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
ObjInfo: objInfo,
|
ObjInfo: objInfo,
|
||||||
ReqParams: map[string]string{
|
ReqParams: extractReqParams(r),
|
||||||
"sourceIPAddress": r.RemoteAddr,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -922,12 +918,10 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||||
|
|
||||||
// Notify object created event.
|
// Notify object created event.
|
||||||
eventNotify(eventData{
|
eventNotify(eventData{
|
||||||
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,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
|
@ -528,12 +526,10 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Notify object created event.
|
// Notify object created event.
|
||||||
eventNotify(eventData{
|
eventNotify(eventData{
|
||||||
Type: ObjectCreatedPut,
|
Type: ObjectCreatedPut,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
ObjInfo: objInfo,
|
ObjInfo: objInfo,
|
||||||
ReqParams: map[string]string{
|
ReqParams: extractReqParams(r),
|
||||||
"sourceIPAddress": r.RemoteAddr,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue