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 }