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