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  }