diff --git a/auth-handler_test.go b/auth-handler_test.go index 5f0d8032c..58d3b1c04 100644 --- a/auth-handler_test.go +++ b/auth-handler_test.go @@ -23,6 +23,74 @@ import ( "testing" ) +// TestIsRequestUnsignedPayload - Test validates the Unsigned payload detection logic. +func TestIsRequestUnsignedPayload(t *testing.T) { + testCases := []struct { + inputAmzContentHeader string + expectedResult bool + }{ + // Test case - 1. + // Test case with "X-Amz-Content-Sha256" header set to empty value. + {"", false}, + // Test case - 2. + // Test case with "X-Amz-Content-Sha256" header set to "UNSIGNED-PAYLOAD" + // The payload is flagged as unsigned When "X-Amz-Content-Sha256" header is set to "UNSIGNED-PAYLOAD". + {"UNSIGNED-PAYLOAD", true}, + // Test case - 3. + // set to a random value. + {"abcd", false}, + } + + // creating an input HTTP request. + // Only the headers are relevant for this particular test. + inputReq, err := http.NewRequest("GET", "http://example.com", nil) + if err != nil { + t.Fatalf("Error initializing input HTTP request: %v", err) + } + + for i, testCase := range testCases { + inputReq.Header.Set("X-Amz-Content-Sha256", testCase.inputAmzContentHeader) + actualResult := isRequestUnsignedPayload(inputReq) + if testCase.expectedResult != actualResult { + t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult) + } + } +} + +// TestIsRequestPresignedSignatureV4 - Test validates the logic for presign signature verision v4 detection. +func TestIsRequestPresignedSignatureV4(t *testing.T) { + testCases := []struct { + inputQueryKey string + inputQueryValue string + expectedResult bool + }{ + // Test case - 1. + // Test case with query key ""X-Amz-Credential" set. + {"", "", false}, + // Test case - 2. + {"X-Amz-Credential", "", true}, + // Test case - 3. + {"X-Amz-Content-Sha256", "", false}, + } + + for i, testCase := range testCases { + // creating an input HTTP request. + // Only the query parameters are relevant for this particular test. + inputReq, err := http.NewRequest("GET", "http://example.com", nil) + if err != nil { + t.Fatalf("Error initializing input HTTP request: %v", err) + } + q := inputReq.URL.Query() + q.Add(testCase.inputQueryKey, testCase.inputQueryValue) + inputReq.URL.RawQuery = q.Encode() + + actualResult := isRequestPresignedSignatureV4(inputReq) + if testCase.expectedResult != actualResult { + t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult) + } + } +} + // Provides a fully populated http request instance, fails otherwise. func mustNewRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request { req, err := newTestRequest(method, urlStr, contentLength, body) diff --git a/signature-v4-utils_test.go b/signature-v4-utils_test.go new file mode 100644 index 000000000..9e1f74f37 --- /dev/null +++ b/signature-v4-utils_test.go @@ -0,0 +1,176 @@ +/* + * Minio Cloud Storage, (C) 2015, 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 main + +import ( + "net/http" + "testing" + "time" +) + +// TestSkipContentSha256Cksum - Test validate the logic which decides whether to skip checksum validation based on the request header. +func TestSkipContentSha256Cksum(t *testing.T) { + testCases := []struct { + inputHeaderKey string + inputHeaderValue string + + inputQueryKey string + inputQueryValue string + + expectedResult bool + }{ + // Test case - 1. + // Test case with "X-Amz-Content-Sha256" header set to empty value. + {"X-Amz-Content-Sha256", "", "", "", false}, + // Test case - 2. + // Test case with "X-Amz-Content-Sha256" header set to "UNSIGNED-PAYLOAD" + // When "X-Amz-Content-Sha256" header is set to "UNSIGNED-PAYLOAD", validation of content sha256 has to be skipped. + {"X-Amz-Content-Sha256", "UNSIGNED-PAYLOAD", "", "", true}, + // Test case - 3. + // Enabling PreSigned Signature v4. + {"", "", "X-Amz-Credential", "", true}, + // Test case - 4. + // "X-Amz-Content-Sha256" not set and PreSigned Signature v4 not enabled, sha256 checksum calculation is not skipped. + {"", "", "X-Amz-Credential", "", true}, + } + + for i, testCase := range testCases { + // creating an input HTTP request. + // Only the headers are relevant for this particular test. + inputReq, err := http.NewRequest("GET", "http://example.com", nil) + if err != nil { + t.Fatalf("Error initializing input HTTP request: %v", err) + } + if testCase.inputHeaderKey != "" { + inputReq.Header.Set(testCase.inputHeaderKey, testCase.inputHeaderValue) + } + if testCase.inputQueryKey != "" { + q := inputReq.URL.Query() + q.Add(testCase.inputQueryKey, testCase.inputQueryValue) + inputReq.URL.RawQuery = q.Encode() + } + + actualResult := skipContentSha256Cksum(inputReq) + if testCase.expectedResult != actualResult { + t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult) + } + } +} + +// TestIsValidRegion - Tests validate the comparison logic for asserting whether the region from http request is valid. +func TestIsValidRegion(t *testing.T) { + testCases := []struct { + inputReqRegion string + inputConfRegion string + + expectedResult bool + }{ + + {"", "", false}, + {"us-east-1", "", true}, + {"us-east-1", "US", true}, + {"us-west-1", "US", false}, + {"us-west-1", "us-west-1", true}, + } + + for i, testCase := range testCases { + + actualResult := isValidRegion(testCase.inputReqRegion, testCase.inputConfRegion) + if testCase.expectedResult != actualResult { + t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult) + } + } +} + +// Tests validate the URL path encoder. +func TestGetURLEncodedName(t *testing.T) { + testCases := []struct { + // Input. + inputStr string + // Expected result. + result string + }{ + // % should be encoded as %25 + {"thisisthe%url", "thisisthe%25url"}, + // UTF-8 encoding. + {"本語", "%E6%9C%AC%E8%AA%9E"}, + // UTF-8 encoding with ASCII. + {"本語.1", "%E6%9C%AC%E8%AA%9E.1"}, + // Unusual ASCII characters. + {">123", "%3E123"}, + // Fragment path characters. + {"myurl#link", "myurl%23link"}, + // Space should be set to %20 not '+'. + {"space in url", "space%20in%20url"}, + // '+' shouldn't be treated as space. + {"url+path", "url%2Bpath"}, + } + + // Tests generated values from url encoded name. + for i, testCase := range testCases { + result := getURLEncodedName(testCase.inputStr) + if testCase.result != result { + t.Errorf("Test %d: Expected queryEncode result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result) + } + } +} + +// TestExtractSignedHeaders - Tests validate extraction of signed headers using list of signed header keys. +func TestExtractSignedHeaders(t *testing.T) { + signedHeaders := []string{"host", "x-amz-content-sha256", "x-amz-date"} + + for i, key := range signedHeaders { + signedHeaders[i] = http.CanonicalHeaderKey(key) + } + + // If the `expect` key exists in the signed headers then golang server would have stripped out the value, expecting the `expect` header set to `100-continue` in the result. + signedHeaders = append(signedHeaders, "expect") + // expected header values. + expectedHost := "play.minio.io:9000" + expectedContentSha256 := "1234abcd" + expectedTime := time.Now().UTC().Format(iso8601Format) + + // Creating input http header. + inputHeader := make(http.Header) + inputHeader.Set(signedHeaders[0], expectedHost) + inputHeader.Set(signedHeaders[1], expectedContentSha256) + inputHeader.Set(signedHeaders[2], expectedTime) + // calling the function being tested. + extractedSignedHeaders := extractSignedHeaders(signedHeaders, inputHeader) + // "x-amz-content-sha256" header value from the extracted result. + extractedContentSha256 := extractedSignedHeaders.Get(signedHeaders[1]) + // "host" header value from the extracted result. + extractedHost := extractedSignedHeaders.Get(signedHeaders[0]) + // "x-amz-date" header from the extracted result. + extractedDate := extractedSignedHeaders.Get(signedHeaders[2]) + // extracted `expect` header. + extractedExpect := extractedSignedHeaders["expect"][0] + // assert the result with the expected value. + if expectedContentSha256 != extractedContentSha256 { + t.Errorf("x-amz-content-sha256 header mismatch: expected `%s`, got `%s`", expectedContentSha256, extractedContentSha256) + } + if expectedTime != extractedDate { + t.Errorf("x-amz-date header mismatch: expected `%s`, got `%s`", expectedTime, extractedDate) + } + if expectedHost != extractedHost { + t.Errorf("host header mismatch: expected `%s`, got `%s`", expectedHost, extractedHost) + } + // Since the list of signed headers value contained `expect`, the default value of `100-continue` will be added to extracted signed headers. + if extractedExpect != "100-continue" { + t.Errorf("expect header incorrect value: expected `%s`, got `%s`", "100-continue", extractedExpect) + } +} diff --git a/signature-v4_test.go b/signature-v4_test.go deleted file mode 100644 index 2964db0cc..000000000 --- a/signature-v4_test.go +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 main - -import "testing" - -// Tests validate the URL path encoder. -func TestUrlEncodePath(t *testing.T) { - testCases := []struct { - // Input. - inputStr string - // Expected result. - result string - }{ - // % should be encoded as %25 - {"thisisthe%url", "thisisthe%25url"}, - // UTF-8 encoding. - {"本語", "%E6%9C%AC%E8%AA%9E"}, - // UTF-8 encoding with ASCII. - {"本語.1", "%E6%9C%AC%E8%AA%9E.1"}, - // Unusual ASCII characters. - {">123", "%3E123"}, - // Fragment path characters. - {"myurl#link", "myurl%23link"}, - // Space should be set to %20 not '+'. - {"space in url", "space%20in%20url"}, - // '+' shouldn't be treated as space. - {"url+path", "url%2Bpath"}, - } - - // Tests generated values from url encoded name. - for i, testCase := range testCases { - result := getURLEncodedName(testCase.inputStr) - if testCase.result != result { - t.Errorf("Test %d: Expected queryEncode result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result) - } - } -}