github.com/minio/console@v1.4.1/api/admin_users_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 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "strings" 25 "testing" 26 27 "github.com/minio/madmin-go/v3" 28 iampolicy "github.com/minio/pkg/v3/policy" 29 asrt "github.com/stretchr/testify/assert" 30 ) 31 32 func TestListUsers(t *testing.T) { 33 assert := asrt.New(t) 34 adminClient := AdminClientMock{} 35 ctx, cancel := context.WithCancel(context.Background()) 36 defer cancel() 37 // Test-1 : listUsers() Get response from minio client with two users and return the same number on listUsers() 38 // mock minIO client 39 mockUserMap := map[string]madmin.UserInfo{ 40 "ABCDEFGHI": { 41 SecretKey: "", 42 PolicyName: "ABCDEFGHI-policy", 43 Status: "enabled", 44 MemberOf: []string{"group1", "group2"}, 45 }, 46 "ZBCDEFGHI": { 47 SecretKey: "", 48 PolicyName: "ZBCDEFGHI-policy", 49 Status: "enabled", 50 MemberOf: []string{"group1", "group2"}, 51 }, 52 } 53 54 // mock function response from listUsersWithContext(ctx) 55 minioListUsersMock = func() (map[string]madmin.UserInfo, error) { 56 return mockUserMap, nil 57 } 58 59 // get list users response this response should have Name, CreationDate, Size and Access 60 // as part of of each user 61 function := "listUsers()" 62 userMap, err := listUsers(ctx, adminClient) 63 if err != nil { 64 t.Errorf("Failed on %s:, error occurred: %s", function, err.Error()) 65 } 66 // verify length of users is correct 67 assert.Equal(len(mockUserMap), len(userMap), fmt.Sprintf("Failed on %s: length of user's lists is not the same", function)) 68 69 for _, b := range userMap { 70 assert.Contains(mockUserMap, b.AccessKey) 71 assert.Equal(string(mockUserMap[b.AccessKey].Status), b.Status) 72 assert.Equal(mockUserMap[b.AccessKey].PolicyName, strings.Join(b.Policy, ",")) 73 assert.ElementsMatch(mockUserMap[b.AccessKey].MemberOf, []string{"group1", "group2"}) 74 } 75 76 // Test-2 : listUsers() Return and see that the error is handled correctly and returned 77 minioListUsersMock = func() (map[string]madmin.UserInfo, error) { 78 return nil, errors.New("error") 79 } 80 _, err = listUsers(ctx, adminClient) 81 if assert.Error(err) { 82 assert.Equal("error", err.Error()) 83 } 84 } 85 86 func TestAddUser(t *testing.T) { 87 assert := asrt.New(t) 88 adminClient := AdminClientMock{} 89 ctx, cancel := context.WithCancel(context.Background()) 90 defer cancel() 91 // Test-1: valid case of adding a user with a proper access key 92 accessKey := "ABCDEFGHI" 93 secretKey := "ABCDEFGHIABCDEFGHI" 94 groups := []string{"group1", "group2", "group3"} 95 policies := []string{} 96 emptyGroupTest := []string{} 97 mockResponse := &madmin.UserInfo{ 98 MemberOf: []string{"group1", "group2", "gropup3"}, 99 PolicyName: "", 100 Status: "enabled", 101 SecretKey: "", 102 } 103 104 // mock function response from addUser() return no error 105 minioAddUserMock = func(_, _ string) error { 106 return nil 107 } 108 109 minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) { 110 return *mockResponse, nil 111 } 112 113 minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error { 114 return nil 115 } 116 // Test-1: Add a user 117 function := "addUser()" 118 user, err := addUser(ctx, adminClient, &accessKey, &secretKey, groups, policies) 119 if err != nil { 120 t.Errorf("Failed on %s:, error occurred: %s", function, err.Error()) 121 } 122 // no error should have been returned 123 assert.Nil(err, "Error is not null") 124 // the same access key should be in the model users 125 assert.Equal(user.AccessKey, accessKey) 126 127 // Test-2 Add a user with empty groups list 128 user, err = addUser(ctx, adminClient, &accessKey, &secretKey, emptyGroupTest, policies) 129 // no error should have been returned 130 assert.Nil(err, "Error is not null") 131 // the same access key should be in the model users 132 assert.Equal(user.AccessKey, accessKey) 133 134 // Test-3: valid case 135 accessKey = "AB" 136 secretKey = "ABCDEFGHIABCDEFGHI" 137 // mock function response from addUser() return no error 138 minioAddUserMock = func(_, _ string) error { 139 return errors.New("error") 140 } 141 142 user, err = addUser(ctx, adminClient, &accessKey, &secretKey, groups, policies) 143 144 // no error should have been returned 145 assert.Nil(user, "User is not null") 146 assert.NotNil(err, "An error should have been returned") 147 148 if assert.Error(err) { 149 assert.Equal("error", err.Error()) 150 } 151 152 // Test-4: add groups function returns an error 153 minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error { 154 return errors.New("error") 155 } 156 157 user, err = addUser(ctx, adminClient, &accessKey, &secretKey, groups, policies) 158 159 // no error should have been returned 160 assert.Nil(user, "User is not null") 161 assert.NotNil(err, "An error should have been returned") 162 163 if assert.Error(err) { 164 assert.Equal("error", err.Error()) 165 } 166 } 167 168 func TestRemoveUser(t *testing.T) { 169 assert := asrt.New(t) 170 // mock minIO client 171 adminClient := AdminClientMock{} 172 ctx, cancel := context.WithCancel(context.Background()) 173 defer cancel() 174 function := "removeUser()" 175 176 // Test-1: removeUser() delete a user 177 // mock function response from removeUser(accessKey) 178 minioRemoveUserMock = func(_ string) error { 179 return nil 180 } 181 182 if err := removeUser(ctx, adminClient, "ABCDEFGHI"); err != nil { 183 t.Errorf("Failed on %s:, error occurred: %s", function, err.Error()) 184 } 185 186 // Test-2: removeUser() make sure errors are handled correctly when error on DeleteUser() 187 // mock function response from removeUser(accessKey) 188 minioRemoveUserMock = func(_ string) error { 189 return errors.New("error") 190 } 191 192 if err := removeUser(ctx, adminClient, "notexistentuser"); assert.Error(err) { 193 assert.Equal("error", err.Error()) 194 } 195 } 196 197 func TestUserGroups(t *testing.T) { 198 assert := asrt.New(t) 199 // mock minIO client 200 adminClient := AdminClientMock{} 201 ctx, cancel := context.WithCancel(context.Background()) 202 defer cancel() 203 204 function := "updateUserGroups()" 205 mockUserGroups := []string{"group1", "group2", "group3"} 206 mockUserName := "testUser" 207 mockResponse := &madmin.UserInfo{ 208 MemberOf: []string{"group1", "group2", "gropup3"}, 209 PolicyName: "", 210 Status: "enabled", 211 SecretKey: mockUserName, 212 } 213 mockEmptyResponse := &madmin.UserInfo{ 214 MemberOf: nil, 215 PolicyName: "", 216 Status: "", 217 SecretKey: "", 218 } 219 220 // Test-1: updateUserGroups() updates the groups for a user 221 // mock function response from updateUserGroups(accessKey, groupsToAssign) 222 223 minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) { 224 return *mockResponse, nil 225 } 226 227 minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error { 228 return nil 229 } 230 231 if _, err := updateUserGroups(ctx, adminClient, mockUserName, mockUserGroups); err != nil { 232 t.Errorf("Failed on %s:, error occurred: %s", function, err.Error()) 233 } 234 235 // Test-2: updateUserGroups() make sure errors are handled correctly when error on UpdateGroupMembersMock() 236 // mock function response from removeUser(accessKey) 237 238 minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error { 239 return errors.New("error") 240 } 241 242 if _, err := updateUserGroups(ctx, adminClient, mockUserName, mockUserGroups); assert.Error(err) { 243 assert.Equal("there was an error updating the groups", err.Error()) 244 } 245 246 // Test-3: updateUserGroups() make sure we return the correct error when getUserInfo returns error 247 minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) { 248 return *mockEmptyResponse, errors.New("error getting user ") 249 } 250 251 minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error { 252 return nil 253 } 254 255 if _, err := updateUserGroups(ctx, adminClient, mockUserName, mockUserGroups); assert.Error(err) { 256 assert.Equal("error getting user ", err.Error()) 257 } 258 } 259 260 func TestGetUserInfo(t *testing.T) { 261 assert := asrt.New(t) 262 adminClient := AdminClientMock{} 263 ctx, cancel := context.WithCancel(context.Background()) 264 defer cancel() 265 266 // Test-1 : getUserInfo() get user info 267 userName := "userNameTest" 268 mockResponse := &madmin.UserInfo{ 269 SecretKey: userName, 270 PolicyName: "", 271 MemberOf: []string{"group1", "group2", "group3"}, 272 Status: "enabled", 273 } 274 emptyMockResponse := &madmin.UserInfo{ 275 SecretKey: "", 276 PolicyName: "", 277 Status: "", 278 MemberOf: nil, 279 } 280 281 // mock function response from getUserInfo() 282 minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) { 283 return *mockResponse, nil 284 } 285 function := "getUserInfo()" 286 info, err := getUserInfo(ctx, adminClient, userName) 287 if err != nil { 288 t.Errorf("Failed on %s:, error occurred: %s", function, err.Error()) 289 } 290 291 assert.Equal(userName, info.SecretKey) 292 assert.Equal("", info.PolicyName) 293 assert.ElementsMatch([]string{"group1", "group2", "group3"}, info.MemberOf) 294 assert.Equal(mockResponse.Status, info.Status) 295 296 // Test-2 : getUserInfo() Return error and see that the error is handled correctly and returned 297 minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) { 298 return *emptyMockResponse, errors.New("error") 299 } 300 _, err = getUserInfo(ctx, adminClient, userName) 301 if assert.Error(err) { 302 assert.Equal("error", err.Error()) 303 } 304 } 305 306 func TestSetUserStatus(t *testing.T) { 307 assert := asrt.New(t) 308 adminClient := AdminClientMock{} 309 function := "setUserStatus()" 310 userName := "userName123" 311 ctx, cancel := context.WithCancel(context.Background()) 312 defer cancel() 313 314 // Test-1: setUserStatus() update valid disabled status 315 expectedStatus := "disabled" 316 minioSetUserStatusMock = func(_ string, _ madmin.AccountStatus) error { 317 return nil 318 } 319 if err := setUserStatus(ctx, adminClient, userName, expectedStatus); err != nil { 320 t.Errorf("Failed on %s:, error occurred: %s", function, err.Error()) 321 } 322 // Test-2: setUserStatus() update valid enabled status 323 expectedStatus = "enabled" 324 minioSetUserStatusMock = func(_ string, _ madmin.AccountStatus) error { 325 return nil 326 } 327 if err := setUserStatus(ctx, adminClient, userName, expectedStatus); err != nil { 328 t.Errorf("Failed on %s:, error occurred: %s", function, err.Error()) 329 } 330 // Test-3: setUserStatus() update invalid status, should send error 331 expectedStatus = "invalid" 332 minioSetUserStatusMock = func(_ string, _ madmin.AccountStatus) error { 333 return nil 334 } 335 if err := setUserStatus(ctx, adminClient, userName, expectedStatus); assert.Error(err) { 336 assert.Equal("status not valid", err.Error()) 337 } 338 // Test-4: setUserStatus() handler error correctly 339 expectedStatus = "enabled" 340 minioSetUserStatusMock = func(_ string, _ madmin.AccountStatus) error { 341 return errors.New("error") 342 } 343 if err := setUserStatus(ctx, adminClient, userName, expectedStatus); assert.Error(err) { 344 assert.Equal("error", err.Error()) 345 } 346 } 347 348 func TestUserGroupsBulk(t *testing.T) { 349 assert := asrt.New(t) 350 // mock minIO client 351 adminClient := AdminClientMock{} 352 ctx, cancel := context.WithCancel(context.Background()) 353 defer cancel() 354 355 function := "updateUserGroups()" 356 mockUserGroups := []string{"group1", "group2", "group3"} 357 mockUsers := []string{"testUser", "testUser2"} 358 359 // Test-1: addUsersListToGroups() updates the groups for a users list 360 // mock function response from updateUserGroups(accessKey, groupsToAssign) 361 minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error { 362 return nil 363 } 364 365 if err := addUsersListToGroups(ctx, adminClient, mockUsers, mockUserGroups); err != nil { 366 t.Errorf("Failed on %s:, error occurred: %s", function, err.Error()) 367 } 368 369 // Test-2: addUsersListToGroups() make sure errors are handled correctly when error on updateGroupMembers() 370 // mock function response from removeUser(accessKey) 371 minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error { 372 return errors.New("error") 373 } 374 375 if err := addUsersListToGroups(ctx, adminClient, mockUsers, mockUserGroups); assert.Error(err) { 376 assert.Equal("error in users-groups assignation: \"error,error,error\"", err.Error()) 377 } 378 } 379 380 func TestListUsersWithAccessToBucket(t *testing.T) { 381 assert := asrt.New(t) 382 ctx, cancel := context.WithCancel(context.Background()) 383 defer cancel() 384 adminClient := AdminClientMock{} 385 user1 := madmin.UserInfo{ 386 SecretKey: "testtest", 387 PolicyName: "consoleAdmin,testPolicy,redundantPolicy", 388 Status: "enabled", 389 MemberOf: []string{"group1"}, 390 } 391 user2 := madmin.UserInfo{ 392 SecretKey: "testtest", 393 PolicyName: "testPolicy, otherPolicy", 394 Status: "enabled", 395 MemberOf: []string{"group1"}, 396 } 397 mockUsers := map[string]madmin.UserInfo{"testuser1": user1, "testuser2": user2} 398 minioListUsersMock = func() (map[string]madmin.UserInfo, error) { 399 return mockUsers, nil 400 } 401 policyMap := map[string]string{ 402 "consoleAdmin": `{ 403 "Version": "2012-10-17", 404 "Statement": [ 405 { 406 "Effect": "Allow", 407 "Action": [ 408 "admin:*" 409 ] 410 }, 411 { 412 "Effect": "Allow", 413 "Action": [ 414 "s3:*" 415 ], 416 "Resource": [ 417 "arn:aws:s3:::*" 418 ] 419 } 420 ] 421 }`, 422 "testPolicy": `{ 423 "Version": "2012-10-17", 424 "Statement": [ 425 { 426 "Effect": "Deny", 427 "Action": [ 428 "s3:*" 429 ], 430 "Resource": [ 431 "arn:aws:s3:::bucket1" 432 ] 433 } 434 ] 435 }`, 436 "otherPolicy": `{ 437 "Version": "2012-10-17", 438 "Statement": [ 439 { 440 "Effect": "Allow", 441 "Action": [ 442 "s3:*" 443 ], 444 "Resource": [ 445 "arn:aws:s3:::bucket2" 446 ] 447 } 448 ] 449 }`, 450 "thirdPolicy": `{ 451 "Version": "2012-10-17", 452 "Statement": [ 453 { 454 "Effect": "Allow", 455 "Action": [ 456 "s3:*" 457 ], 458 "Resource": [ 459 "arn:aws:s3:::bucket3" 460 ] 461 } 462 ] 463 }`, "RedundantPolicy": `{ 464 "Version": "2012-10-17", 465 "Statement": [ 466 { 467 "Effect": "Allow", 468 "Action": [ 469 "s3:*" 470 ], 471 "Resource": [ 472 "arn:aws:s3:::bucket1" 473 ] 474 } 475 ] 476 }`, 477 } 478 minioGetPolicyMock = func(name string) (*iampolicy.Policy, error) { 479 iamp, err := iampolicy.ParseConfig(bytes.NewReader([]byte(policyMap[name]))) 480 if err != nil { 481 return nil, err 482 } 483 return iamp, nil 484 } 485 minioListGroupsMock = func() ([]string, error) { 486 return []string{"group1"}, nil 487 } 488 minioGetGroupDescriptionMock = func(name string) (*madmin.GroupDesc, error) { 489 if name == "group1" { 490 mockResponse := &madmin.GroupDesc{ 491 Name: "group1", 492 Policy: "thirdPolicy", 493 Members: []string{"testuser1", "testuser2"}, 494 Status: "enabled", 495 } 496 return mockResponse, nil 497 } 498 return nil, ErrDefault 499 } 500 type args struct { 501 bucket string 502 } 503 tests := []struct { 504 name string 505 args args 506 want []string 507 }{ 508 { 509 name: "Test1", 510 args: args{bucket: "bucket0"}, 511 want: []string{"testuser1"}, 512 }, 513 { 514 name: "Test2", 515 args: args{bucket: "bucket1"}, 516 want: []string(nil), 517 }, 518 { 519 name: "Test3", 520 args: args{bucket: "bucket2"}, 521 want: []string{"testuser1", "testuser2"}, 522 }, 523 { 524 name: "Test4", 525 args: args{bucket: "bucket3"}, 526 want: []string{"testuser1", "testuser2"}, 527 }, 528 } 529 for _, tt := range tests { 530 t.Run(tt.name, func(_ *testing.T) { 531 got, _ := listUsersWithAccessToBucket(ctx, adminClient, tt.args.bucket) 532 assert.Equal(got, tt.want) 533 }) 534 } 535 }