github.com/minio/console@v1.4.1/api/user_buckets_test.go (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "os" 24 "reflect" 25 "testing" 26 "time" 27 28 "github.com/minio/console/pkg/auth/token" 29 "github.com/minio/console/pkg/utils" 30 31 "github.com/go-openapi/swag" 32 "github.com/minio/console/models" 33 "github.com/minio/madmin-go/v3" 34 "github.com/minio/mc/pkg/probe" 35 "github.com/minio/minio-go/v7" 36 "github.com/minio/minio-go/v7/pkg/sse" 37 "github.com/minio/minio-go/v7/pkg/tags" 38 "github.com/stretchr/testify/assert" 39 ) 40 41 // assigning mock at runtime instead of compile time 42 var minioListBucketsWithContextMock func(ctx context.Context) ([]minio.BucketInfo, error) 43 44 var ( 45 minioMakeBucketWithContextMock func(ctx context.Context, bucketName, location string, objectLock bool) error 46 minioSetBucketPolicyWithContextMock func(ctx context.Context, bucketName, policy string) error 47 minioRemoveBucketMock func(bucketName string) error 48 minioGetBucketPolicyMock func(bucketName string) (string, error) 49 minioSetBucketEncryptionMock func(ctx context.Context, bucketName string, config *sse.Configuration) error 50 minioRemoveBucketEncryptionMock func(ctx context.Context, bucketName string) error 51 minioGetBucketEncryptionMock func(ctx context.Context, bucketName string) (*sse.Configuration, error) 52 minioSetObjectLockConfigMock func(ctx context.Context, bucketName string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit) error 53 minioGetBucketObjectLockConfigMock func(ctx context.Context, bucketName string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) 54 minioGetObjectLockConfigMock func(ctx context.Context, bucketName string) (lock string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) 55 minioSetVersioningMock func(ctx context.Context, state string, excludePrefix []string, excludeFolders bool) *probe.Error 56 minioCopyObjectMock func(ctx context.Context, dst minio.CopyDestOptions, src minio.CopySrcOptions) (minio.UploadInfo, error) 57 minioSetBucketTaggingMock func(ctx context.Context, bucketName string, tags *tags.Tags) error 58 minioRemoveBucketTaggingMock func(ctx context.Context, bucketName string) error 59 ) 60 61 // Define a mock struct of minio Client interface implementation 62 type minioClientMock struct{} 63 64 // mock function of listBucketsWithContext() 65 func (mc minioClientMock) listBucketsWithContext(ctx context.Context) ([]minio.BucketInfo, error) { 66 return minioListBucketsWithContextMock(ctx) 67 } 68 69 // mock function of makeBucketsWithContext() 70 func (mc minioClientMock) makeBucketWithContext(ctx context.Context, bucketName, location string, objectLock bool) error { 71 return minioMakeBucketWithContextMock(ctx, bucketName, location, objectLock) 72 } 73 74 // mock function of setBucketPolicyWithContext() 75 func (mc minioClientMock) setBucketPolicyWithContext(ctx context.Context, bucketName, policy string) error { 76 return minioSetBucketPolicyWithContextMock(ctx, bucketName, policy) 77 } 78 79 // mock function of removeBucket() 80 func (mc minioClientMock) removeBucket(_ context.Context, bucketName string) error { 81 return minioRemoveBucketMock(bucketName) 82 } 83 84 // mock function of getBucketPolicy() 85 func (mc minioClientMock) getBucketPolicy(_ context.Context, bucketName string) (string, error) { 86 return minioGetBucketPolicyMock(bucketName) 87 } 88 89 func (mc minioClientMock) setBucketEncryption(ctx context.Context, bucketName string, config *sse.Configuration) error { 90 return minioSetBucketEncryptionMock(ctx, bucketName, config) 91 } 92 93 func (mc minioClientMock) removeBucketEncryption(ctx context.Context, bucketName string) error { 94 return minioRemoveBucketEncryptionMock(ctx, bucketName) 95 } 96 97 func (mc minioClientMock) getBucketEncryption(ctx context.Context, bucketName string) (*sse.Configuration, error) { 98 return minioGetBucketEncryptionMock(ctx, bucketName) 99 } 100 101 func (mc minioClientMock) setObjectLockConfig(ctx context.Context, bucketName string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit) error { 102 return minioSetObjectLockConfigMock(ctx, bucketName, mode, validity, unit) 103 } 104 105 func (mc minioClientMock) getBucketObjectLockConfig(ctx context.Context, bucketName string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) { 106 return minioGetBucketObjectLockConfigMock(ctx, bucketName) 107 } 108 109 func (mc minioClientMock) getObjectLockConfig(ctx context.Context, bucketName string) (lock string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) { 110 return minioGetObjectLockConfigMock(ctx, bucketName) 111 } 112 113 func (mc minioClientMock) copyObject(ctx context.Context, dst minio.CopyDestOptions, src minio.CopySrcOptions) (minio.UploadInfo, error) { 114 return minioCopyObjectMock(ctx, dst, src) 115 } 116 117 func (c s3ClientMock) setVersioning(ctx context.Context, state string, excludePrefix []string, excludeFolders bool) *probe.Error { 118 return minioSetVersioningMock(ctx, state, excludePrefix, excludeFolders) 119 } 120 121 func (mc minioClientMock) GetBucketTagging(ctx context.Context, bucketName string) (*tags.Tags, error) { 122 return minioGetBucketTaggingMock(ctx, bucketName) 123 } 124 125 func (mc minioClientMock) SetBucketTagging(ctx context.Context, bucketName string, tags *tags.Tags) error { 126 return minioSetBucketTaggingMock(ctx, bucketName, tags) 127 } 128 129 func (mc minioClientMock) RemoveBucketTagging(ctx context.Context, bucketName string) error { 130 return minioRemoveBucketTaggingMock(ctx, bucketName) 131 } 132 133 func minioGetBucketTaggingMock(ctx context.Context, bucketName string) (*tags.Tags, error) { 134 fmt.Println(ctx) 135 fmt.Println(bucketName) 136 retval, _ := tags.NewTags(map[string]string{}, true) 137 return retval, nil 138 } 139 140 func TestMakeBucket(t *testing.T) { 141 assert := assert.New(t) 142 // mock minIO client 143 minClient := minioClientMock{} 144 function := "makeBucket()" 145 ctx := context.Background() 146 // Test-1: makeBucket() create a bucket 147 // mock function response from makeBucketWithContext(ctx) 148 minioMakeBucketWithContextMock = func(_ context.Context, _, _ string, _ bool) error { 149 return nil 150 } 151 if err := makeBucket(ctx, minClient, "bucktest1", true); err != nil { 152 t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error()) 153 } 154 155 // Test-2 makeBucket() make sure errors are handled correctly when errors on MakeBucketWithContext 156 minioMakeBucketWithContextMock = func(_ context.Context, _, _ string, _ bool) error { 157 return errors.New("error") 158 } 159 if err := makeBucket(ctx, minClient, "bucktest1", true); assert.Error(err) { 160 assert.Equal("error", err.Error()) 161 } 162 } 163 164 func TestDeleteBucket(t *testing.T) { 165 assert := assert.New(t) 166 // mock minIO client 167 minClient := minioClientMock{} 168 function := "removeBucket()" 169 170 // Test-1: removeBucket() delete a bucket 171 // mock function response from removeBucket(bucketName) 172 minioRemoveBucketMock = func(_ string) error { 173 return nil 174 } 175 if err := removeBucket(minClient, "bucktest1"); err != nil { 176 t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error()) 177 } 178 179 // Test-2: removeBucket() make sure errors are handled correctly when errors on DeleteBucket() 180 // mock function response from removeBucket(bucketName) 181 minioRemoveBucketMock = func(_ string) error { 182 return errors.New("error") 183 } 184 if err := removeBucket(minClient, "bucktest1"); assert.Error(err) { 185 assert.Equal("error", err.Error()) 186 } 187 } 188 189 func TestBucketInfo(t *testing.T) { 190 assert := assert.New(t) 191 // mock minIO client 192 minClient := minioClientMock{} 193 adminClient := AdminClientMock{} 194 ctx := context.Background() 195 function := "getBucketInfo()" 196 197 // Test-1: getBucketInfo() get a bucket with PRIVATE access 198 // if not policy set on bucket, access should be PRIVATE 199 mockPolicy := "" 200 minioGetBucketPolicyMock = func(_ string) (string, error) { 201 return mockPolicy, nil 202 } 203 bucketToSet := "csbucket" 204 outputExpected := &models.Bucket{ 205 Name: swag.String(bucketToSet), 206 Access: models.NewBucketAccess(models.BucketAccessPRIVATE), 207 CreationDate: "0001-01-01T00:00:00Z", 208 Size: 0, 209 Objects: 0, 210 } 211 infoPolicy := ` 212 { 213 "Version": "2012-10-17", 214 "Statement": [{ 215 "Action": [ 216 "admin:*" 217 ], 218 "Effect": "Allow", 219 "Sid": "" 220 }, 221 { 222 "Action": [ 223 "s3:*" 224 ], 225 "Effect": "Allow", 226 "Resource": [ 227 "arn:aws:s3:::*" 228 ], 229 "Sid": "" 230 } 231 ] 232 }` 233 mockBucketList := madmin.AccountInfo{ 234 AccountName: "test", 235 Buckets: []madmin.BucketAccessInfo{ 236 {Name: "bucket-1", Created: time.Now(), Size: 1024}, 237 {Name: "bucket-2", Created: time.Now().Add(time.Hour * 1), Size: 0}, 238 }, 239 Policy: []byte(infoPolicy), 240 } 241 // mock function response from listBucketsWithContext(ctx) 242 minioAccountInfoMock = func(_ context.Context) (madmin.AccountInfo, error) { 243 return mockBucketList, nil 244 } 245 246 bucketInfo, err := getBucketInfo(ctx, minClient, adminClient, bucketToSet) 247 if err != nil { 248 t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error()) 249 } 250 assert.Equal(outputExpected.Name, bucketInfo.Name) 251 assert.Equal(outputExpected.Access, bucketInfo.Access) 252 assert.Equal(outputExpected.CreationDate, bucketInfo.CreationDate) 253 assert.Equal(outputExpected.Size, bucketInfo.Size) 254 assert.Equal(outputExpected.Objects, bucketInfo.Objects) 255 256 // Test-2: getBucketInfo() get a bucket with PUBLIC access 257 // mock policy for bucket csbucket with readWrite access (should return PUBLIC) 258 mockPolicy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::csbucket\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\"],\"Resource\":[\"arn:aws:s3:::csbucket/*\"]}]}" 259 minioGetBucketPolicyMock = func(_ string) (string, error) { 260 return mockPolicy, nil 261 } 262 bucketToSet = "csbucket" 263 outputExpected = &models.Bucket{ 264 Name: swag.String(bucketToSet), 265 Access: models.NewBucketAccess(models.BucketAccessPUBLIC), 266 CreationDate: "0001-01-01T00:00:00Z", 267 Size: 0, 268 Objects: 0, 269 } 270 bucketInfo, err = getBucketInfo(ctx, minClient, adminClient, bucketToSet) 271 if err != nil { 272 t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error()) 273 } 274 assert.Equal(outputExpected.Name, bucketInfo.Name) 275 assert.Equal(outputExpected.Access, bucketInfo.Access) 276 assert.Equal(outputExpected.CreationDate, bucketInfo.CreationDate) 277 assert.Equal(outputExpected.Size, bucketInfo.Size) 278 assert.Equal(outputExpected.Objects, bucketInfo.Objects) 279 280 // Test-3: getBucketInfo() get a bucket with PRIVATE access 281 // if bucket has a null statement, the bucket is PRIVATE 282 mockPolicy = "{\"Version\":\"2012-10-17\",\"Statement\":[]}" 283 minioGetBucketPolicyMock = func(_ string) (string, error) { 284 return mockPolicy, nil 285 } 286 bucketToSet = "csbucket" 287 outputExpected = &models.Bucket{ 288 Name: swag.String(bucketToSet), 289 Access: models.NewBucketAccess(models.BucketAccessPRIVATE), 290 CreationDate: "0001-01-01T00:00:00Z", 291 Size: 0, 292 Objects: 0, 293 } 294 bucketInfo, err = getBucketInfo(ctx, minClient, adminClient, bucketToSet) 295 if err != nil { 296 t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error()) 297 } 298 assert.Equal(outputExpected.Name, bucketInfo.Name) 299 assert.Equal(outputExpected.Access, bucketInfo.Access) 300 assert.Equal(outputExpected.CreationDate, bucketInfo.CreationDate) 301 assert.Equal(outputExpected.Size, bucketInfo.Size) 302 assert.Equal(outputExpected.Objects, bucketInfo.Objects) 303 304 // Test-4: getBucketInfo() returns an errors while parsing invalid policy 305 mockPolicy = "policyinvalid" 306 minioGetBucketPolicyMock = func(_ string) (string, error) { 307 return mockPolicy, nil 308 } 309 bucketToSet = "csbucket" 310 outputExpected = &models.Bucket{ 311 Name: swag.String(bucketToSet), 312 Access: models.NewBucketAccess(models.BucketAccessCUSTOM), 313 CreationDate: "", 314 Size: 0, 315 } 316 _, err = getBucketInfo(ctx, minClient, adminClient, bucketToSet) 317 if assert.Error(err) { 318 assert.Equal("invalid character 'p' looking for beginning of value", err.Error()) 319 } 320 321 // Test-4: getBucketInfo() handle GetBucketPolicy errors correctly 322 // Test removed since we can tolerate this scenario now 323 } 324 325 func TestSetBucketAccess(t *testing.T) { 326 assert := assert.New(t) 327 ctx := context.Background() 328 // mock minIO client 329 minClient := minioClientMock{} 330 331 function := "setBucketAccessPolicy()" 332 // Test-1: setBucketAccessPolicy() set a bucket's access policy 333 // mock function response from setBucketPolicyWithContext(ctx) 334 minioSetBucketPolicyWithContextMock = func(_ context.Context, _, _ string) error { 335 return nil 336 } 337 if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPUBLIC, ""); err != nil { 338 t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error()) 339 } 340 341 // Test-2: setBucketAccessPolicy() set private access 342 if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPRIVATE, ""); err != nil { 343 t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error()) 344 } 345 346 // Test-3: setBucketAccessPolicy() set invalid access, expected errors 347 if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", "other", ""); assert.Error(err) { 348 assert.Equal("access: `other` not supported", err.Error()) 349 } 350 351 // Test-4: setBucketAccessPolicy() set access on empty bucket name, expected errors 352 if err := setBucketAccessPolicy(ctx, minClient, "", models.BucketAccessPRIVATE, ""); assert.Error(err) { 353 assert.Equal("error: bucket name not present", err.Error()) 354 } 355 356 // Test-5: setBucketAccessPolicy() set empty access on bucket, expected errors 357 if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", "", ""); assert.Error(err) { 358 assert.Equal("error: bucket access not present", err.Error()) 359 } 360 361 // Test-5: setBucketAccessPolicy() handle errors on SetPolicy call 362 minioSetBucketPolicyWithContextMock = func(_ context.Context, _, _ string) error { 363 return errors.New("error") 364 } 365 if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPUBLIC, ""); assert.Error(err) { 366 assert.Equal("error", err.Error()) 367 } 368 } 369 370 func Test_enableBucketEncryption(t *testing.T) { 371 ctx := context.Background() 372 minClient := minioClientMock{} 373 type args struct { 374 ctx context.Context 375 client MinioClient 376 bucketName string 377 encryptionType models.BucketEncryptionType 378 kmsKeyID string 379 mockEnableBucketEncryptionFunc func(ctx context.Context, bucketName string, config *sse.Configuration) error 380 } 381 tests := []struct { 382 name string 383 args args 384 wantErr bool 385 }{ 386 { 387 name: "Bucket encryption enabled correctly", 388 args: args{ 389 ctx: ctx, 390 client: minClient, 391 bucketName: "test", 392 encryptionType: "sse-s3", 393 mockEnableBucketEncryptionFunc: func(_ context.Context, _ string, _ *sse.Configuration) error { 394 return nil 395 }, 396 }, 397 wantErr: false, 398 }, 399 { 400 name: "Error when enabling bucket encryption", 401 args: args{ 402 ctx: ctx, 403 client: minClient, 404 bucketName: "test", 405 encryptionType: "sse-s3", 406 mockEnableBucketEncryptionFunc: func(_ context.Context, _ string, _ *sse.Configuration) error { 407 return ErrInvalidSession 408 }, 409 }, 410 wantErr: true, 411 }, 412 } 413 for _, tt := range tests { 414 t.Run(tt.name, func(_ *testing.T) { 415 minioSetBucketEncryptionMock = tt.args.mockEnableBucketEncryptionFunc 416 if err := enableBucketEncryption(tt.args.ctx, tt.args.client, tt.args.bucketName, tt.args.encryptionType, tt.args.kmsKeyID); (err != nil) != tt.wantErr { 417 t.Errorf("enableBucketEncryption() errors = %v, wantErr %v", err, tt.wantErr) 418 } 419 }) 420 } 421 } 422 423 func Test_disableBucketEncryption(t *testing.T) { 424 ctx := context.Background() 425 minClient := minioClientMock{} 426 type args struct { 427 ctx context.Context 428 client MinioClient 429 bucketName string 430 mockBucketDisableFunc func(ctx context.Context, bucketName string) error 431 } 432 tests := []struct { 433 name string 434 args args 435 wantErr bool 436 }{ 437 { 438 name: "Bucket encryption disabled correctly", 439 args: args{ 440 ctx: ctx, 441 client: minClient, 442 bucketName: "test", 443 mockBucketDisableFunc: func(_ context.Context, _ string) error { 444 return nil 445 }, 446 }, 447 wantErr: false, 448 }, 449 { 450 name: "Error when disabling bucket encryption", 451 args: args{ 452 ctx: ctx, 453 client: minClient, 454 bucketName: "test", 455 mockBucketDisableFunc: func(_ context.Context, _ string) error { 456 return ErrDefault 457 }, 458 }, 459 wantErr: true, 460 }, 461 } 462 for _, tt := range tests { 463 t.Run(tt.name, func(_ *testing.T) { 464 minioRemoveBucketEncryptionMock = tt.args.mockBucketDisableFunc 465 if err := disableBucketEncryption(tt.args.ctx, tt.args.client, tt.args.bucketName); (err != nil) != tt.wantErr { 466 t.Errorf("disableBucketEncryption() errors = %v, wantErr %v", err, tt.wantErr) 467 } 468 }) 469 } 470 } 471 472 func Test_getBucketEncryptionInfo(t *testing.T) { 473 ctx := context.Background() 474 minClient := minioClientMock{} 475 type args struct { 476 ctx context.Context 477 client MinioClient 478 bucketName string 479 mockBucketEncryptionGet func(ctx context.Context, bucketName string) (*sse.Configuration, error) 480 } 481 tests := []struct { 482 name string 483 args args 484 want *models.BucketEncryptionInfo 485 wantErr bool 486 }{ 487 { 488 name: "Bucket encryption info returned correctly", 489 args: args{ 490 ctx: ctx, 491 client: minClient, 492 bucketName: "test", 493 mockBucketEncryptionGet: func(_ context.Context, _ string) (*sse.Configuration, error) { 494 return &sse.Configuration{ 495 Rules: []sse.Rule{ 496 { 497 Apply: sse.ApplySSEByDefault{SSEAlgorithm: "AES256", KmsMasterKeyID: ""}, 498 }, 499 }, 500 }, nil 501 }, 502 }, 503 wantErr: false, 504 want: &models.BucketEncryptionInfo{ 505 Algorithm: "AES256", 506 KmsMasterKeyID: "", 507 }, 508 }, 509 { 510 name: "Bucket encryption info with no rules", 511 args: args{ 512 ctx: ctx, 513 client: minClient, 514 bucketName: "test", 515 mockBucketEncryptionGet: func(_ context.Context, _ string) (*sse.Configuration, error) { 516 return &sse.Configuration{ 517 Rules: []sse.Rule{}, 518 }, nil 519 }, 520 }, 521 wantErr: true, 522 }, 523 { 524 name: "Error when obtaining bucket encryption info", 525 args: args{ 526 ctx: ctx, 527 client: minClient, 528 bucketName: "test", 529 mockBucketEncryptionGet: func(_ context.Context, _ string) (*sse.Configuration, error) { 530 return nil, ErrSSENotConfigured 531 }, 532 }, 533 wantErr: true, 534 }, 535 } 536 for _, tt := range tests { 537 t.Run(tt.name, func(_ *testing.T) { 538 minioGetBucketEncryptionMock = tt.args.mockBucketEncryptionGet 539 got, err := getBucketEncryptionInfo(tt.args.ctx, tt.args.client, tt.args.bucketName) 540 if (err != nil) != tt.wantErr { 541 t.Errorf("getBucketEncryptionInfo() errors = %v, wantErr %v", err, tt.wantErr) 542 return 543 } 544 if !reflect.DeepEqual(got, tt.want) { 545 t.Errorf("getBucketEncryptionInfo() got = %v, want %v", got, tt.want) 546 } 547 }) 548 } 549 } 550 551 func Test_SetBucketRetentionConfig(t *testing.T) { 552 assert := assert.New(t) 553 ctx := context.Background() 554 minClient := minioClientMock{} 555 type args struct { 556 ctx context.Context 557 client MinioClient 558 bucketName string 559 mode models.ObjectRetentionMode 560 unit models.ObjectRetentionUnit 561 validity *int32 562 mockBucketRetentionFunc func(ctx context.Context, bucketName string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit) error 563 } 564 tests := []struct { 565 name string 566 args args 567 expectedError error 568 }{ 569 { 570 name: "Set Bucket Retention Config", 571 args: args{ 572 ctx: ctx, 573 client: minClient, 574 bucketName: "test", 575 mode: models.ObjectRetentionModeCompliance, 576 unit: models.ObjectRetentionUnitDays, 577 validity: swag.Int32(2), 578 mockBucketRetentionFunc: func(_ context.Context, _ string, _ *minio.RetentionMode, _ *uint, _ *minio.ValidityUnit) error { 579 return nil 580 }, 581 }, 582 expectedError: nil, 583 }, 584 { 585 name: "Set Bucket Retention Config 2", 586 args: args{ 587 ctx: ctx, 588 client: minClient, 589 bucketName: "test", 590 mode: models.ObjectRetentionModeGovernance, 591 unit: models.ObjectRetentionUnitYears, 592 validity: swag.Int32(2), 593 mockBucketRetentionFunc: func(_ context.Context, _ string, _ *minio.RetentionMode, _ *uint, _ *minio.ValidityUnit) error { 594 return nil 595 }, 596 }, 597 expectedError: nil, 598 }, 599 { 600 name: "Invalid validity", 601 args: args{ 602 ctx: ctx, 603 client: minClient, 604 bucketName: "test", 605 mode: models.ObjectRetentionModeCompliance, 606 unit: models.ObjectRetentionUnitDays, 607 validity: nil, 608 mockBucketRetentionFunc: func(_ context.Context, _ string, _ *minio.RetentionMode, _ *uint, _ *minio.ValidityUnit) error { 609 return nil 610 }, 611 }, 612 expectedError: errors.New("retention validity can't be nil"), 613 }, 614 { 615 name: "Invalid retention mode", 616 args: args{ 617 ctx: ctx, 618 client: minClient, 619 bucketName: "test", 620 mode: models.ObjectRetentionMode("othermode"), 621 unit: models.ObjectRetentionUnitDays, 622 validity: swag.Int32(2), 623 mockBucketRetentionFunc: func(_ context.Context, _ string, _ *minio.RetentionMode, _ *uint, _ *minio.ValidityUnit) error { 624 return nil 625 }, 626 }, 627 expectedError: errors.New("invalid retention mode"), 628 }, 629 { 630 name: "Invalid retention unit", 631 args: args{ 632 ctx: ctx, 633 client: minClient, 634 bucketName: "test", 635 mode: models.ObjectRetentionModeCompliance, 636 unit: models.ObjectRetentionUnit("otherunit"), 637 validity: swag.Int32(2), 638 mockBucketRetentionFunc: func(_ context.Context, _ string, _ *minio.RetentionMode, _ *uint, _ *minio.ValidityUnit) error { 639 return nil 640 }, 641 }, 642 expectedError: errors.New("invalid retention unit"), 643 }, 644 { 645 name: "Handle errors on objec lock function", 646 args: args{ 647 ctx: ctx, 648 client: minClient, 649 bucketName: "test", 650 mode: models.ObjectRetentionModeCompliance, 651 unit: models.ObjectRetentionUnitDays, 652 validity: swag.Int32(2), 653 mockBucketRetentionFunc: func(_ context.Context, _ string, _ *minio.RetentionMode, _ *uint, _ *minio.ValidityUnit) error { 654 return errors.New("error func") 655 }, 656 }, 657 expectedError: errors.New("error func"), 658 }, 659 } 660 for _, tt := range tests { 661 t.Run(tt.name, func(_ *testing.T) { 662 minioSetObjectLockConfigMock = tt.args.mockBucketRetentionFunc 663 err := setBucketRetentionConfig(tt.args.ctx, tt.args.client, tt.args.bucketName, tt.args.mode, tt.args.unit, tt.args.validity) 664 if tt.expectedError != nil { 665 fmt.Println(t.Name()) 666 assert.Equal(tt.expectedError.Error(), err.Error(), fmt.Sprintf("setObjectRetention() errors: `%s`, wantErr: `%s`", err, tt.expectedError)) 667 } else { 668 assert.Nil(err, fmt.Sprintf("setBucketRetentionConfig() errors: %v, wantErr: %v", err, tt.expectedError)) 669 } 670 }) 671 } 672 } 673 674 func Test_GetBucketRetentionConfig(t *testing.T) { 675 assert := assert.New(t) 676 ctx := context.Background() 677 minClient := minioClientMock{} 678 type args struct { 679 ctx context.Context 680 client MinioClient 681 bucketName string 682 getRetentionFunc func(ctx context.Context, bucketName string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) 683 } 684 tests := []struct { 685 name string 686 args args 687 expectedResponse *models.GetBucketRetentionConfig 688 expectedError error 689 }{ 690 { 691 name: "Get Bucket Retention Config", 692 args: args{ 693 ctx: ctx, 694 client: minClient, 695 bucketName: "test", 696 getRetentionFunc: func(_ context.Context, _ string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) { 697 m := minio.Governance 698 u := minio.Days 699 return &m, swag.Uint(2), &u, nil 700 }, 701 }, 702 expectedResponse: &models.GetBucketRetentionConfig{ 703 Mode: models.ObjectRetentionModeGovernance, 704 Unit: models.ObjectRetentionUnitDays, 705 Validity: int32(2), 706 }, 707 expectedError: nil, 708 }, 709 { 710 name: "Get Bucket Retention Config Compliance", 711 args: args{ 712 ctx: ctx, 713 client: minClient, 714 bucketName: "test", 715 getRetentionFunc: func(_ context.Context, _ string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) { 716 m := minio.Compliance 717 u := minio.Days 718 return &m, swag.Uint(2), &u, nil 719 }, 720 }, 721 expectedResponse: &models.GetBucketRetentionConfig{ 722 Mode: models.ObjectRetentionModeCompliance, 723 Unit: models.ObjectRetentionUnitDays, 724 Validity: int32(2), 725 }, 726 expectedError: nil, 727 }, 728 { 729 name: "Handle Error on minio func", 730 args: args{ 731 ctx: ctx, 732 client: minClient, 733 bucketName: "test", 734 getRetentionFunc: func(_ context.Context, _ string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) { 735 return nil, nil, nil, errors.New("error func") 736 }, 737 }, 738 expectedResponse: nil, 739 expectedError: errors.New("error func"), 740 }, 741 { 742 // Description: if minio return NoSuchObjectLockConfiguration, don't panic 743 // and return empty response 744 name: "Handle NoLock Config errors", 745 args: args{ 746 ctx: ctx, 747 client: minClient, 748 bucketName: "test", 749 getRetentionFunc: func(_ context.Context, _ string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) { 750 return nil, nil, nil, minio.ErrorResponse{ 751 Code: "ObjectLockConfigurationNotFoundError", 752 Message: "Object Lock configuration does not exist for this bucket", 753 } 754 }, 755 }, 756 expectedResponse: &models.GetBucketRetentionConfig{}, 757 expectedError: nil, 758 }, 759 { 760 name: "Return errors on invalid mode", 761 args: args{ 762 ctx: ctx, 763 client: minClient, 764 bucketName: "test", 765 getRetentionFunc: func(_ context.Context, _ string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) { 766 m := minio.RetentionMode("other") 767 u := minio.Days 768 return &m, swag.Uint(2), &u, nil 769 }, 770 }, 771 expectedResponse: nil, 772 expectedError: errors.New("invalid retention mode"), 773 }, 774 { 775 name: "Return errors on invalid unit", 776 args: args{ 777 ctx: ctx, 778 client: minClient, 779 bucketName: "test", 780 getRetentionFunc: func(_ context.Context, _ string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) { 781 m := minio.Governance 782 u := minio.ValidityUnit("otherUnit") 783 return &m, swag.Uint(2), &u, nil 784 }, 785 }, 786 expectedResponse: nil, 787 expectedError: errors.New("invalid retention unit"), 788 }, 789 } 790 791 for _, tt := range tests { 792 t.Run(tt.name, func(_ *testing.T) { 793 minioGetBucketObjectLockConfigMock = tt.args.getRetentionFunc 794 resp, err := getBucketRetentionConfig(tt.args.ctx, tt.args.client, tt.args.bucketName) 795 796 if tt.expectedError != nil { 797 fmt.Println(t.Name()) 798 assert.Equal(tt.expectedError.Error(), err.Error(), fmt.Sprintf("getBucketRetentionConfig() errors: `%s`, wantErr: `%s`", err, tt.expectedError)) 799 } else { 800 assert.Nil(err, fmt.Sprintf("getBucketRetentionConfig() errors: %v, wantErr: %v", err, tt.expectedError)) 801 if !reflect.DeepEqual(resp, tt.expectedResponse) { 802 t.Errorf("getBucketRetentionConfig() resp: %v, expectedResponse: %v", resp, tt.expectedResponse) 803 return 804 } 805 } 806 }) 807 } 808 } 809 810 func Test_SetBucketVersioning(t *testing.T) { 811 assert := assert.New(t) 812 ctx := context.WithValue(context.Background(), utils.ContextClientIP, "127.0.0.1") 813 errorMsg := "Error Message" 814 minClient := s3ClientMock{} 815 type args struct { 816 ctx context.Context 817 state VersionState 818 excludePrefix []string 819 excludeFolders bool 820 bucketName string 821 client s3ClientMock 822 setVersioningFunc func(ctx context.Context, state string, excludePrefix []string, excludeFolders bool) *probe.Error 823 } 824 tests := []struct { 825 name string 826 args args 827 expectedError error 828 }{ 829 { 830 name: "Set Bucket Version Success", 831 args: args{ 832 ctx: ctx, 833 state: VersionEnable, 834 bucketName: "test", 835 client: minClient, 836 setVersioningFunc: func(_ context.Context, _ string, _ []string, _ bool) *probe.Error { 837 return nil 838 }, 839 }, 840 expectedError: nil, 841 }, 842 { 843 name: "Set Bucket Version with Prefixes Success", 844 args: args{ 845 ctx: ctx, 846 state: VersionEnable, 847 excludePrefix: []string{"prefix1", "prefix2"}, 848 bucketName: "test", 849 client: minClient, 850 setVersioningFunc: func(_ context.Context, _ string, _ []string, _ bool) *probe.Error { 851 return nil 852 }, 853 }, 854 expectedError: nil, 855 }, 856 { 857 name: "Set Bucket Version with Excluded Folders Success", 858 args: args{ 859 ctx: ctx, 860 state: VersionEnable, 861 excludePrefix: []string{"prefix1", "prefix2"}, 862 excludeFolders: true, 863 bucketName: "test", 864 client: minClient, 865 setVersioningFunc: func(_ context.Context, _ string, _ []string, _ bool) *probe.Error { 866 return nil 867 }, 868 }, 869 expectedError: nil, 870 }, 871 { 872 name: "Set Bucket Version Error", 873 args: args{ 874 ctx: ctx, 875 state: VersionEnable, 876 bucketName: "test", 877 client: minClient, 878 setVersioningFunc: func(_ context.Context, _ string, _ []string, _ bool) *probe.Error { 879 return probe.NewError(errors.New(errorMsg)) 880 }, 881 }, 882 expectedError: errors.New(errorMsg), 883 }, 884 } 885 886 for _, tt := range tests { 887 t.Run(tt.name, func(_ *testing.T) { 888 minioSetVersioningMock = tt.args.setVersioningFunc 889 890 err := doSetVersioning(tt.args.ctx, tt.args.client, tt.args.state, tt.args.excludePrefix, tt.args.excludeFolders) 891 892 fmt.Println(t.Name()) 893 fmt.Println("Expected:", tt.expectedError, "Error:", err) 894 895 if tt.expectedError != nil { 896 fmt.Println(t.Name()) 897 assert.Equal(tt.expectedError.Error(), err.Error(), fmt.Sprintf("getBucketRetentionConfig() errors: `%s`, wantErr: `%s`", err, tt.expectedError)) 898 } 899 }) 900 } 901 } 902 903 func mustTags(tagsMap map[string]string) *tags.Tags { 904 tags, _ := tags.NewTags(tagsMap, false) 905 return tags 906 } 907 908 func Test_getAccountBuckets(t *testing.T) { 909 type args struct { 910 ctx context.Context 911 mockBucketList madmin.AccountInfo 912 mockError error 913 } 914 915 // Declaring layout constant 916 const layout = "Jan 2, 2006 at 3:04pm (MST)" 917 // Calling Parse() method with its parameters 918 tm, _ := time.Parse(layout, "Feb 4, 2014 at 6:05pm (PST)") 919 tests := []struct { 920 name string 921 args args 922 want []*models.Bucket 923 wantErr assert.ErrorAssertionFunc 924 }{ 925 { 926 name: "Test list Buckets", 927 args: args{ 928 ctx: context.Background(), 929 mockBucketList: madmin.AccountInfo{ 930 AccountName: "test", 931 Buckets: []madmin.BucketAccessInfo{ 932 {Name: "bucket-1", Created: tm, Size: 1024}, 933 {Name: "bucket-2", Created: tm, Size: 0}, 934 }, 935 Policy: []byte(` 936 { 937 "Version": "2012-10-17", 938 "Statement": [{ 939 "Action": [ 940 "admin:*" 941 ], 942 "Effect": "Allow", 943 "Sid": "" 944 }, 945 { 946 "Action": [ 947 "s3:*" 948 ], 949 "Effect": "Allow", 950 "Resource": [ 951 "arn:aws:s3:::*" 952 ], 953 "Sid": "" 954 } 955 ] 956 }`), 957 }, 958 }, 959 want: []*models.Bucket{ 960 { 961 Name: swag.String("bucket-1"), 962 CreationDate: tm.Format(time.RFC3339), 963 Details: &models.BucketDetails{}, 964 RwAccess: &models.BucketRwAccess{}, 965 Size: 1024, 966 }, 967 { 968 Name: swag.String("bucket-2"), 969 CreationDate: tm.Format(time.RFC3339), 970 Details: &models.BucketDetails{}, 971 RwAccess: &models.BucketRwAccess{}, 972 }, 973 }, 974 wantErr: assert.NoError, 975 }, 976 { 977 name: "Test list Buckets Details", 978 args: args{ 979 ctx: context.Background(), 980 mockBucketList: madmin.AccountInfo{ 981 AccountName: "test", 982 Buckets: []madmin.BucketAccessInfo{ 983 { 984 Name: "bucket-1", Created: tm, Size: 1024, 985 Details: &madmin.BucketDetails{ 986 Versioning: true, 987 VersioningSuspended: false, 988 Locking: false, 989 Replication: false, 990 Tagging: nil, 991 Quota: nil, 992 }, 993 }, 994 {Name: "bucket-2", Created: tm, Size: 0}, 995 }, 996 Policy: []byte(` 997 { 998 "Version": "2012-10-17", 999 "Statement": [{ 1000 "Action": [ 1001 "admin:*" 1002 ], 1003 "Effect": "Allow", 1004 "Sid": "" 1005 }, 1006 { 1007 "Action": [ 1008 "s3:*" 1009 ], 1010 "Effect": "Allow", 1011 "Resource": [ 1012 "arn:aws:s3:::*" 1013 ], 1014 "Sid": "" 1015 } 1016 ] 1017 }`), 1018 }, 1019 }, 1020 want: []*models.Bucket{ 1021 { 1022 Name: swag.String("bucket-1"), 1023 CreationDate: tm.Format(time.RFC3339), 1024 Details: &models.BucketDetails{ 1025 Locking: false, 1026 Quota: nil, 1027 Replication: false, 1028 Tags: nil, 1029 Versioning: true, 1030 VersioningSuspended: false, 1031 }, 1032 RwAccess: &models.BucketRwAccess{}, 1033 Size: 1024, 1034 }, 1035 { 1036 Name: swag.String("bucket-2"), 1037 CreationDate: tm.Format(time.RFC3339), 1038 Details: &models.BucketDetails{}, 1039 RwAccess: &models.BucketRwAccess{}, 1040 }, 1041 }, 1042 wantErr: assert.NoError, 1043 }, 1044 { 1045 name: "Test list Buckets Details Tags", 1046 args: args{ 1047 ctx: context.Background(), 1048 mockBucketList: madmin.AccountInfo{ 1049 AccountName: "test", 1050 Buckets: []madmin.BucketAccessInfo{ 1051 { 1052 Name: "bucket-1", Created: tm, Size: 1024, 1053 Details: &madmin.BucketDetails{ 1054 Versioning: true, 1055 VersioningSuspended: false, 1056 Locking: false, 1057 Replication: false, 1058 Tagging: mustTags(map[string]string{ 1059 "key": "val", 1060 }), 1061 Quota: nil, 1062 }, 1063 }, 1064 {Name: "bucket-2", Created: tm, Size: 0}, 1065 }, 1066 Policy: []byte(` 1067 { 1068 "Version": "2012-10-17", 1069 "Statement": [{ 1070 "Action": [ 1071 "admin:*" 1072 ], 1073 "Effect": "Allow", 1074 "Sid": "" 1075 }, 1076 { 1077 "Action": [ 1078 "s3:*" 1079 ], 1080 "Effect": "Allow", 1081 "Resource": [ 1082 "arn:aws:s3:::*" 1083 ], 1084 "Sid": "" 1085 } 1086 ] 1087 }`), 1088 }, 1089 }, 1090 want: []*models.Bucket{ 1091 { 1092 Name: swag.String("bucket-1"), 1093 CreationDate: tm.Format(time.RFC3339), 1094 Details: &models.BucketDetails{ 1095 Locking: false, 1096 Quota: nil, 1097 Replication: false, 1098 Tags: map[string]string{ 1099 "key": "val", 1100 }, 1101 Versioning: true, 1102 VersioningSuspended: false, 1103 }, 1104 RwAccess: &models.BucketRwAccess{}, 1105 Size: 1024, 1106 }, 1107 { 1108 Name: swag.String("bucket-2"), 1109 CreationDate: tm.Format(time.RFC3339), 1110 Details: &models.BucketDetails{}, 1111 RwAccess: &models.BucketRwAccess{}, 1112 }, 1113 }, 1114 wantErr: assert.NoError, 1115 }, 1116 { 1117 name: "Test list Buckets Details Quota", 1118 args: args{ 1119 ctx: context.Background(), 1120 mockBucketList: madmin.AccountInfo{ 1121 AccountName: "test", 1122 Buckets: []madmin.BucketAccessInfo{ 1123 { 1124 Name: "bucket-1", Created: tm, Size: 1024, 1125 Details: &madmin.BucketDetails{ 1126 Versioning: true, 1127 VersioningSuspended: false, 1128 Locking: false, 1129 Replication: false, 1130 Tagging: nil, 1131 Quota: &madmin.BucketQuota{ 1132 Quota: 10, 1133 Type: madmin.HardQuota, 1134 }, 1135 }, 1136 }, 1137 {Name: "bucket-2", Created: tm, Size: 0}, 1138 }, 1139 Policy: []byte(` 1140 { 1141 "Version": "2012-10-17", 1142 "Statement": [{ 1143 "Action": [ 1144 "admin:*" 1145 ], 1146 "Effect": "Allow", 1147 "Sid": "" 1148 }, 1149 { 1150 "Action": [ 1151 "s3:*" 1152 ], 1153 "Effect": "Allow", 1154 "Resource": [ 1155 "arn:aws:s3:::*" 1156 ], 1157 "Sid": "" 1158 } 1159 ] 1160 }`), 1161 }, 1162 }, 1163 want: []*models.Bucket{ 1164 { 1165 Name: swag.String("bucket-1"), 1166 CreationDate: tm.Format(time.RFC3339), 1167 Details: &models.BucketDetails{ 1168 Locking: false, 1169 Quota: &models.BucketDetailsQuota{ 1170 Quota: 10, 1171 Type: "hard", 1172 }, 1173 Replication: false, 1174 Tags: nil, 1175 Versioning: true, 1176 VersioningSuspended: false, 1177 }, 1178 RwAccess: &models.BucketRwAccess{}, 1179 Size: 1024, 1180 }, 1181 { 1182 Name: swag.String("bucket-2"), 1183 CreationDate: tm.Format(time.RFC3339), 1184 Details: &models.BucketDetails{}, 1185 RwAccess: &models.BucketRwAccess{}, 1186 }, 1187 }, 1188 wantErr: assert.NoError, 1189 }, 1190 { 1191 name: "Test list Buckets Error", 1192 args: args{ 1193 ctx: context.Background(), 1194 mockBucketList: madmin.AccountInfo{}, 1195 mockError: errors.New("some errors"), 1196 }, 1197 want: []*models.Bucket{}, 1198 wantErr: assert.Error, 1199 }, 1200 } 1201 for _, tt := range tests { 1202 t.Run(tt.name, func(_ *testing.T) { 1203 // mock function response from listBucketsWithContext(ctx) 1204 minioAccountInfoMock = func(_ context.Context) (madmin.AccountInfo, error) { 1205 return tt.args.mockBucketList, tt.args.mockError 1206 } 1207 client := AdminClientMock{} 1208 1209 got, err := getAccountBuckets(tt.args.ctx, client) 1210 if !tt.wantErr(t, err, fmt.Sprintf("getAccountBuckets(%v, %v)", tt.args.ctx, client)) { 1211 return 1212 } 1213 assert.EqualValues(t, tt.want, got, "getAccountBuckets(%v, %v)", tt.args.ctx, client) 1214 }) 1215 } 1216 } 1217 1218 func Test_getMaxShareLinkExpirationSeconds(t *testing.T) { 1219 type args struct { 1220 session *models.Principal 1221 } 1222 tests := []struct { 1223 name string 1224 args args 1225 want int64 1226 wantErr bool 1227 preFunc func() 1228 postFunc func() 1229 }{ 1230 { 1231 name: "empty session returns error", 1232 args: args{ 1233 session: nil, 1234 }, 1235 want: 0, 1236 wantErr: true, 1237 }, 1238 { 1239 name: "invalid/expired session returns error", 1240 args: args{ 1241 session: &models.Principal{ 1242 STSAccessKeyID: "", 1243 STSSecretAccessKey: "", 1244 STSSessionToken: "", 1245 }, 1246 }, 1247 want: 0, 1248 wantErr: true, 1249 }, 1250 { 1251 name: "valid session, returns value from env variable", 1252 args: args{ 1253 session: &models.Principal{ 1254 STSAccessKeyID: "VQH975JV49JYDLK7F81G", 1255 STSSecretAccessKey: "zZ2oMQrZwPWGEf1yyHneWFK2JBlGkVjYTJnfw75X", 1256 STSSessionToken: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJWUUg5NzVKVjQ5SllETEs3RjgxRyIsImV4cCI6MTY5Nzc0Mzg1MywicGFyZW50IjoibWluaW9hZG1pbiJ9.tRJVb3gbRFswKyNsxz_Dbw1SHoIQRRgA3xmXpXE4shScCsQXDydc7U_F9QOjL_BQDcgs65ZqWo3N2CIPmWoGDA", 1257 }, 1258 }, 1259 want: 3600, 1260 wantErr: false, 1261 preFunc: func() { 1262 os.Setenv(token.ConsoleSTSDuration, "1h") 1263 }, 1264 postFunc: func() { 1265 os.Unsetenv(token.ConsoleSTSDuration) 1266 }, 1267 }, 1268 } 1269 for _, tt := range tests { 1270 tt := tt 1271 t.Run(tt.name, func(_ *testing.T) { 1272 if tt.preFunc != nil { 1273 tt.preFunc() 1274 } 1275 expTime, err := getMaxShareLinkExpirationSeconds(tt.args.session) 1276 if (err != nil) != tt.wantErr { 1277 t.Errorf("getMaxShareLinkExpirationSeconds() error = %v, wantErr %v", err, tt.wantErr) 1278 return 1279 } 1280 if !reflect.DeepEqual(expTime, tt.want) { 1281 t.Errorf("getMaxShareLinkExpirationSeconds() got = %v, want %v", expTime, tt.want) 1282 } 1283 if tt.postFunc != nil { 1284 tt.postFunc() 1285 } 1286 }) 1287 } 1288 }