mirror of
				https://github.com/minio/minio.git
				synced 2025-11-04 10:11:09 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			886 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			886 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) 2015-2021 MinIO, Inc.
 | 
						|
//
 | 
						|
// This file is part of MinIO Object Storage stack
 | 
						|
//
 | 
						|
// This program is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU Affero General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// This program is distributed in the hope that it will be useful
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
// GNU Affero General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU Affero General Public License
 | 
						|
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
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,
 | 
						|
	}, SlashSeparator)
 | 
						|
}
 | 
						|
 | 
						|
// 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.Equal(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 + SlashSeparator+ scope
 | 
						|
// where scope = string.Join([]string{  currTime.Format(yyyymmdd),
 | 
						|
// 			globalMinioDefaultRegion,
 | 
						|
//               	"s3",
 | 
						|
//		        "aws4_request",
 | 
						|
//                       },SlashSeparator)
 | 
						|
func TestParseCredentialHeader(t *testing.T) {
 | 
						|
	sampleTimeStr := UTCNow().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 AccessKey of length 2.
 | 
						|
		{
 | 
						|
			inputCredentialStr: generateCredentialStr(
 | 
						|
				"^#",
 | 
						|
				UTCNow().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",
 | 
						|
				UTCNow().String(),
 | 
						|
				"ABCD",
 | 
						|
				"ABCD",
 | 
						|
				"ABCD"),
 | 
						|
			expectedCredentials: credentialHeader{},
 | 
						|
			expectedErrCode:     ErrMalformedCredentialDate,
 | 
						|
		},
 | 
						|
		// Test Case - 6.
 | 
						|
		// Test case with invalid service.
 | 
						|
		// "s3" is the valid service string.
 | 
						|
		{
 | 
						|
			inputCredentialStr: generateCredentialStr(
 | 
						|
				"Z7IXGOO6BZ0REAN1Q26I",
 | 
						|
				UTCNow().Format(yyyymmdd),
 | 
						|
				"us-west-1",
 | 
						|
				"ABCD",
 | 
						|
				"ABCD"),
 | 
						|
			expectedCredentials: credentialHeader{},
 | 
						|
			expectedErrCode:     ErrInvalidServiceS3,
 | 
						|
		},
 | 
						|
		// Test Case - 7.
 | 
						|
		// Test case with invalid region.
 | 
						|
		{
 | 
						|
			inputCredentialStr: generateCredentialStr(
 | 
						|
				"Z7IXGOO6BZ0REAN1Q26I",
 | 
						|
				UTCNow().Format(yyyymmdd),
 | 
						|
				"us-west-2",
 | 
						|
				"s3",
 | 
						|
				"aws4_request"),
 | 
						|
			expectedCredentials: credentialHeader{},
 | 
						|
			expectedErrCode:     ErrAuthorizationHeaderMalformed,
 | 
						|
		},
 | 
						|
		// Test Case - 8.
 | 
						|
		// Test case with invalid request version.
 | 
						|
		// "aws4_request" is the valid request version.
 | 
						|
		{
 | 
						|
			inputCredentialStr: generateCredentialStr(
 | 
						|
				"Z7IXGOO6BZ0REAN1Q26I",
 | 
						|
				UTCNow().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,
 | 
						|
		},
 | 
						|
		// Test Case - 10.
 | 
						|
		// Test case with right inputs -> AccessKey contains `/`. See minio/#6443
 | 
						|
		// "aws4_request" is the valid request version.
 | 
						|
		{
 | 
						|
			inputCredentialStr: generateCredentialStr(
 | 
						|
				"LOCALKEY/DEV/1",
 | 
						|
				sampleTimeStr,
 | 
						|
				"us-west-1",
 | 
						|
				"s3",
 | 
						|
				"aws4_request"),
 | 
						|
			expectedCredentials: generateCredentials(
 | 
						|
				t,
 | 
						|
				"LOCALKEY/DEV/1",
 | 
						|
				sampleTimeStr,
 | 
						|
				"us-west-1",
 | 
						|
				"s3",
 | 
						|
				"aws4_request"),
 | 
						|
			expectedErrCode: ErrNone,
 | 
						|
		},
 | 
						|
		// Test Case - 11.
 | 
						|
		// Test case with right inputs -> AccessKey contains `=`. See minio/#7376
 | 
						|
		// "aws4_request" is the valid request version.
 | 
						|
		{
 | 
						|
			inputCredentialStr: generateCredentialStr(
 | 
						|
				"LOCALKEY/DEV/1=",
 | 
						|
				sampleTimeStr,
 | 
						|
				"us-west-1",
 | 
						|
				"s3",
 | 
						|
				"aws4_request"),
 | 
						|
			expectedCredentials: generateCredentials(
 | 
						|
				t,
 | 
						|
				"LOCALKEY/DEV/1=",
 | 
						|
				sampleTimeStr,
 | 
						|
				"us-west-1",
 | 
						|
				"s3",
 | 
						|
				"aws4_request"),
 | 
						|
			expectedErrCode: ErrNone,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for i, testCase := range testCases {
 | 
						|
		actualCredential, actualErrCode := parseCredentialHeader(testCase.inputCredentialStr, "us-west-1", "s3")
 | 
						|
		// validating the credential fields.
 | 
						|
		if testCase.expectedErrCode != actualErrCode {
 | 
						|
			t.Fatalf("Test %d: Expected the APIErrCode to be %s, got %s", i+1, errorCodes[testCase.expectedErrCode].Code, errorCodes[actualErrCode].Code)
 | 
						|
		}
 | 
						|
		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.
 | 
						|
		// SignElement does have 2 parts but doesn't have valid signature value.
 | 
						|
		// ErrMissingFields expected.
 | 
						|
		{
 | 
						|
			inputSignElement: "Signature=",
 | 
						|
			expectedSignStr:  "",
 | 
						|
			expectedErrCode:  ErrMissingFields,
 | 
						|
		},
 | 
						|
		// Test case - 3.
 | 
						|
		// SignElemenet with missing "SignatureTag",ErrMissingSignTag expected.
 | 
						|
		{
 | 
						|
			inputSignElement: "Sign=",
 | 
						|
			expectedSignStr:  "",
 | 
						|
			expectedErrCode:  ErrMissingSignTag,
 | 
						|
		},
 | 
						|
		// Test case - 4.
 | 
						|
		// 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 := parseSignedHeader(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 := UTCNow().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,
 | 
						|
		},
 | 
						|
		// Test case - 8.
 | 
						|
		{
 | 
						|
			inputV4AuthStr: signV4Algorithm +
 | 
						|
				strings.Join([]string{
 | 
						|
					// generating a valid credential.
 | 
						|
					generateCredentialStr(
 | 
						|
						"access key",
 | 
						|
						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,
 | 
						|
					"access key",
 | 
						|
					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, "", "s3")
 | 
						|
 | 
						|
		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 !compareSignatureV4(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.Itoa(expires)
 | 
						|
	}
 | 
						|
	// used in expected preSignValues, preSignValues.Date is of type time.Time .
 | 
						|
	queryTime := UTCNow()
 | 
						|
 | 
						|
	sampleTimeStr := UTCNow().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", UTCNow().Format(iso8601Format),
 | 
						|
				"X-Amz-Expires", "MalformedExpiry",
 | 
						|
				"X-Amz-SignedHeaders", "",
 | 
						|
				"X-Amz-Signature", "",
 | 
						|
			},
 | 
						|
			expectedPreSignValues: preSignValues{},
 | 
						|
			expectedErrCode:       ErrMalformedExpires,
 | 
						|
		},
 | 
						|
		// Test case - 6.
 | 
						|
		// Test case with negative X-Amz-Expires header.
 | 
						|
		{
 | 
						|
			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(-1),
 | 
						|
				"X-Amz-Signature", "abcd",
 | 
						|
				"X-Amz-SignedHeaders", "host;x-amz-content-sha256;x-amz-date",
 | 
						|
			},
 | 
						|
			expectedPreSignValues: preSignValues{},
 | 
						|
			expectedErrCode:       ErrNegativeExpires,
 | 
						|
		},
 | 
						|
		// Test case - 7.
 | 
						|
		// Test case with empty X-Amz-SignedHeaders.
 | 
						|
		{
 | 
						|
			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", "",
 | 
						|
			},
 | 
						|
			expectedPreSignValues: preSignValues{},
 | 
						|
			expectedErrCode:       ErrMissingFields,
 | 
						|
		},
 | 
						|
		// Test case - 8.
 | 
						|
		// 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,
 | 
						|
		},
 | 
						|
 | 
						|
		// Test case - 9.
 | 
						|
		// Test case with value greater than 604800 in X-Amz-Expires header.
 | 
						|
		{
 | 
						|
			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),
 | 
						|
				// Invalid Expiry time greater than 7 days (604800 in seconds).
 | 
						|
				"X-Amz-Expires", getDurationStr(605000),
 | 
						|
				"X-Amz-Signature", "abcd",
 | 
						|
				"X-Amz-SignedHeaders", "host;x-amz-content-sha256;x-amz-date",
 | 
						|
			},
 | 
						|
			expectedPreSignValues: preSignValues{},
 | 
						|
			expectedErrCode:       ErrMaximumExpires,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	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, "", serviceS3)
 | 
						|
		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 !compareSignatureV4(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))
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
}
 |