storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/bucket/object/lock/lock_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 lock
    18  
    19  import (
    20  	"encoding/xml"
    21  	"errors"
    22  	"fmt"
    23  	"net/http"
    24  	"reflect"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	xhttp "storj.io/minio/cmd/http"
    30  )
    31  
    32  func TestParseMode(t *testing.T) {
    33  	testCases := []struct {
    34  		value        string
    35  		expectedMode RetMode
    36  	}{
    37  		{
    38  			value:        "governance",
    39  			expectedMode: RetGovernance,
    40  		},
    41  		{
    42  			value:        "complIAnce",
    43  			expectedMode: RetCompliance,
    44  		},
    45  		{
    46  			value:        "gce",
    47  			expectedMode: "",
    48  		},
    49  	}
    50  
    51  	for _, tc := range testCases {
    52  		if parseRetMode(tc.value) != tc.expectedMode {
    53  			t.Errorf("Expected Mode %s, got %s", tc.expectedMode, parseRetMode(tc.value))
    54  		}
    55  	}
    56  }
    57  func TestParseLegalHoldStatus(t *testing.T) {
    58  	tests := []struct {
    59  		value          string
    60  		expectedStatus LegalHoldStatus
    61  	}{
    62  		{
    63  			value:          "ON",
    64  			expectedStatus: LegalHoldOn,
    65  		},
    66  		{
    67  			value:          "Off",
    68  			expectedStatus: LegalHoldOff,
    69  		},
    70  		{
    71  			value:          "x",
    72  			expectedStatus: "",
    73  		},
    74  	}
    75  
    76  	for _, tt := range tests {
    77  		actualStatus := parseLegalHoldStatus(tt.value)
    78  		if actualStatus != tt.expectedStatus {
    79  			t.Errorf("Expected legal hold status %s, got %s", tt.expectedStatus, actualStatus)
    80  		}
    81  	}
    82  }
    83  
    84  // TestUnmarshalDefaultRetention checks if default retention
    85  // marshaling and unmarshaling work as expected
    86  func TestUnmarshalDefaultRetention(t *testing.T) {
    87  	days := uint64(4)
    88  	years := uint64(1)
    89  	zerodays := uint64(0)
    90  	invalidDays := uint64(maximumRetentionDays + 1)
    91  	tests := []struct {
    92  		value       DefaultRetention
    93  		expectedErr error
    94  		expectErr   bool
    95  	}{
    96  		{
    97  			value:       DefaultRetention{Mode: "retain"},
    98  			expectedErr: fmt.Errorf("unknown retention mode retain"),
    99  			expectErr:   true,
   100  		},
   101  		{
   102  			value:       DefaultRetention{Mode: RetGovernance},
   103  			expectedErr: fmt.Errorf("either Days or Years must be specified"),
   104  			expectErr:   true,
   105  		},
   106  		{
   107  			value:       DefaultRetention{Mode: RetGovernance, Days: &days},
   108  			expectedErr: nil,
   109  			expectErr:   false,
   110  		},
   111  		{
   112  			value:       DefaultRetention{Mode: RetGovernance, Years: &years},
   113  			expectedErr: nil,
   114  			expectErr:   false,
   115  		},
   116  		{
   117  			value:       DefaultRetention{Mode: RetGovernance, Days: &days, Years: &years},
   118  			expectedErr: fmt.Errorf("either Days or Years must be specified, not both"),
   119  			expectErr:   true,
   120  		},
   121  		{
   122  			value:       DefaultRetention{Mode: RetGovernance, Days: &zerodays},
   123  			expectedErr: fmt.Errorf("Default retention period must be a positive integer value for 'Days'"),
   124  			expectErr:   true,
   125  		},
   126  		{
   127  			value:       DefaultRetention{Mode: RetGovernance, Days: &invalidDays},
   128  			expectedErr: fmt.Errorf("Default retention period too large for 'Days' %d", invalidDays),
   129  			expectErr:   true,
   130  		},
   131  	}
   132  	for _, tt := range tests {
   133  		d, err := xml.MarshalIndent(&tt.value, "", "\t")
   134  		if err != nil {
   135  			t.Fatal(err)
   136  		}
   137  		var dr DefaultRetention
   138  		err = xml.Unmarshal(d, &dr)
   139  		if tt.expectedErr == nil {
   140  			if err != nil {
   141  				t.Fatalf("error: expected = <nil>, got = %v", err)
   142  			}
   143  		} else if err == nil {
   144  			t.Fatalf("error: expected = %v, got = <nil>", tt.expectedErr)
   145  		} else if tt.expectedErr.Error() != err.Error() {
   146  			t.Fatalf("error: expected = %v, got = %v", tt.expectedErr, err)
   147  		}
   148  	}
   149  }
   150  
   151  func TestParseObjectLockConfig(t *testing.T) {
   152  	tests := []struct {
   153  		value       string
   154  		expectedErr error
   155  		expectErr   bool
   156  	}{
   157  		{
   158  			value:       `<ObjectLockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><ObjectLockEnabled>yes</ObjectLockEnabled></ObjectLockConfiguration>`,
   159  			expectedErr: fmt.Errorf("only 'Enabled' value is allowed to ObjectLockEnabled element"),
   160  			expectErr:   true,
   161  		},
   162  		{
   163  			value:       `<ObjectLockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><ObjectLockEnabled>Enabled</ObjectLockEnabled><Rule><DefaultRetention><Mode>COMPLIANCE</Mode><Days>0</Days></DefaultRetention></Rule></ObjectLockConfiguration>`,
   164  			expectedErr: fmt.Errorf("Default retention period must be a positive integer value for 'Days'"),
   165  			expectErr:   true,
   166  		},
   167  		{
   168  			value:       `<ObjectLockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><ObjectLockEnabled>Enabled</ObjectLockEnabled><Rule><DefaultRetention><Mode>COMPLIANCE</Mode><Days>30</Days></DefaultRetention></Rule></ObjectLockConfiguration>`,
   169  			expectedErr: nil,
   170  			expectErr:   false,
   171  		},
   172  	}
   173  	for _, tt := range tests {
   174  		_, err := ParseObjectLockConfig(strings.NewReader(tt.value))
   175  		if tt.expectedErr == nil {
   176  			if err != nil {
   177  				t.Fatalf("error: expected = <nil>, got = %v", err)
   178  			}
   179  		} else if err == nil {
   180  			t.Fatalf("error: expected = %v, got = <nil>", tt.expectedErr)
   181  		} else if tt.expectedErr.Error() != err.Error() {
   182  			t.Fatalf("error: expected = %v, got = %v", tt.expectedErr, err)
   183  		}
   184  	}
   185  }
   186  
   187  func TestParseObjectRetention(t *testing.T) {
   188  	tests := []struct {
   189  		value       string
   190  		expectedErr error
   191  		expectErr   bool
   192  	}{
   193  		{
   194  			value:       `<?xml version="1.0" encoding="UTF-8"?><Retention xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Mode>string</Mode><RetainUntilDate>2020-01-02T15:04:05Z</RetainUntilDate></Retention>`,
   195  			expectedErr: ErrUnknownWORMModeDirective,
   196  			expectErr:   true,
   197  		},
   198  		{
   199  			value:       `<?xml version="1.0" encoding="UTF-8"?><Retention xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Mode>COMPLIANCE</Mode><RetainUntilDate>2017-01-02T15:04:05Z</RetainUntilDate></Retention>`,
   200  			expectedErr: ErrPastObjectLockRetainDate,
   201  			expectErr:   true,
   202  		},
   203  		{
   204  			value:       `<?xml version="1.0" encoding="UTF-8"?><Retention xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Mode>GOVERNANCE</Mode><RetainUntilDate>2057-01-02T15:04:05Z</RetainUntilDate></Retention>`,
   205  			expectedErr: nil,
   206  			expectErr:   false,
   207  		},
   208  	}
   209  	for _, tt := range tests {
   210  		_, err := ParseObjectRetention(strings.NewReader(tt.value))
   211  		if tt.expectedErr == nil {
   212  			if err != nil {
   213  				t.Fatalf("error: expected = <nil>, got = %v", err)
   214  			}
   215  		} else if err == nil {
   216  			t.Fatalf("error: expected = %v, got = <nil>", tt.expectedErr)
   217  		} else if tt.expectedErr.Error() != err.Error() {
   218  			t.Fatalf("error: expected = %v, got = %v", tt.expectedErr, err)
   219  		}
   220  	}
   221  }
   222  
   223  func TestIsObjectLockRequested(t *testing.T) {
   224  	tests := []struct {
   225  		header      http.Header
   226  		expectedVal bool
   227  	}{
   228  		{
   229  			header: http.Header{
   230  				"Authorization":        []string{"AWS4-HMAC-SHA256 <cred_string>"},
   231  				"X-Amz-Content-Sha256": []string{""},
   232  				"Content-Encoding":     []string{""},
   233  			},
   234  			expectedVal: false,
   235  		},
   236  		{
   237  			header: http.Header{
   238  				AmzObjectLockLegalHold: []string{""},
   239  			},
   240  			expectedVal: true,
   241  		},
   242  		{
   243  			header: http.Header{
   244  				AmzObjectLockRetainUntilDate: []string{""},
   245  				AmzObjectLockMode:            []string{""},
   246  			},
   247  			expectedVal: true,
   248  		},
   249  		{
   250  			header: http.Header{
   251  				AmzObjectLockBypassRetGovernance: []string{""},
   252  			},
   253  			expectedVal: false,
   254  		},
   255  	}
   256  	for _, tt := range tests {
   257  		actualVal := IsObjectLockRequested(tt.header)
   258  		if actualVal != tt.expectedVal {
   259  			t.Fatalf("error: expected %v, actual %v", tt.expectedVal, actualVal)
   260  		}
   261  	}
   262  }
   263  
   264  func TestIsObjectLockGovernanceBypassSet(t *testing.T) {
   265  	tests := []struct {
   266  		header      http.Header
   267  		expectedVal bool
   268  	}{
   269  		{
   270  			header: http.Header{
   271  				"Authorization":        []string{"AWS4-HMAC-SHA256 <cred_string>"},
   272  				"X-Amz-Content-Sha256": []string{""},
   273  				"Content-Encoding":     []string{""},
   274  			},
   275  			expectedVal: false,
   276  		},
   277  		{
   278  			header: http.Header{
   279  				AmzObjectLockLegalHold: []string{""},
   280  			},
   281  			expectedVal: false,
   282  		},
   283  		{
   284  			header: http.Header{
   285  				AmzObjectLockRetainUntilDate: []string{""},
   286  				AmzObjectLockMode:            []string{""},
   287  			},
   288  			expectedVal: false,
   289  		},
   290  		{
   291  			header: http.Header{
   292  				AmzObjectLockBypassRetGovernance: []string{""},
   293  			},
   294  			expectedVal: false,
   295  		},
   296  		{
   297  			header: http.Header{
   298  				AmzObjectLockBypassRetGovernance: []string{"true"},
   299  			},
   300  			expectedVal: true,
   301  		},
   302  	}
   303  	for _, tt := range tests {
   304  		actualVal := IsObjectLockGovernanceBypassSet(tt.header)
   305  		if actualVal != tt.expectedVal {
   306  			t.Fatalf("error: expected %v, actual %v", tt.expectedVal, actualVal)
   307  		}
   308  	}
   309  }
   310  
   311  func TestParseObjectLockRetentionHeaders(t *testing.T) {
   312  	tests := []struct {
   313  		header      http.Header
   314  		expectedErr error
   315  	}{
   316  		{
   317  			header: http.Header{
   318  				"Authorization":        []string{"AWS4-HMAC-SHA256 <cred_string>"},
   319  				"X-Amz-Content-Sha256": []string{""},
   320  				"Content-Encoding":     []string{""},
   321  			},
   322  			expectedErr: ErrObjectLockInvalidHeaders,
   323  		},
   324  		{
   325  			header: http.Header{
   326  				xhttp.AmzObjectLockMode:            []string{"lock"},
   327  				xhttp.AmzObjectLockRetainUntilDate: []string{"2017-01-02"},
   328  			},
   329  			expectedErr: ErrUnknownWORMModeDirective,
   330  		},
   331  		{
   332  			header: http.Header{
   333  				xhttp.AmzObjectLockMode: []string{"governance"},
   334  			},
   335  			expectedErr: ErrObjectLockInvalidHeaders,
   336  		},
   337  		{
   338  			header: http.Header{
   339  				xhttp.AmzObjectLockRetainUntilDate: []string{"2017-01-02"},
   340  				xhttp.AmzObjectLockMode:            []string{"governance"},
   341  			},
   342  			expectedErr: ErrInvalidRetentionDate,
   343  		},
   344  		{
   345  			header: http.Header{
   346  				xhttp.AmzObjectLockRetainUntilDate: []string{"2017-01-02T15:04:05Z"},
   347  				xhttp.AmzObjectLockMode:            []string{"governance"},
   348  			},
   349  			expectedErr: ErrPastObjectLockRetainDate,
   350  		},
   351  		{
   352  			header: http.Header{
   353  				xhttp.AmzObjectLockMode:            []string{"governance"},
   354  				xhttp.AmzObjectLockRetainUntilDate: []string{"2017-01-02T15:04:05Z"},
   355  			},
   356  			expectedErr: ErrPastObjectLockRetainDate,
   357  		},
   358  		{
   359  			header: http.Header{
   360  				xhttp.AmzObjectLockMode:            []string{"governance"},
   361  				xhttp.AmzObjectLockRetainUntilDate: []string{"2087-01-02T15:04:05Z"},
   362  			},
   363  			expectedErr: nil,
   364  		},
   365  	}
   366  
   367  	for i, tt := range tests {
   368  		_, _, err := ParseObjectLockRetentionHeaders(tt.header)
   369  		if tt.expectedErr == nil {
   370  			if err != nil {
   371  				t.Fatalf("Case %d error: expected = <nil>, got = %v", i, err)
   372  			}
   373  		} else if err == nil {
   374  			t.Fatalf("Case %d error: expected = %v, got = <nil>", i, tt.expectedErr)
   375  		} else if tt.expectedErr.Error() != err.Error() {
   376  			t.Fatalf("Case %d error: expected = %v, got = %v", i, tt.expectedErr, err)
   377  		}
   378  	}
   379  }
   380  
   381  func TestGetObjectRetentionMeta(t *testing.T) {
   382  	tests := []struct {
   383  		metadata map[string]string
   384  		expected ObjectRetention
   385  	}{
   386  		{
   387  			metadata: map[string]string{
   388  				"Authorization":        "AWS4-HMAC-SHA256 <cred_string>",
   389  				"X-Amz-Content-Sha256": "",
   390  				"Content-Encoding":     "",
   391  			},
   392  			expected: ObjectRetention{},
   393  		},
   394  		{
   395  			metadata: map[string]string{
   396  				"x-amz-object-lock-mode": "governance",
   397  			},
   398  			expected: ObjectRetention{Mode: RetGovernance},
   399  		},
   400  		{
   401  			metadata: map[string]string{
   402  				"x-amz-object-lock-retain-until-date": "2020-02-01",
   403  			},
   404  			expected: ObjectRetention{RetainUntilDate: RetentionDate{time.Date(2020, 2, 1, 12, 0, 0, 0, time.UTC)}},
   405  		},
   406  	}
   407  
   408  	for i, tt := range tests {
   409  		o := GetObjectRetentionMeta(tt.metadata)
   410  		if o.Mode != tt.expected.Mode {
   411  			t.Fatalf("Case %d expected %v, got %v", i, tt.expected.Mode, o.Mode)
   412  		}
   413  	}
   414  }
   415  
   416  func TestGetObjectLegalHoldMeta(t *testing.T) {
   417  	tests := []struct {
   418  		metadata map[string]string
   419  		expected ObjectLegalHold
   420  	}{
   421  		{
   422  			metadata: map[string]string{
   423  				"x-amz-object-lock-mode": "governance",
   424  			},
   425  			expected: ObjectLegalHold{},
   426  		},
   427  		{
   428  			metadata: map[string]string{
   429  				"x-amz-object-lock-legal-hold": "on",
   430  			},
   431  			expected: ObjectLegalHold{Status: LegalHoldOn},
   432  		},
   433  		{
   434  			metadata: map[string]string{
   435  				"x-amz-object-lock-legal-hold": "off",
   436  			},
   437  			expected: ObjectLegalHold{Status: LegalHoldOff},
   438  		},
   439  		{
   440  			metadata: map[string]string{
   441  				"x-amz-object-lock-legal-hold": "X",
   442  			},
   443  			expected: ObjectLegalHold{Status: ""},
   444  		},
   445  	}
   446  
   447  	for i, tt := range tests {
   448  		o := GetObjectLegalHoldMeta(tt.metadata)
   449  		if o.Status != tt.expected.Status {
   450  			t.Fatalf("Case %d expected %v, got %v", i, tt.expected.Status, o.Status)
   451  		}
   452  	}
   453  }
   454  
   455  func TestParseObjectLegalHold(t *testing.T) {
   456  	tests := []struct {
   457  		value       string
   458  		expectedErr error
   459  		expectErr   bool
   460  	}{
   461  		{
   462  			value:       `<?xml version="1.0" encoding="UTF-8"?><LegalHold xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Status>string</Status></LegalHold>`,
   463  			expectedErr: ErrMalformedXML,
   464  			expectErr:   true,
   465  		},
   466  		{
   467  			value:       `<?xml version="1.0" encoding="UTF-8"?><LegalHold xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Status>ON</Status></LegalHold>`,
   468  			expectedErr: nil,
   469  			expectErr:   false,
   470  		},
   471  		{
   472  			value:       `<?xml version="1.0" encoding="UTF-8"?><ObjectLockLegalHold xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Status>ON</Status></ObjectLockLegalHold>`,
   473  			expectedErr: nil,
   474  			expectErr:   false,
   475  		},
   476  		// invalid Status key
   477  		{
   478  			value:       `<?xml version="1.0" encoding="UTF-8"?><ObjectLockLegalHold xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><MyStatus>ON</MyStatus></ObjectLockLegalHold>`,
   479  			expectedErr: errors.New("expected element type <Status> but have <MyStatus>"),
   480  			expectErr:   true,
   481  		},
   482  		// invalid XML attr
   483  		{
   484  			value:       `<?xml version="1.0" encoding="UTF-8"?><UnknownLegalHold xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Status>ON</Status></UnknownLegalHold>`,
   485  			expectedErr: errors.New("expected element type <LegalHold>/<ObjectLockLegalHold> but have <UnknownLegalHold>"),
   486  			expectErr:   true,
   487  		},
   488  		{
   489  			value:       `<?xml version="1.0" encoding="UTF-8"?><LegalHold xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Status>On</Status></LegalHold>`,
   490  			expectedErr: ErrMalformedXML,
   491  			expectErr:   true,
   492  		},
   493  	}
   494  	for i, tt := range tests {
   495  		_, err := ParseObjectLegalHold(strings.NewReader(tt.value))
   496  		if tt.expectedErr == nil {
   497  			if err != nil {
   498  				t.Fatalf("Case %d error: expected = <nil>, got = %v", i, err)
   499  			}
   500  		} else if err == nil {
   501  			t.Fatalf("Case %d error: expected = %v, got = <nil>", i, tt.expectedErr)
   502  		} else if tt.expectedErr.Error() != err.Error() {
   503  			t.Fatalf("Case %d error: expected = %v, got = %v", i, tt.expectedErr, err)
   504  		}
   505  	}
   506  }
   507  func TestFilterObjectLockMetadata(t *testing.T) {
   508  	tests := []struct {
   509  		metadata        map[string]string
   510  		filterRetention bool
   511  		filterLegalHold bool
   512  		expected        map[string]string
   513  	}{
   514  		{
   515  			metadata: map[string]string{
   516  				"Authorization":        "AWS4-HMAC-SHA256 <cred_string>",
   517  				"X-Amz-Content-Sha256": "",
   518  				"Content-Encoding":     "",
   519  			},
   520  			expected: map[string]string{
   521  				"Authorization":        "AWS4-HMAC-SHA256 <cred_string>",
   522  				"X-Amz-Content-Sha256": "",
   523  				"Content-Encoding":     "",
   524  			},
   525  		},
   526  		{
   527  			metadata: map[string]string{
   528  				"x-amz-object-lock-mode": "governance",
   529  			},
   530  			expected: map[string]string{
   531  				"x-amz-object-lock-mode": "governance",
   532  			},
   533  			filterRetention: false,
   534  		},
   535  		{
   536  			metadata: map[string]string{
   537  				"x-amz-object-lock-mode":              "governance",
   538  				"x-amz-object-lock-retain-until-date": "2020-02-01",
   539  			},
   540  			expected:        map[string]string{},
   541  			filterRetention: true,
   542  		},
   543  		{
   544  			metadata: map[string]string{
   545  				"x-amz-object-lock-legal-hold": "off",
   546  			},
   547  			expected:        map[string]string{},
   548  			filterLegalHold: true,
   549  		},
   550  		{
   551  			metadata: map[string]string{
   552  				"x-amz-object-lock-legal-hold": "on",
   553  			},
   554  			expected:        map[string]string{"x-amz-object-lock-legal-hold": "on"},
   555  			filterLegalHold: false,
   556  		},
   557  		{
   558  			metadata: map[string]string{
   559  				"x-amz-object-lock-legal-hold":        "on",
   560  				"x-amz-object-lock-mode":              "governance",
   561  				"x-amz-object-lock-retain-until-date": "2020-02-01",
   562  			},
   563  			expected:        map[string]string{},
   564  			filterRetention: true,
   565  			filterLegalHold: true,
   566  		},
   567  		{
   568  			metadata: map[string]string{
   569  				"x-amz-object-lock-legal-hold":        "on",
   570  				"x-amz-object-lock-mode":              "governance",
   571  				"x-amz-object-lock-retain-until-date": "2020-02-01",
   572  			},
   573  			expected: map[string]string{"x-amz-object-lock-legal-hold": "on",
   574  				"x-amz-object-lock-mode":              "governance",
   575  				"x-amz-object-lock-retain-until-date": "2020-02-01"},
   576  		},
   577  	}
   578  
   579  	for i, tt := range tests {
   580  		o := FilterObjectLockMetadata(tt.metadata, tt.filterRetention, tt.filterLegalHold)
   581  		if !reflect.DeepEqual(o, tt.metadata) {
   582  			t.Fatalf("Case %d expected %v, got %v", i, tt.metadata, o)
   583  		}
   584  	}
   585  }