storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/post-policy_test.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2016, 2017, 2018 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cmd
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/base64"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"mime/multipart"
    26  	"net/http"
    27  	"net/http/httptest"
    28  	"net/url"
    29  	"testing"
    30  	"time"
    31  
    32  	humanize "github.com/dustin/go-humanize"
    33  )
    34  
    35  const (
    36  	iso8601DateFormat = "20060102T150405Z"
    37  )
    38  
    39  func newPostPolicyBytesV4WithContentRange(credential, bucketName, objectKey string, expiration time.Time) []byte {
    40  	t := UTCNow()
    41  	// Add the expiration date.
    42  	expirationStr := fmt.Sprintf(`"expiration": "%s"`, expiration.Format(iso8601TimeFormat))
    43  	// Add the bucket condition, only accept buckets equal to the one passed.
    44  	bucketConditionStr := fmt.Sprintf(`["eq", "$bucket", "%s"]`, bucketName)
    45  	// Add the key condition, only accept keys equal to the one passed.
    46  	keyConditionStr := fmt.Sprintf(`["eq", "$key", "%s/upload.txt"]`, objectKey)
    47  	// Add content length condition, only accept content sizes of a given length.
    48  	contentLengthCondStr := `["content-length-range", 1024, 1048576]`
    49  	// Add the algorithm condition, only accept AWS SignV4 Sha256.
    50  	algorithmConditionStr := `["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"]`
    51  	// Add the date condition, only accept the current date.
    52  	dateConditionStr := fmt.Sprintf(`["eq", "$x-amz-date", "%s"]`, t.Format(iso8601DateFormat))
    53  	// Add the credential string, only accept the credential passed.
    54  	credentialConditionStr := fmt.Sprintf(`["eq", "$x-amz-credential", "%s"]`, credential)
    55  	// Add the meta-uuid string, set to 1234
    56  	uuidConditionStr := fmt.Sprintf(`["eq", "$x-amz-meta-uuid", "%s"]`, "1234")
    57  
    58  	// Combine all conditions into one string.
    59  	conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s, %s]`, bucketConditionStr,
    60  		keyConditionStr, contentLengthCondStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr)
    61  	retStr := "{"
    62  	retStr = retStr + expirationStr + ","
    63  	retStr = retStr + conditionStr
    64  	retStr = retStr + "}"
    65  
    66  	return []byte(retStr)
    67  }
    68  
    69  // newPostPolicyBytesV4 - creates a bare bones postpolicy string with key and bucket matches.
    70  func newPostPolicyBytesV4(credential, bucketName, objectKey string, expiration time.Time) []byte {
    71  	t := UTCNow()
    72  	// Add the expiration date.
    73  	expirationStr := fmt.Sprintf(`"expiration": "%s"`, expiration.Format(iso8601TimeFormat))
    74  	// Add the bucket condition, only accept buckets equal to the one passed.
    75  	bucketConditionStr := fmt.Sprintf(`["eq", "$bucket", "%s"]`, bucketName)
    76  	// Add the key condition, only accept keys equal to the one passed.
    77  	keyConditionStr := fmt.Sprintf(`["eq", "$key", "%s/upload.txt"]`, objectKey)
    78  	// Add the algorithm condition, only accept AWS SignV4 Sha256.
    79  	algorithmConditionStr := `["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"]`
    80  	// Add the date condition, only accept the current date.
    81  	dateConditionStr := fmt.Sprintf(`["eq", "$x-amz-date", "%s"]`, t.Format(iso8601DateFormat))
    82  	// Add the credential string, only accept the credential passed.
    83  	credentialConditionStr := fmt.Sprintf(`["eq", "$x-amz-credential", "%s"]`, credential)
    84  	// Add the meta-uuid string, set to 1234
    85  	uuidConditionStr := fmt.Sprintf(`["eq", "$x-amz-meta-uuid", "%s"]`, "1234")
    86  
    87  	// Combine all conditions into one string.
    88  	conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s]`, bucketConditionStr, keyConditionStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr)
    89  	retStr := "{"
    90  	retStr = retStr + expirationStr + ","
    91  	retStr = retStr + conditionStr
    92  	retStr = retStr + "}"
    93  
    94  	return []byte(retStr)
    95  }
    96  
    97  // newPostPolicyBytesV2 - creates a bare bones postpolicy string with key and bucket matches.
    98  func newPostPolicyBytesV2(bucketName, objectKey string, expiration time.Time) []byte {
    99  	// Add the expiration date.
   100  	expirationStr := fmt.Sprintf(`"expiration": "%s"`, expiration.Format(iso8601TimeFormat))
   101  	// Add the bucket condition, only accept buckets equal to the one passed.
   102  	bucketConditionStr := fmt.Sprintf(`["eq", "$bucket", "%s"]`, bucketName)
   103  	// Add the key condition, only accept keys equal to the one passed.
   104  	keyConditionStr := fmt.Sprintf(`["starts-with", "$key", "%s/upload.txt"]`, objectKey)
   105  
   106  	// Combine all conditions into one string.
   107  	conditionStr := fmt.Sprintf(`"conditions":[%s, %s]`, bucketConditionStr, keyConditionStr)
   108  	retStr := "{"
   109  	retStr = retStr + expirationStr + ","
   110  	retStr = retStr + conditionStr
   111  	retStr = retStr + "}"
   112  
   113  	return []byte(retStr)
   114  }
   115  
   116  // Wrapper for calling TestPostPolicyBucketHandler tests for both Erasure multiple disks and single node setup.
   117  func TestPostPolicyBucketHandler(t *testing.T) {
   118  	ExecObjectLayerTest(t, testPostPolicyBucketHandler)
   119  }
   120  
   121  // testPostPolicyBucketHandler - Tests validate post policy handler uploading objects.
   122  func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErrHandler) {
   123  	if err := newTestConfig(globalMinioDefaultRegion, obj); err != nil {
   124  		t.Fatalf("Initializing config.json failed")
   125  	}
   126  
   127  	// get random bucket name.
   128  	bucketName := getRandomBucketName()
   129  
   130  	var opts ObjectOptions
   131  	// Register the API end points with Erasure/FS object layer.
   132  	apiRouter := initTestAPIEndPoints(obj, []string{"PostPolicy"})
   133  
   134  	credentials := globalActiveCred
   135  
   136  	curTime := UTCNow()
   137  	curTimePlus5Min := curTime.Add(time.Minute * 5)
   138  
   139  	// bucketnames[0].
   140  	// objectNames[0].
   141  	// uploadIds [0].
   142  	// Create bucket before initiating NewMultipartUpload.
   143  	err := obj.MakeBucketWithLocation(context.Background(), bucketName, BucketOptions{})
   144  	if err != nil {
   145  		// Failed to create newbucket, abort.
   146  		t.Fatalf("%s : %s", instanceType, err.Error())
   147  	}
   148  
   149  	// Test cases for signature-V2.
   150  	testCasesV2 := []struct {
   151  		expectedStatus int
   152  		accessKey      string
   153  		secretKey      string
   154  	}{
   155  		{http.StatusForbidden, "invalidaccesskey", credentials.SecretKey},
   156  		{http.StatusForbidden, credentials.AccessKey, "invalidsecretkey"},
   157  		{http.StatusNoContent, credentials.AccessKey, credentials.SecretKey},
   158  	}
   159  
   160  	for i, test := range testCasesV2 {
   161  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   162  		rec := httptest.NewRecorder()
   163  		req, perr := newPostRequestV2("", bucketName, "testobject", test.accessKey, test.secretKey)
   164  		if perr != nil {
   165  			t.Fatalf("Test %d: %s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", i+1, instanceType, perr)
   166  		}
   167  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
   168  		// Call the ServeHTTP to execute the handler.
   169  		apiRouter.ServeHTTP(rec, req)
   170  		if rec.Code != test.expectedStatus {
   171  			t.Fatalf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, test.expectedStatus, rec.Code)
   172  		}
   173  	}
   174  
   175  	// Test cases for signature-V4.
   176  	testCasesV4 := []struct {
   177  		objectName         string
   178  		data               []byte
   179  		expectedHeaders    map[string]string
   180  		expectedRespStatus int
   181  		accessKey          string
   182  		secretKey          string
   183  		malformedBody      bool
   184  	}{
   185  		// Success case.
   186  		{
   187  			objectName:         "test",
   188  			data:               []byte("Hello, World"),
   189  			expectedRespStatus: http.StatusNoContent,
   190  			expectedHeaders:    map[string]string{"X-Amz-Meta-Uuid": "1234"},
   191  			accessKey:          credentials.AccessKey,
   192  			secretKey:          credentials.SecretKey,
   193  			malformedBody:      false,
   194  		},
   195  		// Bad case invalid request.
   196  		{
   197  			objectName:         "test",
   198  			data:               []byte("Hello, World"),
   199  			expectedRespStatus: http.StatusForbidden,
   200  			accessKey:          "",
   201  			secretKey:          "",
   202  			malformedBody:      false,
   203  		},
   204  		// Bad case malformed input.
   205  		{
   206  			objectName:         "test",
   207  			data:               []byte("Hello, World"),
   208  			expectedRespStatus: http.StatusBadRequest,
   209  			accessKey:          credentials.AccessKey,
   210  			secretKey:          credentials.SecretKey,
   211  			malformedBody:      true,
   212  		},
   213  	}
   214  
   215  	for i, testCase := range testCasesV4 {
   216  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   217  		rec := httptest.NewRecorder()
   218  		req, perr := newPostRequestV4("", bucketName, testCase.objectName, testCase.data, testCase.accessKey, testCase.secretKey)
   219  		if perr != nil {
   220  			t.Fatalf("Test %d: %s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", i+1, instanceType, perr)
   221  		}
   222  		if testCase.malformedBody {
   223  			// Change the request body.
   224  			req.Body = ioutil.NopCloser(bytes.NewReader([]byte("Hello,")))
   225  		}
   226  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
   227  		// Call the ServeHTTP to execute the handler.
   228  		apiRouter.ServeHTTP(rec, req)
   229  		if rec.Code != testCase.expectedRespStatus {
   230  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
   231  		}
   232  		// When the operation is successful, check if sending metadata is successful too
   233  		if rec.Code == http.StatusNoContent {
   234  			objInfo, err := obj.GetObjectInfo(context.Background(), bucketName, testCase.objectName+"/upload.txt", opts)
   235  			if err != nil {
   236  				t.Error("Unexpected error: ", err)
   237  			}
   238  			for k, v := range testCase.expectedHeaders {
   239  				if objInfo.UserDefined[k] != v {
   240  					t.Errorf("Expected to have header %s with value %s, but found value `%s` instead", k, v, objInfo.UserDefined[k])
   241  				}
   242  			}
   243  		}
   244  	}
   245  
   246  	region := "us-east-1"
   247  	// Test cases for signature-V4.
   248  	testCasesV4BadData := []struct {
   249  		objectName         string
   250  		data               []byte
   251  		expectedRespStatus int
   252  		accessKey          string
   253  		secretKey          string
   254  		dates              []interface{}
   255  		policy             string
   256  		corruptedBase64    bool
   257  		corruptedMultipart bool
   258  	}{
   259  		// Success case.
   260  		{
   261  			objectName:         "test",
   262  			data:               []byte("Hello, World"),
   263  			expectedRespStatus: http.StatusNoContent,
   264  			accessKey:          credentials.AccessKey,
   265  			secretKey:          credentials.SecretKey,
   266  			dates:              []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
   267  			policy:             `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["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"],["eq", "$x-amz-meta-uuid", "1234"]]}`,
   268  		},
   269  		// Corrupted Base 64 result
   270  		{
   271  			objectName:         "test",
   272  			data:               []byte("Hello, World"),
   273  			expectedRespStatus: http.StatusBadRequest,
   274  			accessKey:          credentials.AccessKey,
   275  			secretKey:          credentials.SecretKey,
   276  			dates:              []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
   277  			policy:             `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["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"]]}`,
   278  			corruptedBase64:    true,
   279  		},
   280  		// Corrupted Multipart body
   281  		{
   282  			objectName:         "test",
   283  			data:               []byte("Hello, World"),
   284  			expectedRespStatus: http.StatusBadRequest,
   285  			accessKey:          credentials.AccessKey,
   286  			secretKey:          credentials.SecretKey,
   287  			dates:              []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
   288  			policy:             `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["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"]]}`,
   289  			corruptedMultipart: true,
   290  		},
   291  
   292  		// Bad case invalid request.
   293  		{
   294  			objectName:         "test",
   295  			data:               []byte("Hello, World"),
   296  			expectedRespStatus: http.StatusForbidden,
   297  			accessKey:          "",
   298  			secretKey:          "",
   299  			dates:              []interface{}{},
   300  			policy:             ``,
   301  		},
   302  		// Expired document
   303  		{
   304  			objectName:         "test",
   305  			data:               []byte("Hello, World"),
   306  			expectedRespStatus: http.StatusForbidden,
   307  			accessKey:          credentials.AccessKey,
   308  			secretKey:          credentials.SecretKey,
   309  			dates:              []interface{}{curTime.Add(-1 * time.Minute * 5).Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
   310  			policy:             `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["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"]]}`,
   311  		},
   312  		// Corrupted policy document
   313  		{
   314  			objectName:         "test",
   315  			data:               []byte("Hello, World"),
   316  			expectedRespStatus: http.StatusForbidden,
   317  			accessKey:          credentials.AccessKey,
   318  			secretKey:          credentials.SecretKey,
   319  			dates:              []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
   320  			policy:             `{"3/aws4_request"]]}`,
   321  		},
   322  	}
   323  
   324  	for i, testCase := range testCasesV4BadData {
   325  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   326  		rec := httptest.NewRecorder()
   327  
   328  		testCase.policy = fmt.Sprintf(testCase.policy, testCase.dates...)
   329  
   330  		req, perr := newPostRequestV4Generic("", bucketName, testCase.objectName, testCase.data, testCase.accessKey,
   331  			testCase.secretKey, region, curTime, []byte(testCase.policy), nil, testCase.corruptedBase64, testCase.corruptedMultipart)
   332  		if perr != nil {
   333  			t.Fatalf("Test %d: %s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", i+1, instanceType, perr)
   334  		}
   335  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
   336  		// Call the ServeHTTP to execute the handler.
   337  		apiRouter.ServeHTTP(rec, req)
   338  		if rec.Code != testCase.expectedRespStatus {
   339  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
   340  		}
   341  	}
   342  
   343  	testCases2 := []struct {
   344  		objectName          string
   345  		data                []byte
   346  		expectedRespStatus  int
   347  		accessKey           string
   348  		secretKey           string
   349  		malformedBody       bool
   350  		ignoreContentLength bool
   351  	}{
   352  		// Success case.
   353  		{
   354  			objectName:          "test",
   355  			data:                bytes.Repeat([]byte("a"), 1025),
   356  			expectedRespStatus:  http.StatusNoContent,
   357  			accessKey:           credentials.AccessKey,
   358  			secretKey:           credentials.SecretKey,
   359  			malformedBody:       false,
   360  			ignoreContentLength: false,
   361  		},
   362  		// Failed with Content-Length not specified.
   363  		{
   364  			objectName:          "test",
   365  			data:                bytes.Repeat([]byte("a"), 1025),
   366  			expectedRespStatus:  http.StatusNoContent,
   367  			accessKey:           credentials.AccessKey,
   368  			secretKey:           credentials.SecretKey,
   369  			malformedBody:       false,
   370  			ignoreContentLength: true,
   371  		},
   372  		// Failed with entity too small.
   373  		{
   374  			objectName:          "test",
   375  			data:                bytes.Repeat([]byte("a"), 1023),
   376  			expectedRespStatus:  http.StatusBadRequest,
   377  			accessKey:           credentials.AccessKey,
   378  			secretKey:           credentials.SecretKey,
   379  			malformedBody:       false,
   380  			ignoreContentLength: false,
   381  		},
   382  		// Failed with entity too large.
   383  		{
   384  			objectName:          "test",
   385  			data:                bytes.Repeat([]byte("a"), (1*humanize.MiByte)+1),
   386  			expectedRespStatus:  http.StatusBadRequest,
   387  			accessKey:           credentials.AccessKey,
   388  			secretKey:           credentials.SecretKey,
   389  			malformedBody:       false,
   390  			ignoreContentLength: false,
   391  		},
   392  	}
   393  
   394  	for i, testCase := range testCases2 {
   395  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   396  		rec := httptest.NewRecorder()
   397  		var req *http.Request
   398  		var perr error
   399  		if testCase.ignoreContentLength {
   400  			req, perr = newPostRequestV4("", bucketName, testCase.objectName, testCase.data, testCase.accessKey, testCase.secretKey)
   401  		} else {
   402  			req, perr = newPostRequestV4WithContentLength("", bucketName, testCase.objectName, testCase.data, testCase.accessKey, testCase.secretKey)
   403  		}
   404  		if perr != nil {
   405  			t.Fatalf("Test %d: %s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", i+1, instanceType, perr)
   406  		}
   407  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
   408  		// Call the ServeHTTP to execute the handler.
   409  		apiRouter.ServeHTTP(rec, req)
   410  		if rec.Code != testCase.expectedRespStatus {
   411  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
   412  		}
   413  	}
   414  
   415  }
   416  
   417  // Wrapper for calling TestPostPolicyBucketHandlerRedirect tests for both Erasure multiple disks and single node setup.
   418  func TestPostPolicyBucketHandlerRedirect(t *testing.T) {
   419  	ExecObjectLayerTest(t, testPostPolicyBucketHandlerRedirect)
   420  }
   421  
   422  // testPostPolicyBucketHandlerRedirect tests POST Object when success_action_redirect is specified
   423  func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t TestErrHandler) {
   424  	if err := newTestConfig(globalMinioDefaultRegion, obj); err != nil {
   425  		t.Fatalf("Initializing config.json failed")
   426  	}
   427  
   428  	// get random bucket name.
   429  	bucketName := getRandomBucketName()
   430  
   431  	// Key specified in Form data
   432  	keyName := "test/object"
   433  
   434  	var opts ObjectOptions
   435  
   436  	// The final name of the upload object
   437  	targetObj := keyName + "/upload.txt"
   438  
   439  	// The url of success_action_redirect field
   440  	redirectURL, err := url.Parse("http://www.google.com")
   441  	if err != nil {
   442  		t.Fatal(err)
   443  	}
   444  
   445  	// Register the API end points with Erasure/FS object layer.
   446  	apiRouter := initTestAPIEndPoints(obj, []string{"PostPolicy"})
   447  
   448  	credentials := globalActiveCred
   449  
   450  	curTime := UTCNow()
   451  	curTimePlus5Min := curTime.Add(time.Minute * 5)
   452  
   453  	err = obj.MakeBucketWithLocation(context.Background(), bucketName, BucketOptions{})
   454  	if err != nil {
   455  		// Failed to create newbucket, abort.
   456  		t.Fatalf("%s : %s", instanceType, err.Error())
   457  	}
   458  
   459  	// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   460  	rec := httptest.NewRecorder()
   461  
   462  	dates := []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}
   463  	policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL.String() + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-meta-uuid", "1234"], ["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"]]}`
   464  
   465  	// Generate the final policy document
   466  	policy = fmt.Sprintf(policy, dates...)
   467  
   468  	region := "us-east-1"
   469  	// Create a new POST request with success_action_redirect field specified
   470  	req, perr := newPostRequestV4Generic("", bucketName, keyName, []byte("objData"),
   471  		credentials.AccessKey, credentials.SecretKey, region, curTime,
   472  		[]byte(policy), map[string]string{"success_action_redirect": redirectURL.String()}, false, false)
   473  
   474  	if perr != nil {
   475  		t.Fatalf("%s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", instanceType, perr)
   476  	}
   477  	// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
   478  	// Call the ServeHTTP to execute the handler.
   479  	apiRouter.ServeHTTP(rec, req)
   480  
   481  	// Check the status code, which must be 303 because success_action_redirect is specified
   482  	if rec.Code != http.StatusSeeOther {
   483  		t.Errorf("%s: Expected the response status to be `%d`, but instead found `%d`", instanceType, http.StatusSeeOther, rec.Code)
   484  	}
   485  
   486  	// Get the uploaded object info
   487  	info, err := obj.GetObjectInfo(context.Background(), bucketName, targetObj, opts)
   488  	if err != nil {
   489  		t.Error("Unexpected error: ", err)
   490  	}
   491  
   492  	redirectURL.RawQuery = getRedirectPostRawQuery(info)
   493  	expectedLocation := redirectURL.String()
   494  
   495  	// Check the new location url
   496  	if rec.Header().Get("Location") != expectedLocation {
   497  		t.Errorf("Unexpected location, expected = %s, found = `%s`", rec.Header().Get("Location"), expectedLocation)
   498  	}
   499  
   500  }
   501  
   502  // postPresignSignatureV4 - presigned signature for PostPolicy requests.
   503  func postPresignSignatureV4(policyBase64 string, t time.Time, secretAccessKey, location string) string {
   504  	// Get signining key.
   505  	signingkey := getSigningKey(secretAccessKey, t, location, "s3")
   506  	// Calculate signature.
   507  	signature := getSignature(signingkey, policyBase64)
   508  	return signature
   509  }
   510  
   511  func newPostRequestV2(endPoint, bucketName, objectName string, accessKey, secretKey string) (*http.Request, error) {
   512  	// Expire the request five minutes from now.
   513  	expirationTime := UTCNow().Add(time.Minute * 5)
   514  	// Create a new post policy.
   515  	policy := newPostPolicyBytesV2(bucketName, objectName, expirationTime)
   516  	// Only need the encoding.
   517  	encodedPolicy := base64.StdEncoding.EncodeToString(policy)
   518  
   519  	// Presign with V4 signature based on the policy.
   520  	signature := calculateSignatureV2(encodedPolicy, secretKey)
   521  
   522  	formData := map[string]string{
   523  		"AWSAccessKeyId": accessKey,
   524  		"bucket":         bucketName,
   525  		"key":            objectName + "/${filename}",
   526  		"policy":         encodedPolicy,
   527  		"signature":      signature,
   528  	}
   529  
   530  	// Create the multipart form.
   531  	var buf bytes.Buffer
   532  	w := multipart.NewWriter(&buf)
   533  
   534  	// Set the normal formData
   535  	for k, v := range formData {
   536  		w.WriteField(k, v)
   537  	}
   538  	// Set the File formData
   539  	writer, err := w.CreateFormFile("file", "upload.txt")
   540  	if err != nil {
   541  		// return nil, err
   542  		return nil, err
   543  	}
   544  	writer.Write([]byte("hello world"))
   545  	// Close before creating the new request.
   546  	w.Close()
   547  
   548  	// Set the body equal to the created policy.
   549  	reader := bytes.NewReader(buf.Bytes())
   550  
   551  	req, err := http.NewRequest(http.MethodPost, makeTestTargetURL(endPoint, bucketName, "", nil), reader)
   552  	if err != nil {
   553  		return nil, err
   554  	}
   555  
   556  	// Set form content-type.
   557  	req.Header.Set("Content-Type", w.FormDataContentType())
   558  	return req, nil
   559  }
   560  
   561  func buildGenericPolicy(t time.Time, accessKey, region, bucketName, objectName string, contentLengthRange bool) []byte {
   562  	// Expire the request five minutes from now.
   563  	expirationTime := t.Add(time.Minute * 5)
   564  
   565  	credStr := getCredentialString(accessKey, region, t)
   566  	// Create a new post policy.
   567  	policy := newPostPolicyBytesV4(credStr, bucketName, objectName, expirationTime)
   568  	if contentLengthRange {
   569  		policy = newPostPolicyBytesV4WithContentRange(credStr, bucketName, objectName, expirationTime)
   570  	}
   571  	return policy
   572  }
   573  
   574  func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string, region string,
   575  	t time.Time, policy []byte, addFormData map[string]string, corruptedB64 bool, corruptedMultipart bool) (*http.Request, error) {
   576  	// Get the user credential.
   577  	credStr := getCredentialString(accessKey, region, t)
   578  
   579  	// Only need the encoding.
   580  	encodedPolicy := base64.StdEncoding.EncodeToString(policy)
   581  
   582  	if corruptedB64 {
   583  		encodedPolicy = "%!~&" + encodedPolicy
   584  	}
   585  
   586  	// Presign with V4 signature based on the policy.
   587  	signature := postPresignSignatureV4(encodedPolicy, t, secretKey, region)
   588  
   589  	formData := map[string]string{
   590  		"bucket":           bucketName,
   591  		"key":              objectName + "/${filename}",
   592  		"x-amz-credential": credStr,
   593  		"policy":           encodedPolicy,
   594  		"x-amz-signature":  signature,
   595  		"x-amz-date":       t.Format(iso8601DateFormat),
   596  		"x-amz-algorithm":  "AWS4-HMAC-SHA256",
   597  		"x-amz-meta-uuid":  "1234",
   598  		"Content-Encoding": "gzip",
   599  	}
   600  
   601  	// Add form data
   602  	for k, v := range addFormData {
   603  		formData[k] = v
   604  	}
   605  
   606  	// Create the multipart form.
   607  	var buf bytes.Buffer
   608  	w := multipart.NewWriter(&buf)
   609  
   610  	// Set the normal formData
   611  	for k, v := range formData {
   612  		w.WriteField(k, v)
   613  	}
   614  	// Set the File formData but don't if we want send an incomplete multipart request
   615  	if !corruptedMultipart {
   616  		writer, err := w.CreateFormFile("file", "upload.txt")
   617  		if err != nil {
   618  			// return nil, err
   619  			return nil, err
   620  		}
   621  		writer.Write(objData)
   622  		// Close before creating the new request.
   623  		w.Close()
   624  	}
   625  
   626  	// Set the body equal to the created policy.
   627  	reader := bytes.NewReader(buf.Bytes())
   628  
   629  	req, err := http.NewRequest(http.MethodPost, makeTestTargetURL(endPoint, bucketName, "", nil), reader)
   630  	if err != nil {
   631  		return nil, err
   632  	}
   633  
   634  	// Set form content-type.
   635  	req.Header.Set("Content-Type", w.FormDataContentType())
   636  	return req, nil
   637  }
   638  
   639  func newPostRequestV4WithContentLength(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string) (*http.Request, error) {
   640  	t := UTCNow()
   641  	region := "us-east-1"
   642  	policy := buildGenericPolicy(t, accessKey, region, bucketName, objectName, true)
   643  	return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, region, t, policy, nil, false, false)
   644  }
   645  
   646  func newPostRequestV4(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string) (*http.Request, error) {
   647  	t := UTCNow()
   648  	region := "us-east-1"
   649  	policy := buildGenericPolicy(t, accessKey, region, bucketName, objectName, false)
   650  	return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, region, t, policy, nil, false, false)
   651  }