1/* 2 * Copyright 3 * 2015, 2016, 2017 Minio, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package minio 19 20import ( 21 "bytes" 22 "encoding/xml" 23 "io/ioutil" 24 "net/http" 25 "net/url" 26 "path" 27 "reflect" 28 "testing" 29 30 "github.com/minio/minio-go/pkg/credentials" 31 "github.com/minio/minio-go/pkg/s3signer" 32) 33 34// Test validates `newBucketLocationCache`. 35func TestNewBucketLocationCache(t *testing.T) { 36 expectedBucketLocationcache := &bucketLocationCache{ 37 items: make(map[string]string), 38 } 39 actualBucketLocationCache := newBucketLocationCache() 40 41 if !reflect.DeepEqual(actualBucketLocationCache, expectedBucketLocationcache) { 42 t.Errorf("Unexpected return value") 43 } 44} 45 46// Tests validate bucketLocationCache operations. 47func TestBucketLocationCacheOps(t *testing.T) { 48 testBucketLocationCache := newBucketLocationCache() 49 expectedBucketName := "minio-bucket" 50 expectedLocation := "us-east-1" 51 testBucketLocationCache.Set(expectedBucketName, expectedLocation) 52 actualLocation, ok := testBucketLocationCache.Get(expectedBucketName) 53 if !ok { 54 t.Errorf("Bucket location cache not set") 55 } 56 if expectedLocation != actualLocation { 57 t.Errorf("Bucket location cache not set to expected value") 58 } 59 testBucketLocationCache.Delete(expectedBucketName) 60 _, ok = testBucketLocationCache.Get(expectedBucketName) 61 if ok { 62 t.Errorf("Bucket location cache not deleted as expected") 63 } 64} 65 66// Tests validate http request generation for 'getBucketLocation'. 67func TestGetBucketLocationRequest(t *testing.T) { 68 // Generates expected http request for getBucketLocation. 69 // Used for asserting with the actual request generated. 70 createExpectedRequest := func(c *Client, bucketName string, req *http.Request) (*http.Request, error) { 71 // Set location query. 72 urlValues := make(url.Values) 73 urlValues.Set("location", "") 74 75 // Set get bucket location always as path style. 76 targetURL := c.endpointURL 77 targetURL.Path = path.Join(bucketName, "") + "/" 78 targetURL.RawQuery = urlValues.Encode() 79 80 // Get a new HTTP request for the method. 81 var err error 82 req, err = http.NewRequest("GET", targetURL.String(), nil) 83 if err != nil { 84 return nil, err 85 } 86 87 // Set UserAgent for the request. 88 c.setUserAgent(req) 89 90 // Get credentials from the configured credentials provider. 91 value, err := c.credsProvider.Get() 92 if err != nil { 93 return nil, err 94 } 95 96 var ( 97 signerType = value.SignerType 98 accessKeyID = value.AccessKeyID 99 secretAccessKey = value.SecretAccessKey 100 sessionToken = value.SessionToken 101 ) 102 103 // Custom signer set then override the behavior. 104 if c.overrideSignerType != credentials.SignatureDefault { 105 signerType = c.overrideSignerType 106 } 107 108 // If signerType returned by credentials helper is anonymous, 109 // then do not sign regardless of signerType override. 110 if value.SignerType == credentials.SignatureAnonymous { 111 signerType = credentials.SignatureAnonymous 112 } 113 114 // Set sha256 sum for signature calculation only 115 // with signature version '4'. 116 switch { 117 case signerType.IsV4(): 118 contentSha256 := emptySHA256Hex 119 if c.secure { 120 contentSha256 = unsignedPayload 121 } 122 req.Header.Set("X-Amz-Content-Sha256", contentSha256) 123 req = s3signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, "us-east-1") 124 case signerType.IsV2(): 125 req = s3signer.SignV2(*req, accessKeyID, secretAccessKey, false) 126 } 127 128 return req, nil 129 130 } 131 // Info for 'Client' creation. 132 // Will be used as arguments for 'NewClient'. 133 type infoForClient struct { 134 endPoint string 135 accessKey string 136 secretKey string 137 enableInsecure bool 138 } 139 // dataset for 'NewClient' call. 140 info := []infoForClient{ 141 // endpoint localhost. 142 // both access-key and secret-key are empty. 143 {"localhost:9000", "", "", false}, 144 // both access-key are secret-key exists. 145 {"localhost:9000", "my-access-key", "my-secret-key", false}, 146 // one of acess-key and secret-key are empty. 147 {"localhost:9000", "", "my-secret-key", false}, 148 149 // endpoint amazon s3. 150 {"s3.amazonaws.com", "", "", false}, 151 {"s3.amazonaws.com", "my-access-key", "my-secret-key", false}, 152 {"s3.amazonaws.com", "my-acess-key", "", false}, 153 154 // endpoint google cloud storage. 155 {"storage.googleapis.com", "", "", false}, 156 {"storage.googleapis.com", "my-access-key", "my-secret-key", false}, 157 {"storage.googleapis.com", "", "my-secret-key", false}, 158 159 // endpoint custom domain running Minio server. 160 {"play.minio.io", "", "", false}, 161 {"play.minio.io", "my-access-key", "my-secret-key", false}, 162 {"play.minio.io", "my-acess-key", "", false}, 163 } 164 testCases := []struct { 165 bucketName string 166 // data for new client creation. 167 info infoForClient 168 // error in the output. 169 err error 170 // flag indicating whether tests should pass. 171 shouldPass bool 172 }{ 173 // Client is constructed using the info struct. 174 // case with empty location. 175 {"my-bucket", info[0], nil, true}, 176 // case with location set to standard 'us-east-1'. 177 {"my-bucket", info[0], nil, true}, 178 // case with location set to a value different from 'us-east-1'. 179 {"my-bucket", info[0], nil, true}, 180 181 {"my-bucket", info[1], nil, true}, 182 {"my-bucket", info[1], nil, true}, 183 {"my-bucket", info[1], nil, true}, 184 185 {"my-bucket", info[2], nil, true}, 186 {"my-bucket", info[2], nil, true}, 187 {"my-bucket", info[2], nil, true}, 188 189 {"my-bucket", info[3], nil, true}, 190 {"my-bucket", info[3], nil, true}, 191 {"my-bucket", info[3], nil, true}, 192 193 {"my-bucket", info[4], nil, true}, 194 {"my-bucket", info[4], nil, true}, 195 {"my-bucket", info[4], nil, true}, 196 197 {"my-bucket", info[5], nil, true}, 198 {"my-bucket", info[5], nil, true}, 199 {"my-bucket", info[5], nil, true}, 200 201 {"my-bucket", info[6], nil, true}, 202 {"my-bucket", info[6], nil, true}, 203 {"my-bucket", info[6], nil, true}, 204 205 {"my-bucket", info[7], nil, true}, 206 {"my-bucket", info[7], nil, true}, 207 {"my-bucket", info[7], nil, true}, 208 209 {"my-bucket", info[8], nil, true}, 210 {"my-bucket", info[8], nil, true}, 211 {"my-bucket", info[8], nil, true}, 212 213 {"my-bucket", info[9], nil, true}, 214 {"my-bucket", info[9], nil, true}, 215 {"my-bucket", info[9], nil, true}, 216 217 {"my-bucket", info[10], nil, true}, 218 {"my-bucket", info[10], nil, true}, 219 {"my-bucket", info[10], nil, true}, 220 221 {"my-bucket", info[11], nil, true}, 222 {"my-bucket", info[11], nil, true}, 223 {"my-bucket", info[11], nil, true}, 224 } 225 for i, testCase := range testCases { 226 // cannot create a newclient with empty endPoint value. 227 // validates and creates a new client only if the endPoint value is not empty. 228 client := &Client{} 229 var err error 230 if testCase.info.endPoint != "" { 231 232 client, err = New(testCase.info.endPoint, testCase.info.accessKey, testCase.info.secretKey, testCase.info.enableInsecure) 233 if err != nil { 234 t.Fatalf("Test %d: Failed to create new Client: %s", i+1, err.Error()) 235 } 236 } 237 238 actualReq, err := client.getBucketLocationRequest(testCase.bucketName) 239 if err != nil && testCase.shouldPass { 240 t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error()) 241 } 242 if err == nil && !testCase.shouldPass { 243 t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error()) 244 } 245 // Failed as expected, but does it fail for the expected reason. 246 if err != nil && !testCase.shouldPass { 247 if err.Error() != testCase.err.Error() { 248 t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error()) 249 } 250 } 251 252 // Test passes as expected, but the output values are verified for correctness here. 253 if err == nil && testCase.shouldPass { 254 expectedReq := &http.Request{} 255 expectedReq, err = createExpectedRequest(client, testCase.bucketName, expectedReq) 256 if err != nil { 257 t.Fatalf("Test %d: Expected request Creation failed", i+1) 258 } 259 if expectedReq.Method != actualReq.Method { 260 t.Errorf("Test %d: The expected Request method doesn't match with the actual one", i+1) 261 } 262 if expectedReq.URL.String() != actualReq.URL.String() { 263 t.Errorf("Test %d: Expected the request URL to be '%s', but instead found '%s'", i+1, expectedReq.URL.String(), actualReq.URL.String()) 264 } 265 if expectedReq.ContentLength != actualReq.ContentLength { 266 t.Errorf("Test %d: Expected the request body Content-Length to be '%d', but found '%d' instead", i+1, expectedReq.ContentLength, actualReq.ContentLength) 267 } 268 269 if expectedReq.Header.Get("X-Amz-Content-Sha256") != actualReq.Header.Get("X-Amz-Content-Sha256") { 270 t.Errorf("Test %d: 'X-Amz-Content-Sha256' header of the expected request doesn't match with that of the actual request", i+1) 271 } 272 if expectedReq.Header.Get("User-Agent") != actualReq.Header.Get("User-Agent") { 273 t.Errorf("Test %d: Expected 'User-Agent' header to be \"%s\",but found \"%s\" instead", i+1, expectedReq.Header.Get("User-Agent"), actualReq.Header.Get("User-Agent")) 274 } 275 } 276 } 277} 278 279// generates http response with bucket location set in the body. 280func generateLocationResponse(resp *http.Response, bodyContent []byte) (*http.Response, error) { 281 resp.StatusCode = http.StatusOK 282 resp.Body = ioutil.NopCloser(bytes.NewBuffer(bodyContent)) 283 return resp, nil 284} 285 286// Tests the processing of GetPolicy response from server. 287func TestProcessBucketLocationResponse(t *testing.T) { 288 // LocationResponse - format for location response. 289 type LocationResponse struct { 290 XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ LocationConstraint" json:"-"` 291 Location string `xml:",chardata"` 292 } 293 294 APIErrors := []APIError{ 295 { 296 Code: "AccessDenied", 297 Description: "Access Denied", 298 HTTPStatusCode: http.StatusUnauthorized, 299 }, 300 } 301 testCases := []struct { 302 bucketName string 303 inputLocation string 304 isAPIError bool 305 apiErr APIError 306 // expected results. 307 expectedResult string 308 err error 309 // flag indicating whether tests should pass. 310 shouldPass bool 311 }{ 312 {"my-bucket", "", true, APIErrors[0], "us-east-1", nil, true}, 313 {"my-bucket", "", false, APIError{}, "us-east-1", nil, true}, 314 {"my-bucket", "EU", false, APIError{}, "eu-west-1", nil, true}, 315 {"my-bucket", "eu-central-1", false, APIError{}, "eu-central-1", nil, true}, 316 {"my-bucket", "us-east-1", false, APIError{}, "us-east-1", nil, true}, 317 } 318 319 for i, testCase := range testCases { 320 inputResponse := &http.Response{} 321 var err error 322 if testCase.isAPIError { 323 inputResponse = generateErrorResponse(inputResponse, testCase.apiErr, testCase.bucketName) 324 } else { 325 inputResponse, err = generateLocationResponse(inputResponse, encodeResponse(LocationResponse{ 326 Location: testCase.inputLocation, 327 })) 328 if err != nil { 329 t.Fatalf("Test %d: Creation of valid response failed", i+1) 330 } 331 } 332 actualResult, err := processBucketLocationResponse(inputResponse, "my-bucket") 333 if err != nil && testCase.shouldPass { 334 t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error()) 335 } 336 if err == nil && !testCase.shouldPass { 337 t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error()) 338 } 339 // Failed as expected, but does it fail for the expected reason. 340 if err != nil && !testCase.shouldPass { 341 if err.Error() != testCase.err.Error() { 342 t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error()) 343 } 344 } 345 if err == nil && testCase.shouldPass { 346 if !reflect.DeepEqual(testCase.expectedResult, actualResult) { 347 t.Errorf("Test %d: The expected BucketPolicy doesn't match the actual BucketPolicy", i+1) 348 } 349 } 350 } 351} 352