mirror of https://github.com/minio/minio.git
				
				
				
			
		
			
	
	
		
			752 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
		
		
			
		
	
	
			752 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
|  | /* | ||
|  |  * Minio Cloud Storage, (C) 2016 Minio, Inc. | ||
|  |  * | ||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||
|  |  * you may not use this file except in compliance with the License. | ||
|  |  * You may obtain a copy of the License at | ||
|  |  * | ||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | ||
|  |  * | ||
|  |  * Unless required by applicable law or agreed to in writing, software | ||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
|  |  * See the License for the specific language governing permissions and | ||
|  |  * limitations under the License. | ||
|  |  */ | ||
|  | 
 | ||
|  | package cmd | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"net/url" | ||
|  | 	"strconv" | ||
|  | 	"strings" | ||
|  | 	"testing" | ||
|  | 	"time" | ||
|  | ) | ||
|  | 
 | ||
|  | // generates credential string from its fields.
 | ||
|  | func generateCredentialStr(accessKey, date, region, service, requestVersion string) string { | ||
|  | 	return "Credential=" + joinWithSlash(accessKey, date, region, service, requestVersion) | ||
|  | } | ||
|  | 
 | ||
|  | // joins the argument strings with a '/' and returns it.
 | ||
|  | func joinWithSlash(accessKey, date, region, service, requestVersion string) string { | ||
|  | 	return strings.Join([]string{ | ||
|  | 		accessKey, | ||
|  | 		date, | ||
|  | 		region, | ||
|  | 		service, | ||
|  | 		requestVersion}, "/") | ||
|  | } | ||
|  | 
 | ||
|  | // generate CredentialHeader from its fields.
 | ||
|  | func generateCredentials(t *testing.T, accessKey string, date string, region, service, requestVersion string) credentialHeader { | ||
|  | 	cred := credentialHeader{ | ||
|  | 		accessKey: accessKey, | ||
|  | 	} | ||
|  | 	parsedDate, err := time.Parse(yyyymmdd, date) | ||
|  | 	if err != nil { | ||
|  | 		t.Fatalf("Failed to parse date") | ||
|  | 	} | ||
|  | 	cred.scope.date = parsedDate | ||
|  | 	cred.scope.region = region | ||
|  | 	cred.scope.service = service | ||
|  | 	cred.scope.request = requestVersion | ||
|  | 
 | ||
|  | 	return cred | ||
|  | } | ||
|  | 
 | ||
|  | // validates the credential fields against the expected credential.
 | ||
|  | func validateCredentialfields(t *testing.T, testNum int, expectedCredentials credentialHeader, actualCredential credentialHeader) { | ||
|  | 
 | ||
|  | 	if expectedCredentials.accessKey != actualCredential.accessKey { | ||
|  | 		t.Errorf("Test %d: AccessKey mismatch: Expected \"%s\", got \"%s\"", testNum, expectedCredentials.accessKey, actualCredential.accessKey) | ||
|  | 	} | ||
|  | 	if expectedCredentials.scope.date != actualCredential.scope.date { | ||
|  | 		t.Errorf("Test %d: Date mismatch:Expected \"%s\", got \"%s\"", testNum, expectedCredentials.scope.date, actualCredential.scope.date) | ||
|  | 	} | ||
|  | 	if expectedCredentials.scope.region != actualCredential.scope.region { | ||
|  | 		t.Errorf("Test %d: region mismatch:Expected \"%s\", got \"%s\"", testNum, expectedCredentials.scope.region, actualCredential.scope.region) | ||
|  | 	} | ||
|  | 	if expectedCredentials.scope.service != actualCredential.scope.service { | ||
|  | 		t.Errorf("Test %d: service mismatch:Expected \"%s\", got \"%s\"", testNum, expectedCredentials.scope.service, actualCredential.scope.service) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if expectedCredentials.scope.request != actualCredential.scope.request { | ||
|  | 		t.Errorf("Test %d: scope request mismatch:Expected \"%s\", got \"%s\"", testNum, expectedCredentials.scope.request, actualCredential.scope.request) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // TestParseCredentialHeader - validates the format validator and extractor for the Credential header in an aws v4 request.
 | ||
|  | // A valid format of creadential should be of the following format.
 | ||
|  | // Credential = accessKey + "/"+ scope
 | ||
|  | // where scope = string.Join([]string{  currTime.Format(yyyymmdd),
 | ||
|  | // 			"us-east-1",
 | ||
|  | //               	"s3",
 | ||
|  | //		        "aws4_request",
 | ||
|  | //                       },"/")
 | ||
|  | func TestParseCredentialHeader(t *testing.T) { | ||
|  | 
 | ||
|  | 	sampleTimeStr := time.Now().UTC().Format(yyyymmdd) | ||
|  | 
 | ||
|  | 	testCases := []struct { | ||
|  | 		inputCredentialStr  string | ||
|  | 		expectedCredentials credentialHeader | ||
|  | 		expectedErrCode     APIErrorCode | ||
|  | 	}{ | ||
|  | 		// Test Case - 1.
 | ||
|  | 		// Test case with no '=' in te inputCredentialStr.
 | ||
|  | 		{ | ||
|  | 			inputCredentialStr:  "Credential", | ||
|  | 			expectedCredentials: credentialHeader{}, | ||
|  | 			expectedErrCode:     ErrMissingFields, | ||
|  | 		}, | ||
|  | 		// Test Case - 2.
 | ||
|  | 		// Test case with no "Credential" string in te inputCredentialStr.
 | ||
|  | 		{ | ||
|  | 			inputCredentialStr:  "Cred=", | ||
|  | 			expectedCredentials: credentialHeader{}, | ||
|  | 			expectedErrCode:     ErrMissingCredTag, | ||
|  | 		}, | ||
|  | 		// Test Case - 3.
 | ||
|  | 		// Test case with malformed credentials.
 | ||
|  | 		{ | ||
|  | 			inputCredentialStr:  "Credential=abc", | ||
|  | 			expectedCredentials: credentialHeader{}, | ||
|  | 			expectedErrCode:     ErrCredMalformed, | ||
|  | 		}, | ||
|  | 		// Test Case - 4.
 | ||
|  | 		// Test case with malformed AccessKey.
 | ||
|  | 		{ | ||
|  | 			inputCredentialStr: generateCredentialStr( | ||
|  | 				"^#@..!23", | ||
|  | 				time.Now().UTC().Format(yyyymmdd), | ||
|  | 				"ABCD", | ||
|  | 				"ABCD", | ||
|  | 				"ABCD"), | ||
|  | 			expectedCredentials: credentialHeader{}, | ||
|  | 			expectedErrCode:     ErrInvalidAccessKeyID, | ||
|  | 		}, | ||
|  | 		// Test Case - 5.
 | ||
|  | 		// Test case with invalid date format date.
 | ||
|  | 		// a valid date format for credentials is "yyyymmdd".
 | ||
|  | 		{ | ||
|  | 			inputCredentialStr: generateCredentialStr( | ||
|  | 				"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 				time.Now().UTC().String(), | ||
|  | 				"ABCD", | ||
|  | 				"ABCD", | ||
|  | 				"ABCD"), | ||
|  | 			expectedCredentials: credentialHeader{}, | ||
|  | 			expectedErrCode:     ErrMalformedCredentialDate, | ||
|  | 		}, | ||
|  | 		// Test Case - 6.
 | ||
|  | 		// Test case with invalid region.
 | ||
|  | 		// region should a non empty string.
 | ||
|  | 		{ | ||
|  | 			inputCredentialStr: generateCredentialStr( | ||
|  | 				"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 				time.Now().UTC().Format(yyyymmdd), | ||
|  | 				"", | ||
|  | 				"ABCD", | ||
|  | 				"ABCD"), | ||
|  | 			expectedCredentials: credentialHeader{}, | ||
|  | 			expectedErrCode:     ErrMalformedCredentialRegion, | ||
|  | 		}, | ||
|  | 		// Test Case - 7.
 | ||
|  | 		// Test case with invalid service.
 | ||
|  | 		// "s3" is the valid service string.
 | ||
|  | 		{ | ||
|  | 			inputCredentialStr: generateCredentialStr( | ||
|  | 				"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 				time.Now().UTC().Format(yyyymmdd), | ||
|  | 				"us-west-1", | ||
|  | 				"ABCD", | ||
|  | 				"ABCD"), | ||
|  | 			expectedCredentials: credentialHeader{}, | ||
|  | 			expectedErrCode:     ErrInvalidService, | ||
|  | 		}, | ||
|  | 		// Test Case - 8.
 | ||
|  | 		// Test case with invalid request version.
 | ||
|  | 		// "aws4_request" is the valid request version.
 | ||
|  | 		{ | ||
|  | 			inputCredentialStr: generateCredentialStr( | ||
|  | 				"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 				time.Now().UTC().Format(yyyymmdd), | ||
|  | 				"us-west-1", | ||
|  | 				"s3", | ||
|  | 				"ABCD"), | ||
|  | 			expectedCredentials: credentialHeader{}, | ||
|  | 			expectedErrCode:     ErrInvalidRequestVersion, | ||
|  | 		}, | ||
|  | 		// Test Case - 9.
 | ||
|  | 		// Test case with right inputs. Expected to return a valid CredentialHeader.
 | ||
|  | 		// "aws4_request" is the valid request version.
 | ||
|  | 		{ | ||
|  | 			inputCredentialStr: generateCredentialStr( | ||
|  | 				"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 				sampleTimeStr, | ||
|  | 				"us-west-1", | ||
|  | 				"s3", | ||
|  | 				"aws4_request"), | ||
|  | 			expectedCredentials: generateCredentials( | ||
|  | 				t, | ||
|  | 				"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 				sampleTimeStr, | ||
|  | 				"us-west-1", | ||
|  | 				"s3", | ||
|  | 				"aws4_request"), | ||
|  | 			expectedErrCode: ErrNone, | ||
|  | 		}, | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for i, testCase := range testCases { | ||
|  | 		actualCredential, actualErrCode := parseCredentialHeader(testCase.inputCredentialStr) | ||
|  | 		// validating the credential fields.
 | ||
|  | 		if testCase.expectedErrCode != actualErrCode { | ||
|  | 			t.Fatalf("Test %d: Expected the APIErrCode to be %d, got %d", i+1, testCase.expectedErrCode, actualErrCode) | ||
|  | 		} | ||
|  | 		if actualErrCode == ErrNone { | ||
|  | 			validateCredentialfields(t, i+1, testCase.expectedCredentials, actualCredential) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // TestParseSignature - validates the logic for extracting the signature string.
 | ||
|  | func TestParseSignature(t *testing.T) { | ||
|  | 	testCases := []struct { | ||
|  | 		inputSignElement string | ||
|  | 		expectedSignStr  string | ||
|  | 		expectedErrCode  APIErrorCode | ||
|  | 	}{ | ||
|  | 		// Test case - 1.
 | ||
|  | 		// SignElemenet doesn't have 2 parts on an attempt to split at '='.
 | ||
|  | 		// ErrMissingFields expected.
 | ||
|  | 		{ | ||
|  | 			inputSignElement: "Signature", | ||
|  | 			expectedSignStr:  "", | ||
|  | 			expectedErrCode:  ErrMissingFields, | ||
|  | 		}, | ||
|  | 		// Test case - 2.
 | ||
|  | 		// SignElemenet with missing "SignatureTag",ErrMissingSignTag expected.
 | ||
|  | 		{ | ||
|  | 			inputSignElement: "Sign=", | ||
|  | 			expectedSignStr:  "", | ||
|  | 			expectedErrCode:  ErrMissingSignTag, | ||
|  | 		}, | ||
|  | 		// Test case - 3.
 | ||
|  | 		// Test case with valid inputs.
 | ||
|  | 		{ | ||
|  | 			inputSignElement: "Signature=abcd", | ||
|  | 			expectedSignStr:  "abcd", | ||
|  | 			expectedErrCode:  ErrNone, | ||
|  | 		}, | ||
|  | 	} | ||
|  | 	for i, testCase := range testCases { | ||
|  | 		actualSignStr, actualErrCode := parseSignature(testCase.inputSignElement) | ||
|  | 		if testCase.expectedErrCode != actualErrCode { | ||
|  | 			t.Fatalf("Test %d: Expected the APIErrCode to be %d, got %d", i+1, testCase.expectedErrCode, actualErrCode) | ||
|  | 		} | ||
|  | 		if actualErrCode == ErrNone { | ||
|  | 			if testCase.expectedSignStr != actualSignStr { | ||
|  | 				t.Errorf("Test %d: Expected the result to be \"%s\", but got \"%s\". ", i+1, testCase.expectedSignStr, actualSignStr) | ||
|  | 
 | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // TestParseSignedHeaders - validates the logic for extracting the signature string.
 | ||
|  | func TestParseSignedHeaders(t *testing.T) { | ||
|  | 	testCases := []struct { | ||
|  | 		inputSignElement      string | ||
|  | 		expectedSignedHeaders []string | ||
|  | 		expectedErrCode       APIErrorCode | ||
|  | 	}{ | ||
|  | 		// Test case - 1.
 | ||
|  | 		// SignElemenet doesn't have 2 parts on an attempt to split at '='.
 | ||
|  | 		// ErrMissingFields expected.
 | ||
|  | 		{ | ||
|  | 			inputSignElement:      "SignedHeaders", | ||
|  | 			expectedSignedHeaders: nil, | ||
|  | 			expectedErrCode:       ErrMissingFields, | ||
|  | 		}, | ||
|  | 		// Test case - 2.
 | ||
|  | 		// SignElemenet with missing "SigHeaderTag",ErrMissingSignHeadersTag expected.
 | ||
|  | 		{ | ||
|  | 			inputSignElement:      "Sign=", | ||
|  | 			expectedSignedHeaders: nil, | ||
|  | 			expectedErrCode:       ErrMissingSignHeadersTag, | ||
|  | 		}, | ||
|  | 		// Test case - 3.
 | ||
|  | 		// Test case with valid inputs.
 | ||
|  | 		{ | ||
|  | 			inputSignElement:      "SignedHeaders=host;x-amz-content-sha256;x-amz-date", | ||
|  | 			expectedSignedHeaders: []string{"host", "x-amz-content-sha256", "x-amz-date"}, | ||
|  | 			expectedErrCode:       ErrNone, | ||
|  | 		}, | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for i, testCase := range testCases { | ||
|  | 		actualSignedHeaders, actualErrCode := parseSignedHeaders(testCase.inputSignElement) | ||
|  | 
 | ||
|  | 		if testCase.expectedErrCode != actualErrCode { | ||
|  | 			t.Errorf("Test %d: Expected the APIErrCode to be %d, got %d", i+1, testCase.expectedErrCode, actualErrCode) | ||
|  | 		} | ||
|  | 		if actualErrCode == ErrNone { | ||
|  | 			if strings.Join(testCase.expectedSignedHeaders, ",") != strings.Join(actualSignedHeaders, ",") { | ||
|  | 				t.Errorf("Test %d: Expected the result to be \"%v\", but got \"%v\". ", i+1, testCase.expectedSignedHeaders, actualSignedHeaders) | ||
|  | 
 | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // TestParseSignV4 - Tests Parsing of v4 signature form the authorization string.
 | ||
|  | func TestParseSignV4(t *testing.T) { | ||
|  | 	sampleTimeStr := time.Now().UTC().Format(yyyymmdd) | ||
|  | 	testCases := []struct { | ||
|  | 		inputV4AuthStr    string | ||
|  | 		expectedAuthField signValues | ||
|  | 		expectedErrCode   APIErrorCode | ||
|  | 	}{ | ||
|  | 		// Test case - 1.
 | ||
|  | 		// Test case with empty auth string.
 | ||
|  | 		{ | ||
|  | 			inputV4AuthStr:    "", | ||
|  | 			expectedAuthField: signValues{}, | ||
|  | 			expectedErrCode:   ErrAuthHeaderEmpty, | ||
|  | 		}, | ||
|  | 		// Test case - 2.
 | ||
|  | 		// Test case with no sign v4 Algorithm prefix.
 | ||
|  | 		// A valid authorization string should begin(prefix)
 | ||
|  | 		{ | ||
|  | 			inputV4AuthStr:    "no-singv4AlgorithmPrefix", | ||
|  | 			expectedAuthField: signValues{}, | ||
|  | 			expectedErrCode:   ErrSignatureVersionNotSupported, | ||
|  | 		}, | ||
|  | 		// Test case - 3.
 | ||
|  | 		// Test case with missing fields.
 | ||
|  | 		// A valid authorization string should have 3 fields.
 | ||
|  | 		{ | ||
|  | 			inputV4AuthStr:    signV4Algorithm, | ||
|  | 			expectedAuthField: signValues{}, | ||
|  | 			expectedErrCode:   ErrMissingFields, | ||
|  | 		}, | ||
|  | 		// Test case - 4.
 | ||
|  | 		// Test case with invalid credential field.
 | ||
|  | 		{ | ||
|  | 			inputV4AuthStr:    signV4Algorithm + " Cred=,a,b", | ||
|  | 			expectedAuthField: signValues{}, | ||
|  | 			expectedErrCode:   ErrMissingCredTag, | ||
|  | 		}, | ||
|  | 		// Test case - 5.
 | ||
|  | 		// Auth field with missing "SigHeaderTag",ErrMissingSignHeadersTag expected.
 | ||
|  | 		// A vaild credential is generated.
 | ||
|  | 		// Test case with invalid credential field.
 | ||
|  | 		{ | ||
|  | 			inputV4AuthStr: signV4Algorithm + | ||
|  | 				strings.Join([]string{ | ||
|  | 					// generating a valid credential field.
 | ||
|  | 					generateCredentialStr( | ||
|  | 						"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 						sampleTimeStr, | ||
|  | 						"us-west-1", | ||
|  | 						"s3", | ||
|  | 						"aws4_request"), | ||
|  | 					// Incorrect SignedHeader field.
 | ||
|  | 					"SignIncorrectHeader=", | ||
|  | 					"b", | ||
|  | 				}, ","), | ||
|  | 
 | ||
|  | 			expectedAuthField: signValues{}, | ||
|  | 			expectedErrCode:   ErrMissingSignHeadersTag, | ||
|  | 		}, | ||
|  | 		// Test case - 6.
 | ||
|  | 		// Auth string with missing "SignatureTag",ErrMissingSignTag expected.
 | ||
|  | 		// A vaild credential is generated.
 | ||
|  | 		// Test case with invalid credential field.
 | ||
|  | 		{ | ||
|  | 			inputV4AuthStr: signV4Algorithm + | ||
|  | 				strings.Join([]string{ | ||
|  | 					// generating a valid credential.
 | ||
|  | 					generateCredentialStr( | ||
|  | 						"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 						sampleTimeStr, | ||
|  | 						"us-west-1", | ||
|  | 						"s3", | ||
|  | 						"aws4_request"), | ||
|  | 					// valid SignedHeader.
 | ||
|  | 					"SignedHeaders=host;x-amz-content-sha256;x-amz-date", | ||
|  | 					// invalid Signature field.
 | ||
|  | 					// a valid signature is of form "Signature="
 | ||
|  | 					"Sign=", | ||
|  | 				}, ","), | ||
|  | 
 | ||
|  | 			expectedAuthField: signValues{}, | ||
|  | 			expectedErrCode:   ErrMissingSignTag, | ||
|  | 		}, | ||
|  | 		// Test case - 7.
 | ||
|  | 		{ | ||
|  | 			inputV4AuthStr: signV4Algorithm + | ||
|  | 				strings.Join([]string{ | ||
|  | 					// generating a valid credential.
 | ||
|  | 					generateCredentialStr( | ||
|  | 						"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 						sampleTimeStr, | ||
|  | 						"us-west-1", | ||
|  | 						"s3", | ||
|  | 						"aws4_request"), | ||
|  | 					// valid SignedHeader.
 | ||
|  | 					"SignedHeaders=host;x-amz-content-sha256;x-amz-date", | ||
|  | 					// valid Signature field.
 | ||
|  | 					// a valid signature is of form "Signature="
 | ||
|  | 					"Signature=abcd", | ||
|  | 				}, ","), | ||
|  | 
 | ||
|  | 			expectedAuthField: signValues{ | ||
|  | 				Credential: generateCredentials( | ||
|  | 					t, | ||
|  | 					"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 					sampleTimeStr, | ||
|  | 					"us-west-1", | ||
|  | 					"s3", | ||
|  | 					"aws4_request"), | ||
|  | 				SignedHeaders: []string{"host", "x-amz-content-sha256", "x-amz-date"}, | ||
|  | 				Signature:     "abcd", | ||
|  | 			}, | ||
|  | 			expectedErrCode: ErrNone, | ||
|  | 		}, | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for i, testCase := range testCases { | ||
|  | 		parsedAuthField, actualErrCode := parseSignV4(testCase.inputV4AuthStr) | ||
|  | 
 | ||
|  | 		if testCase.expectedErrCode != actualErrCode { | ||
|  | 			t.Fatalf("Test %d: Expected the APIErrCode to be %d, got %d", i+1, testCase.expectedErrCode, actualErrCode) | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if actualErrCode == ErrNone { | ||
|  | 			// validating the extracted/parsed credential fields.
 | ||
|  | 			validateCredentialfields(t, i+1, testCase.expectedAuthField.Credential, parsedAuthField.Credential) | ||
|  | 
 | ||
|  | 			// validating the extraction/parsing of signature field.
 | ||
|  | 			if testCase.expectedAuthField.Signature != parsedAuthField.Signature { | ||
|  | 				t.Errorf("Test %d: Parsed Signature field mismatch: Expected \"%s\", got \"%s\"", i+1, testCase.expectedAuthField.Signature, parsedAuthField.Signature) | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// validating the extracted signed headers.
 | ||
|  | 			if strings.Join(testCase.expectedAuthField.SignedHeaders, ",") != strings.Join(parsedAuthField.SignedHeaders, ",") { | ||
|  | 				t.Errorf("Test %d: Expected the result to be \"%v\", but got \"%v\". ", i+1, testCase.expectedAuthField, parsedAuthField.SignedHeaders) | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | // TestDoesV4PresignParamsExist - tests validate the logic to
 | ||
|  | func TestDoesV4PresignParamsExist(t *testing.T) { | ||
|  | 	testCases := []struct { | ||
|  | 		inputQueryKeyVals []string | ||
|  | 		expectedErrCode   APIErrorCode | ||
|  | 	}{ | ||
|  | 		// Test case - 1.
 | ||
|  | 		// contains all query param keys which are necessary for v4 presign request.
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 				"X-Amz-Algorithm", "", | ||
|  | 				"X-Amz-Credential", "", | ||
|  | 				"X-Amz-Signature", "", | ||
|  | 				"X-Amz-Date", "", | ||
|  | 				"X-Amz-SignedHeaders", "", | ||
|  | 				"X-Amz-Expires", "", | ||
|  | 			}, | ||
|  | 			expectedErrCode: ErrNone, | ||
|  | 		}, | ||
|  | 		// Test case - 2.
 | ||
|  | 		// missing 	"X-Amz-Algorithm" in tdhe query param.
 | ||
|  | 		// contains all query param keys which are necessary for v4 presign request.
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 				"X-Amz-Credential", "", | ||
|  | 				"X-Amz-Signature", "", | ||
|  | 				"X-Amz-Date", "", | ||
|  | 				"X-Amz-SignedHeaders", "", | ||
|  | 				"X-Amz-Expires", "", | ||
|  | 			}, | ||
|  | 			expectedErrCode: ErrInvalidQueryParams, | ||
|  | 		}, | ||
|  | 		// Test case - 3.
 | ||
|  | 		// missing "X-Amz-Credential" in the query param.
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 				"X-Amz-Algorithm", "", | ||
|  | 				"X-Amz-Signature", "", | ||
|  | 				"X-Amz-Date", "", | ||
|  | 				"X-Amz-SignedHeaders", "", | ||
|  | 				"X-Amz-Expires", "", | ||
|  | 			}, | ||
|  | 			expectedErrCode: ErrInvalidQueryParams, | ||
|  | 		}, | ||
|  | 		// Test case - 4.
 | ||
|  | 		// missing "X-Amz-Signature" in the query param.
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 				"X-Amz-Algorithm", "", | ||
|  | 				"X-Amz-Credential", "", | ||
|  | 				"X-Amz-Date", "", | ||
|  | 				"X-Amz-SignedHeaders", "", | ||
|  | 				"X-Amz-Expires", "", | ||
|  | 			}, | ||
|  | 			expectedErrCode: ErrInvalidQueryParams, | ||
|  | 		}, | ||
|  | 		// Test case - 5.
 | ||
|  | 		// missing "X-Amz-Date" in the query param.
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 				"X-Amz-Algorithm", "", | ||
|  | 				"X-Amz-Credential", "", | ||
|  | 				"X-Amz-Signature", "", | ||
|  | 				"X-Amz-SignedHeaders", "", | ||
|  | 				"X-Amz-Expires", "", | ||
|  | 			}, | ||
|  | 			expectedErrCode: ErrInvalidQueryParams, | ||
|  | 		}, | ||
|  | 		// Test case - 6.
 | ||
|  | 		// missing "X-Amz-SignedHeaders" in the query param.
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 				"X-Amz-Algorithm", "", | ||
|  | 				"X-Amz-Credential", "", | ||
|  | 				"X-Amz-Signature", "", | ||
|  | 				"X-Amz-Date", "", | ||
|  | 				"X-Amz-Expires", "", | ||
|  | 			}, | ||
|  | 			expectedErrCode: ErrInvalidQueryParams, | ||
|  | 		}, | ||
|  | 		// Test case - 7.
 | ||
|  | 		// missing "X-Amz-Expires" in the query param.
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 				"X-Amz-Algorithm", "", | ||
|  | 				"X-Amz-Credential", "", | ||
|  | 				"X-Amz-Signature", "", | ||
|  | 				"X-Amz-Date", "", | ||
|  | 				"X-Amz-SignedHeaders", "", | ||
|  | 			}, | ||
|  | 			expectedErrCode: ErrInvalidQueryParams, | ||
|  | 		}, | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for i, testCase := range testCases { | ||
|  | 		inputQuery := url.Values{} | ||
|  | 		// iterating through input query key value and setting the inputQuery of type url.Values.
 | ||
|  | 		for j := 0; j < len(testCase.inputQueryKeyVals)-1; j += 2 { | ||
|  | 
 | ||
|  | 			inputQuery.Set(testCase.inputQueryKeyVals[j], testCase.inputQueryKeyVals[j+1]) | ||
|  | 		} | ||
|  | 
 | ||
|  | 		actualErrCode := doesV4PresignParamsExist(inputQuery) | ||
|  | 
 | ||
|  | 		if testCase.expectedErrCode != actualErrCode { | ||
|  | 			t.Fatalf("Test %d: Expected the APIErrCode to be %d, got %d", i+1, testCase.expectedErrCode, actualErrCode) | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | // TestParsePreSignV4 - Validates the parsing logic of Presignied v4 request from its url query values.
 | ||
|  | func TestParsePreSignV4(t *testing.T) { | ||
|  | 	// converts the duration in seconds into string format.
 | ||
|  | 	getDurationStr := func(expires int) string { | ||
|  | 		return strconv.FormatInt(int64(expires), 10) | ||
|  | 	} | ||
|  | 	// used in expected preSignValues, preSignValues.Date is of type time.Time .
 | ||
|  | 	queryTime := time.Now() | ||
|  | 
 | ||
|  | 	sampleTimeStr := time.Now().UTC().Format(yyyymmdd) | ||
|  | 
 | ||
|  | 	testCases := []struct { | ||
|  | 		inputQueryKeyVals     []string | ||
|  | 		expectedPreSignValues preSignValues | ||
|  | 		expectedErrCode       APIErrorCode | ||
|  | 	}{ | ||
|  | 		// Test case - 1.
 | ||
|  | 		// A Valid v4 presign URL requires the following params to be in the query.
 | ||
|  | 		// "X-Amz-Algorithm", "X-Amz-Credential", "X-Amz-Signature", " X-Amz-Date", "X-Amz-SignedHeaders", "X-Amz-Expires".
 | ||
|  | 		// If these params are missing its expected to get ErrInvalidQueryParams .
 | ||
|  | 		// In the following test case 2 out of 6 query params are missing.
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 				"X-Amz-Algorithm", "", | ||
|  | 				"X-Amz-Credential", "", | ||
|  | 				"X-Amz-Signature", "", | ||
|  | 				"X-Amz-Expires", "", | ||
|  | 			}, | ||
|  | 			expectedPreSignValues: preSignValues{}, | ||
|  | 			expectedErrCode:       ErrInvalidQueryParams, | ||
|  | 		}, | ||
|  | 		// Test case - 2.
 | ||
|  | 		// Test case with invalid  "X-Amz-Algorithm" query value.
 | ||
|  | 		// The other query params should exist, other wise ErrInvalidQueryParams will be returned because of missing fields.
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 
 | ||
|  | 				"X-Amz-Algorithm", "InvalidValue", | ||
|  | 				"X-Amz-Credential", "", | ||
|  | 				"X-Amz-Signature", "", | ||
|  | 				"X-Amz-Date", "", | ||
|  | 				"X-Amz-SignedHeaders", "", | ||
|  | 				"X-Amz-Expires", "", | ||
|  | 			}, | ||
|  | 			expectedPreSignValues: preSignValues{}, | ||
|  | 			expectedErrCode:       ErrInvalidQuerySignatureAlgo, | ||
|  | 		}, | ||
|  | 		// Test case - 3.
 | ||
|  | 		// Test case with valid "X-Amz-Algorithm" query value, but invalid  "X-Amz-Credential" header.
 | ||
|  | 		// Malformed crenential.
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 				// valid  "X-Amz-Algorithm" header.
 | ||
|  | 				"X-Amz-Algorithm", signV4Algorithm, | ||
|  | 				// valid  "X-Amz-Credential" header.
 | ||
|  | 				"X-Amz-Credential", "invalid-credential", | ||
|  | 				"X-Amz-Signature", "", | ||
|  | 				"X-Amz-Date", "", | ||
|  | 				"X-Amz-SignedHeaders", "", | ||
|  | 				"X-Amz-Expires", "", | ||
|  | 			}, | ||
|  | 			expectedPreSignValues: preSignValues{}, | ||
|  | 			expectedErrCode:       ErrCredMalformed, | ||
|  | 		}, | ||
|  | 
 | ||
|  | 		// Test case - 4.
 | ||
|  | 		// Test case with valid "X-Amz-Algorithm" query value.
 | ||
|  | 		// Malformed date.
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 				// valid  "X-Amz-Algorithm" header.
 | ||
|  | 				"X-Amz-Algorithm", signV4Algorithm, | ||
|  | 				// valid  "X-Amz-Credential" header.
 | ||
|  | 				"X-Amz-Credential", joinWithSlash( | ||
|  | 					"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 					sampleTimeStr, | ||
|  | 					"us-west-1", | ||
|  | 					"s3", | ||
|  | 					"aws4_request"), | ||
|  | 				// invalid "X-Amz-Date" query.
 | ||
|  | 				"X-Amz-Date", "invalid-time", | ||
|  | 				"X-Amz-SignedHeaders", "", | ||
|  | 				"X-Amz-Expires", "", | ||
|  | 				"X-Amz-Signature", "", | ||
|  | 			}, | ||
|  | 			expectedPreSignValues: preSignValues{}, | ||
|  | 			expectedErrCode:       ErrMalformedPresignedDate, | ||
|  | 		}, | ||
|  | 		// Test case - 5.
 | ||
|  | 		// Test case with valid "X-Amz-Algorithm", "X-Amz-Credential", "X-Amz-Date" query value.
 | ||
|  | 		// Malformed Expiry, a valid expiry should be of format "<int>s".
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 				// valid  "X-Amz-Algorithm" header.
 | ||
|  | 				"X-Amz-Algorithm", signV4Algorithm, | ||
|  | 				// valid  "X-Amz-Credential" header.
 | ||
|  | 				"X-Amz-Credential", joinWithSlash( | ||
|  | 					"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 					sampleTimeStr, | ||
|  | 					"us-west-1", | ||
|  | 					"s3", | ||
|  | 					"aws4_request"), | ||
|  | 				// valid "X-Amz-Date" query.
 | ||
|  | 				"X-Amz-Date", time.Now().UTC().Format(iso8601Format), | ||
|  | 				"X-Amz-Expires", "MalformedExpiry", | ||
|  | 				"X-Amz-SignedHeaders", "", | ||
|  | 				"X-Amz-Signature", "", | ||
|  | 			}, | ||
|  | 			expectedPreSignValues: preSignValues{}, | ||
|  | 			expectedErrCode:       ErrMalformedExpires, | ||
|  | 		}, | ||
|  | 		// Test case - 6.
 | ||
|  | 		// Test case with valid "X-Amz-Algorithm", "X-Amz-Credential", "X-Amz-Date" query value.
 | ||
|  | 		// Malformed Expiry, a valid expiry should be of format "<int>s".
 | ||
|  | 		{ | ||
|  | 			inputQueryKeyVals: []string{ | ||
|  | 				// valid  "X-Amz-Algorithm" header.
 | ||
|  | 				"X-Amz-Algorithm", signV4Algorithm, | ||
|  | 				// valid  "X-Amz-Credential" header.
 | ||
|  | 				"X-Amz-Credential", joinWithSlash( | ||
|  | 					"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 					sampleTimeStr, | ||
|  | 					"us-west-1", | ||
|  | 					"s3", | ||
|  | 					"aws4_request"), | ||
|  | 				// valid "X-Amz-Date" query.
 | ||
|  | 				"X-Amz-Date", queryTime.UTC().Format(iso8601Format), | ||
|  | 				"X-Amz-Expires", getDurationStr(100), | ||
|  | 				"X-Amz-Signature", "abcd", | ||
|  | 				"X-Amz-SignedHeaders", "host;x-amz-content-sha256;x-amz-date", | ||
|  | 			}, | ||
|  | 			expectedPreSignValues: preSignValues{ | ||
|  | 				signValues{ | ||
|  | 					// Credentials.
 | ||
|  | 					generateCredentials( | ||
|  | 						t, | ||
|  | 						"Z7IXGOO6BZ0REAN1Q26I", | ||
|  | 						sampleTimeStr, | ||
|  | 						"us-west-1", | ||
|  | 						"s3", | ||
|  | 						"aws4_request", | ||
|  | 					), | ||
|  | 					// SignedHeaders.
 | ||
|  | 					[]string{"host", "x-amz-content-sha256", "x-amz-date"}, | ||
|  | 					// Signature.
 | ||
|  | 					"abcd", | ||
|  | 				}, | ||
|  | 				// Date
 | ||
|  | 				queryTime, | ||
|  | 				// Expires.
 | ||
|  | 				100 * time.Second, | ||
|  | 			}, | ||
|  | 			expectedErrCode: ErrNone, | ||
|  | 		}, | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for i, testCase := range testCases { | ||
|  | 		inputQuery := url.Values{} | ||
|  | 		// iterating through input query key value and setting the inputQuery of type url.Values.
 | ||
|  | 		for j := 0; j < len(testCase.inputQueryKeyVals)-1; j += 2 { | ||
|  | 			inputQuery.Set(testCase.inputQueryKeyVals[j], testCase.inputQueryKeyVals[j+1]) | ||
|  | 		} | ||
|  | 		// call the function under test.
 | ||
|  | 		parsedPreSign, actualErrCode := parsePreSignV4(inputQuery) | ||
|  | 
 | ||
|  | 		if testCase.expectedErrCode != actualErrCode { | ||
|  | 			t.Fatalf("Test %d: Expected the APIErrCode to be %d, got %d", i+1, testCase.expectedErrCode, actualErrCode) | ||
|  | 		} | ||
|  | 		if actualErrCode == ErrNone { | ||
|  | 			// validating credentials.
 | ||
|  | 			validateCredentialfields(t, i+1, testCase.expectedPreSignValues.Credential, parsedPreSign.Credential) | ||
|  | 			// validating signed headers.
 | ||
|  | 			if strings.Join(testCase.expectedPreSignValues.SignedHeaders, ",") != strings.Join(parsedPreSign.SignedHeaders, ",") { | ||
|  | 				t.Errorf("Test %d: Expected the result to be \"%v\", but got \"%v\". ", i+1, testCase.expectedPreSignValues.SignedHeaders, parsedPreSign.SignedHeaders) | ||
|  | 			} | ||
|  | 			// validating signature field.
 | ||
|  | 			if testCase.expectedPreSignValues.Signature != parsedPreSign.Signature { | ||
|  | 				t.Errorf("Test %d: Signature field mismatch: Expected \"%s\", got \"%s\"", i+1, testCase.expectedPreSignValues.Signature, parsedPreSign.Signature) | ||
|  | 			} | ||
|  | 			// validating expiry duration.
 | ||
|  | 			if testCase.expectedPreSignValues.Expires != parsedPreSign.Expires { | ||
|  | 				t.Errorf("Test %d: Expected expiry time to be %v, but got %v", i+1, testCase.expectedPreSignValues.Expires, parsedPreSign.Expires) | ||
|  | 			} | ||
|  | 			// validating presign date field.
 | ||
|  | 			if testCase.expectedPreSignValues.Date.UTC().Format(iso8601Format) != parsedPreSign.Date.UTC().Format(iso8601Format) { | ||
|  | 				t.Errorf("Test %d: Expected date to be %v, but got %v", i+1, testCase.expectedPreSignValues.Date.UTC().Format(iso8601Format), parsedPreSign.Date.UTC().Format(iso8601Format)) | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | } |