storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/bucket-lifecycle-handlers_test.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cmd
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/xml"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"testing"
    25  
    26  	"storj.io/minio/pkg/auth"
    27  )
    28  
    29  // Test S3 Bucket lifecycle APIs with wrong credentials
    30  func TestBucketLifecycleWrongCredentials(t *testing.T) {
    31  	ExecObjectLayerAPITest(t, testBucketLifecycleHandlersWrongCredentials, []string{"GetBucketLifecycle", "PutBucketLifecycle", "DeleteBucketLifecycle"})
    32  }
    33  
    34  // Test for authentication
    35  func testBucketLifecycleHandlersWrongCredentials(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
    36  	credentials auth.Credentials, t *testing.T) {
    37  	// test cases with sample input and expected output.
    38  	testCases := []struct {
    39  		method     string
    40  		bucketName string
    41  		accessKey  string
    42  		secretKey  string
    43  		// Sent body
    44  		body []byte
    45  		// Expected response
    46  		expectedRespStatus int
    47  		lifecycleResponse  []byte
    48  		errorResponse      APIErrorResponse
    49  		shouldPass         bool
    50  	}{
    51  		// GET empty credentials
    52  		{
    53  			method: http.MethodGet, bucketName: bucketName,
    54  			accessKey:          "",
    55  			secretKey:          "",
    56  			expectedRespStatus: http.StatusForbidden,
    57  			lifecycleResponse:  []byte(""),
    58  			errorResponse: APIErrorResponse{
    59  				Resource: SlashSeparator + bucketName + SlashSeparator,
    60  				Code:     "AccessDenied",
    61  				Message:  "Access Denied.",
    62  			},
    63  			shouldPass: false,
    64  		},
    65  		// GET wrong credentials
    66  		{
    67  			method: http.MethodGet, bucketName: bucketName,
    68  			accessKey:          "abcd",
    69  			secretKey:          "abcd",
    70  			expectedRespStatus: http.StatusForbidden,
    71  			lifecycleResponse:  []byte(""),
    72  			errorResponse: APIErrorResponse{
    73  				Resource: SlashSeparator + bucketName + SlashSeparator,
    74  				Code:     "InvalidAccessKeyId",
    75  				Message:  "The Access Key Id you provided does not exist in our records.",
    76  			},
    77  			shouldPass: false,
    78  		},
    79  		// PUT empty credentials
    80  		{
    81  			method:             http.MethodPut,
    82  			bucketName:         bucketName,
    83  			accessKey:          "",
    84  			secretKey:          "",
    85  			expectedRespStatus: http.StatusForbidden,
    86  			lifecycleResponse:  []byte(""),
    87  			errorResponse: APIErrorResponse{
    88  				Resource: SlashSeparator + bucketName + SlashSeparator,
    89  				Code:     "AccessDenied",
    90  				Message:  "Access Denied.",
    91  			},
    92  			shouldPass: false,
    93  		},
    94  		// PUT wrong credentials
    95  		{
    96  			method:             http.MethodPut,
    97  			bucketName:         bucketName,
    98  			accessKey:          "abcd",
    99  			secretKey:          "abcd",
   100  			expectedRespStatus: http.StatusForbidden,
   101  			lifecycleResponse:  []byte(""),
   102  			errorResponse: APIErrorResponse{
   103  				Resource: SlashSeparator + bucketName + SlashSeparator,
   104  				Code:     "InvalidAccessKeyId",
   105  				Message:  "The Access Key Id you provided does not exist in our records.",
   106  			},
   107  			shouldPass: false,
   108  		},
   109  		// DELETE empty credentials
   110  		{
   111  			method:             http.MethodDelete,
   112  			bucketName:         bucketName,
   113  			accessKey:          "",
   114  			secretKey:          "",
   115  			expectedRespStatus: http.StatusForbidden,
   116  			lifecycleResponse:  []byte(""),
   117  			errorResponse: APIErrorResponse{
   118  				Resource: SlashSeparator + bucketName + SlashSeparator,
   119  				Code:     "AccessDenied",
   120  				Message:  "Access Denied.",
   121  			},
   122  			shouldPass: false,
   123  		},
   124  		// DELETE wrong credentials
   125  		{
   126  			method:             http.MethodDelete,
   127  			bucketName:         bucketName,
   128  			accessKey:          "abcd",
   129  			secretKey:          "abcd",
   130  			expectedRespStatus: http.StatusForbidden,
   131  			lifecycleResponse:  []byte(""),
   132  			errorResponse: APIErrorResponse{
   133  				Resource: SlashSeparator + bucketName + SlashSeparator,
   134  				Code:     "InvalidAccessKeyId",
   135  				Message:  "The Access Key Id you provided does not exist in our records.",
   136  			},
   137  			shouldPass: false,
   138  		},
   139  	}
   140  
   141  	testBucketLifecycle(obj, instanceType, bucketName, apiRouter, t, testCases)
   142  }
   143  
   144  // Test S3 Bucket lifecycle APIs
   145  func TestBucketLifecycle(t *testing.T) {
   146  	ExecObjectLayerAPITest(t, testBucketLifecycleHandlers, []string{"GetBucketLifecycle", "PutBucketLifecycle", "DeleteBucketLifecycle"})
   147  }
   148  
   149  // Simple tests of bucket lifecycle: PUT, GET, DELETE.
   150  // Tests are related and the order is important.
   151  func testBucketLifecycleHandlers(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
   152  	creds auth.Credentials, t *testing.T) {
   153  
   154  	// test cases with sample input and expected output.
   155  	testCases := []struct {
   156  		method     string
   157  		bucketName string
   158  		accessKey  string
   159  		secretKey  string
   160  		// Sent body
   161  		body []byte
   162  		// Expected response
   163  		expectedRespStatus int
   164  		lifecycleResponse  []byte
   165  		errorResponse      APIErrorResponse
   166  		shouldPass         bool
   167  	}{
   168  		// Test case - 1.
   169  		// Filter contains more than (Prefix,Tag,And) rule
   170  		{
   171  			method:             http.MethodPut,
   172  			bucketName:         bucketName,
   173  			accessKey:          creds.AccessKey,
   174  			secretKey:          creds.SecretKey,
   175  			body:               []byte(`<LifecycleConfiguration><Rule><ID>id</ID><Filter><Prefix>logs/</Prefix><Tag><Key>Key1</Key><Value>Value1</Value></Tag></Filter><Status>Enabled</Status><Expiration><Days>365</Days></Expiration></Rule></LifecycleConfiguration>`),
   176  			expectedRespStatus: http.StatusBadRequest,
   177  			lifecycleResponse:  []byte(``),
   178  			errorResponse: APIErrorResponse{
   179  				Resource: SlashSeparator + bucketName + SlashSeparator,
   180  				Code:     "InvalidRequest",
   181  				Message:  "Filter must have exactly one of Prefix, Tag, or And specified",
   182  			},
   183  
   184  			shouldPass: false,
   185  		},
   186  		// Date contains wrong format
   187  		{
   188  			method:             http.MethodPut,
   189  			bucketName:         bucketName,
   190  			accessKey:          creds.AccessKey,
   191  			secretKey:          creds.SecretKey,
   192  			body:               []byte(`<LifecycleConfiguration><Rule><ID>id</ID><Filter><Prefix>logs/</Prefix><Tag><Key>Key1</Key><Value>Value1</Value></Tag></Filter><Status>Enabled</Status><Expiration><Date>365</Date></Expiration></Rule></LifecycleConfiguration>`),
   193  			expectedRespStatus: http.StatusBadRequest,
   194  			lifecycleResponse:  []byte(``),
   195  			errorResponse: APIErrorResponse{
   196  				Resource: SlashSeparator + bucketName + SlashSeparator,
   197  				Code:     "InvalidRequest",
   198  				Message:  "Date must be provided in ISO 8601 format",
   199  			},
   200  
   201  			shouldPass: false,
   202  		},
   203  		{
   204  			method:             http.MethodPut,
   205  			bucketName:         bucketName,
   206  			accessKey:          creds.AccessKey,
   207  			secretKey:          creds.SecretKey,
   208  			body:               []byte(`<?xml version="1.0" encoding="UTF-8"?><LifecycleConfiguration><Rule><ID>id</ID><Filter><Prefix>logs/</Prefix></Filter><Status>Enabled</Status><Expiration><Days>365</Days></Expiration></Rule></LifecycleConfiguration>`),
   209  			expectedRespStatus: http.StatusOK,
   210  			lifecycleResponse:  []byte(``),
   211  			errorResponse:      APIErrorResponse{},
   212  			shouldPass:         true,
   213  		},
   214  		{
   215  			method:             http.MethodGet,
   216  			accessKey:          creds.AccessKey,
   217  			secretKey:          creds.SecretKey,
   218  			bucketName:         bucketName,
   219  			body:               []byte(``),
   220  			expectedRespStatus: http.StatusOK,
   221  			lifecycleResponse:  []byte(`<LifecycleConfiguration><Rule><ID>id</ID><Status>Enabled</Status><Filter><Prefix>logs/</Prefix></Filter><Expiration><Days>365</Days></Expiration></Rule></LifecycleConfiguration>`),
   222  			errorResponse:      APIErrorResponse{},
   223  			shouldPass:         true,
   224  		},
   225  		{
   226  			method:             http.MethodDelete,
   227  			accessKey:          creds.AccessKey,
   228  			secretKey:          creds.SecretKey,
   229  			bucketName:         bucketName,
   230  			body:               []byte(``),
   231  			expectedRespStatus: http.StatusNoContent,
   232  			lifecycleResponse:  []byte(``),
   233  			errorResponse:      APIErrorResponse{},
   234  			shouldPass:         true,
   235  		},
   236  		{
   237  			method:             http.MethodGet,
   238  			accessKey:          creds.AccessKey,
   239  			secretKey:          creds.SecretKey,
   240  			bucketName:         bucketName,
   241  			body:               []byte(``),
   242  			expectedRespStatus: http.StatusNotFound,
   243  			lifecycleResponse:  []byte(``),
   244  			errorResponse: APIErrorResponse{
   245  				Resource: SlashSeparator + bucketName + SlashSeparator,
   246  				Code:     "NoSuchLifecycleConfiguration",
   247  				Message:  "The lifecycle configuration does not exist",
   248  			},
   249  			shouldPass: false,
   250  		},
   251  	}
   252  
   253  	testBucketLifecycle(obj, instanceType, bucketName, apiRouter, t, testCases)
   254  }
   255  
   256  // testBucketLifecycle is a generic testing of lifecycle requests
   257  func testBucketLifecycle(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
   258  	t *testing.T, testCases []struct {
   259  		method             string
   260  		bucketName         string
   261  		accessKey          string
   262  		secretKey          string
   263  		body               []byte
   264  		expectedRespStatus int
   265  		lifecycleResponse  []byte
   266  		errorResponse      APIErrorResponse
   267  		shouldPass         bool
   268  	}) {
   269  
   270  	for i, testCase := range testCases {
   271  		// initialize httptest Recorder, this records any mutations to response writer inside the handler.
   272  		rec := httptest.NewRecorder()
   273  		// construct HTTP request
   274  		req, err := newTestSignedRequestV4(testCase.method, getBucketLifecycleURL("", testCase.bucketName),
   275  			int64(len(testCase.body)), bytes.NewReader(testCase.body), testCase.accessKey, testCase.secretKey, nil)
   276  		if err != nil {
   277  			t.Fatalf("Test %d: %s: Failed to create HTTP request for GetBucketLocationHandler: <ERROR> %v", i+1, instanceType, err)
   278  		}
   279  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   280  		// Call the ServeHTTP to execute the handler.
   281  		apiRouter.ServeHTTP(rec, req)
   282  		if rec.Code != testCase.expectedRespStatus {
   283  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
   284  		}
   285  		if testCase.shouldPass && !bytes.Equal(testCase.lifecycleResponse, rec.Body.Bytes()) {
   286  			t.Errorf("Test %d: %s: Expected the response to be `%s`, but instead found `%s`", i+1, instanceType, string(testCase.lifecycleResponse), rec.Body.String())
   287  		}
   288  		errorResponse := APIErrorResponse{}
   289  		err = xml.Unmarshal(rec.Body.Bytes(), &errorResponse)
   290  		if err != nil && !testCase.shouldPass {
   291  			t.Fatalf("Test %d: %s: Unable to marshal response body %s", i+1, instanceType, rec.Body.String())
   292  		}
   293  		if errorResponse.Resource != testCase.errorResponse.Resource {
   294  			t.Errorf("Test %d: %s: Expected the error resource to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Resource, errorResponse.Resource)
   295  		}
   296  		if errorResponse.Message != testCase.errorResponse.Message {
   297  			t.Errorf("Test %d: %s: Expected the error message to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Message, errorResponse.Message)
   298  		}
   299  		if errorResponse.Code != testCase.errorResponse.Code {
   300  			t.Errorf("Test %d: %s: Expected the error code to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Code, errorResponse.Code)
   301  		}
   302  	}
   303  }