// This file is part of MinIO Console Server // Copyright (c) 2022 MinIO, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package integration import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io" "log" "net/http" "strconv" "strings" "testing" "time" "github.com/stretchr/testify/assert" ) func AddUser(accessKey, secretKey string, groups, policies []string) (*http.Response, error) { /* This is an atomic function to add user and can be reused across different functions. */ client := &http.Client{ Timeout: 3 * time.Second, } requestDataAdd := map[string]interface{}{ "accessKey": accessKey, "secretKey": secretKey, "groups": groups, "policies": policies, } requestDataJSON, _ := json.Marshal(requestDataAdd) requestDataBody := bytes.NewReader(requestDataJSON) request, err := http.NewRequest( "POST", "http://localhost:9090/api/v1/users", requestDataBody) if err != nil { log.Println(err) } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") response, err := client.Do(request) return response, err } func DeleteUser(userName string) (*http.Response, error) { userName = base64.StdEncoding.EncodeToString([]byte(userName)) /* This is an atomic function to delete user and can be reused across different functions. */ client := &http.Client{ Timeout: 3 * time.Second, } request, err := http.NewRequest( "DELETE", "http://localhost:9090/api/v1/user/"+userName, nil) if err != nil { log.Println(err) } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") response, err := client.Do(request) return response, err } func ListUsers(offset, limit string) (*http.Response, error) { /* This is an atomic function to list users. {{baseUrl}}/users?offset=-5480083&limit=-5480083 */ client := &http.Client{ Timeout: 3 * time.Second, } request, err := http.NewRequest( "GET", "http://localhost:9090/api/v1/users?offset="+offset+"&limit="+limit, nil) if err != nil { log.Println(err) } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") response, err := client.Do(request) return response, err } func GetUserInformation(userName string) (*http.Response, error) { userName = base64.StdEncoding.EncodeToString([]byte(userName)) /* Helper function to get user information via API: {{baseUrl}}/user?name=proident velit */ client := &http.Client{ Timeout: 3 * time.Second, } request, err := http.NewRequest( "GET", "http://localhost:9090/api/v1/user/"+userName, nil) if err != nil { log.Println(err) } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") response, err := client.Do(request) return response, err } func UpdateUserInformation(name, status string, groups []string) (*http.Response, error) { name = base64.StdEncoding.EncodeToString([]byte(name)) /* Helper function to update user information: PUT: {{baseUrl}}/user?name=proident velit Body: { "status": "nisi voluptate amet ea", "groups": [ "ipsum eu cupidatat", "aliquip non nulla" ] } */ client := &http.Client{ Timeout: 3 * time.Second, } requestDataAdd := map[string]interface{}{ "status": status, "groups": groups, } requestDataJSON, _ := json.Marshal(requestDataAdd) requestDataBody := bytes.NewReader(requestDataJSON) request, err := http.NewRequest( "PUT", "http://localhost:9090/api/v1/user/"+name, requestDataBody) if err != nil { log.Println(err) } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") response, err := client.Do(request) return response, err } func RemoveUser(name string) (*http.Response, error) { name = base64.StdEncoding.EncodeToString([]byte(name)) /* Helper function to remove user. DELETE: {{baseUrl}}/user?name=proident velit */ client := &http.Client{ Timeout: 3 * time.Second, } request, err := http.NewRequest( "DELETE", "http://localhost:9090/api/v1/user/"+name, nil) if err != nil { log.Println(err) } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") response, err := client.Do(request) return response, err } func UpdateGroupsForAUser(userName string, groups []string) (*http.Response, error) { userName = base64.StdEncoding.EncodeToString([]byte(userName)) /* Helper function to update groups for a user PUT: {{baseUrl}}/user/groups?name=username { "groups":[ "groupone", "grouptwo" ] } */ client := &http.Client{ Timeout: 3 * time.Second, } requestDataAdd := map[string]interface{}{ "groups": groups, } requestDataJSON, _ := json.Marshal(requestDataAdd) requestDataBody := bytes.NewReader(requestDataJSON) request, err := http.NewRequest( "PUT", "http://localhost:9090/api/v1/user/"+userName+"/groups", requestDataBody, ) if err != nil { log.Println(err) } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") response, err := client.Do(request) return response, err } func CreateServiceAccountForUser(userName, policy string) (*http.Response, error) { userName = base64.StdEncoding.EncodeToString([]byte(userName)) /* Helper function to Create Service Account for user POST: api/v1/user/username/service-accounts { "policy": "ad magna" } */ client := &http.Client{ Timeout: 3 * time.Second, } requestDataAdd := map[string]interface{}{ "policy": policy, } requestDataJSON, _ := json.Marshal(requestDataAdd) requestDataBody := bytes.NewReader(requestDataJSON) request, err := http.NewRequest( "POST", "http://localhost:9090/api/v1/user/"+userName+"/service-accounts", requestDataBody, ) if err != nil { log.Println(err) } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") response, err := client.Do(request) return response, err } func CreateServiceAccountForUserWithCredentials(userName, policy, accessKey, secretKey string) (*http.Response, error) { userName = base64.StdEncoding.EncodeToString([]byte(userName)) // Helper function to test "Create Service Account for User With Credentials" end point. client := &http.Client{ Timeout: 3 * time.Second, } requestDataAdd := map[string]interface{}{ "policy": policy, "accessKey": accessKey, "secretKey": secretKey, } requestDataJSON, _ := json.Marshal(requestDataAdd) requestDataBody := bytes.NewReader(requestDataJSON) request, err := http.NewRequest( "POST", "http://localhost:9090/api/v1/user/"+userName+"/service-account-credentials", requestDataBody, ) if err != nil { log.Println(err) } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") response, err := client.Do(request) return response, err } func ReturnsAListOfServiceAccountsForAUser(userName string) (*http.Response, error) { userName = base64.StdEncoding.EncodeToString([]byte(userName)) /* Helper function to return a list of service accounts for a user. GET: {{baseUrl}}/user/:name/service-accounts */ client := &http.Client{ Timeout: 3 * time.Second, } request, err := http.NewRequest( "GET", "http://localhost:9090/api/v1/user/"+userName+"/service-accounts", nil, ) if err != nil { log.Println(err) } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") response, err := client.Do(request) return response, err } func AddGroup(group string, members []string) (*http.Response, error) { /* Helper function to add a group. */ client := &http.Client{ Timeout: 3 * time.Second, } requestDataAdd := map[string]interface{}{ "group": group, "members": members, } requestDataJSON, _ := json.Marshal(requestDataAdd) requestDataBody := bytes.NewReader(requestDataJSON) request, err := http.NewRequest( "POST", "http://localhost:9090/api/v1/groups", requestDataBody, ) if err != nil { log.Println(err) } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") response, err := client.Do(request) return response, err } func UsersGroupsBulk(users, groups []string) (*http.Response, error) { /* Helper function to test Bulk functionality to Add Users to Groups. PUT: {{baseUrl}}/users-groups-bulk { "users": [ "magna id", "enim sit tempor incididunt" ], "groups": [ "nisi est esse", "fugiat eu" ] } */ requestDataAdd := map[string]interface{}{ "users": users, "groups": groups, } requestDataJSON, _ := json.Marshal(requestDataAdd) requestDataBody := bytes.NewReader(requestDataJSON) request, err := http.NewRequest( "PUT", "http://localhost:9090/api/v1/users-groups-bulk", requestDataBody, ) if err != nil { log.Println(err) } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") client := &http.Client{ Timeout: 2 * time.Second, } response, err := client.Do(request) return response, err } func TestAddUser(t *testing.T) { /* This is an API Test to add a user via api/v1/users, the intention is simple, add a user and make sure the response is 201 meaning that the user got added successfully. After test completion, it is expected that user is removed, so other tests like users.ts can run over clean data and we don't collide against it. */ assert := assert.New(t) // With no groups & no policies groups := []string{} policies := []string{} response, err := AddUser("accessKey", "secretKey", groups, policies) if err != nil { log.Println(err) return } if response != nil { fmt.Println("POST StatusCode:", response.StatusCode) assert.Equal(201, response.StatusCode, "Status Code is incorrect") } response, err = DeleteUser("accessKey") if err != nil { log.Println(err) return } if response != nil { fmt.Println("DELETE StatusCode:", response.StatusCode) assert.Equal(204, response.StatusCode, "has to be 204 when delete user") } } func TestListUsers(t *testing.T) { /* This test is intended to list users via API. 1. First, it creates the users 2. Then, it lists the users <------ 200 is expected when listing them. 3. Finally, it deletes the users */ assert := assert.New(t) // With no groups & no policies groups := []string{} policies := []string{} // 1. Create the users numberOfUsers := 5 for i := 1; i < numberOfUsers; i++ { response, err := AddUser( strconv.Itoa(i)+"accessKey"+strconv.Itoa(i), "secretKey"+strconv.Itoa(i), groups, policies) if err != nil { log.Println(err) return } if response != nil { fmt.Println("POST StatusCode:", response.StatusCode) assert.Equal(201, response.StatusCode, "Status Code is incorrect on index: "+strconv.Itoa(i)) } b, err := io.ReadAll(response.Body) if err != nil { log.Fatalln(err) } fmt.Println(string(b)) } // 2. List the users listResponse, listError := ListUsers("-5480083", "-5480083") if listError != nil { log.Fatalln(listError) } if listResponse != nil { fmt.Println("POST StatusCode:", listResponse.StatusCode) assert.Equal(200, listResponse.StatusCode, "TestListUsers(): Status Code is incorrect when listing users") } b, err := io.ReadAll(listResponse.Body) if err != nil { log.Fatalln(err) } fmt.Println(string(b)) // 3. Delete the users for i := 1; i < numberOfUsers; i++ { response, err := DeleteUser( strconv.Itoa(i) + "accessKey" + strconv.Itoa(i)) if err != nil { log.Println(err) return } if response != nil { fmt.Println("DELETE StatusCode:", response.StatusCode) assert.Equal(204, response.StatusCode, "has to be 204 when delete user") } } } func TestGetUserInfo(t *testing.T) { /* Test to get the user information via API. */ // 1. Create the user fmt.Println("TestGetUserInfo(): 1. Create the user") assert := assert.New(t) groups := []string{} policies := []string{} response, err := AddUser("accessKey", "secretKey", groups, policies) if err != nil { log.Println(err) return } if response != nil { fmt.Println("POST StatusCode:", response.StatusCode) assert.Equal(201, response.StatusCode, "Status Code is incorrect") } // 2. Get user information fmt.Println("TestGetUserInfo(): 2. Get user information") response, err = GetUserInformation("accessKey") if err != nil { log.Println(err) assert.Fail("There was an error in the response") return } // 3. Verify user information fmt.Println("TestGetUserInfo(): 3. Verify user information") if response != nil { fmt.Println("POST StatusCode:", response.StatusCode) assert.Equal(200, response.StatusCode, "Status Code is incorrect") } b, err := io.ReadAll(response.Body) if err != nil { log.Fatalln(err) } fmt.Println(string(b)) expected := "{\"accessKey\":\"accessKey\",\"memberOf\":null,\"policy\":[],\"status\":\"enabled\"}\n" obtained := string(b) assert.Equal(expected, obtained, "User Information is wrong") } func TestUpdateUserInfoSuccessfulResponse(t *testing.T) { /* Update User Information Test with Successful Response */ assert := assert.New(t) // 1. Create an active user groups := []string{} policies := []string{} addUserResponse, addUserError := AddUser( "updateuser", "secretKey", groups, policies) if addUserError != nil { log.Println(addUserError) return } if addUserResponse != nil { fmt.Println("StatusCode:", addUserResponse.StatusCode) assert.Equal( 201, addUserResponse.StatusCode, "Status Code is incorrect") } // 2. Deactivate the user // '{"status":"disabled","groups":[]}' updateUserResponse, UpdateUserError := UpdateUserInformation( "updateuser", "disabled", groups) // 3. Verify user got deactivated if UpdateUserError != nil { log.Println(UpdateUserError) return } if updateUserResponse != nil { fmt.Println("StatusCode:", updateUserResponse.StatusCode) assert.Equal( 200, updateUserResponse.StatusCode, "Status Code is incorrect") } b, err := io.ReadAll(updateUserResponse.Body) if err != nil { log.Fatalln(err) } assert.True(strings.Contains(string(b), "disabled")) } func TestUpdateUserInfoGenericErrorResponse(t *testing.T) { /* Update User Information Test with Generic Error Response */ assert := assert.New(t) // 1. Create an active user groups := []string{} policies := []string{} addUserResponse, addUserError := AddUser( "updateusererror", "secretKey", groups, policies) if addUserError != nil { log.Println(addUserError) return } if addUserResponse != nil { fmt.Println("StatusCode:", addUserResponse.StatusCode) assert.Equal( 201, addUserResponse.StatusCode, "Status Code is incorrect") } // 2. Deactivate the user with wrong status updateUserResponse, UpdateUserError := UpdateUserInformation( "updateusererror", "inactive", groups) // 3. Verify user got deactivated if UpdateUserError != nil { log.Println(UpdateUserError) assert.Fail("There was an error while updating user info") return } if updateUserResponse != nil { fmt.Println("StatusCode:", updateUserResponse.StatusCode) assert.Equal( 500, updateUserResponse.StatusCode, "Status Code is incorrect") } b, err := io.ReadAll(updateUserResponse.Body) if err != nil { log.Fatalln(err) } assert.True(strings.Contains(string(b), "status not valid")) } func TestRemoveUserSuccessfulResponse(t *testing.T) { /* To test removing a user from API */ assert := assert.New(t) // 1. Create an active user groups := []string{} policies := []string{} addUserResponse, addUserError := AddUser( "testremoveuser1", "secretKey", groups, policies) if addUserError != nil { log.Println(addUserError) return } if addUserResponse != nil { fmt.Println("StatusCode:", addUserResponse.StatusCode) assert.Equal( 201, addUserResponse.StatusCode, "Status Code is incorrect") } // 2. Remove the user removeUserResponse, removeUserError := RemoveUser("testremoveuser1") if removeUserError != nil { log.Println(removeUserError) return } if removeUserResponse != nil { fmt.Println("StatusCode:", removeUserResponse.StatusCode) assert.Equal( 204, removeUserResponse.StatusCode, "Status Code is incorrect") } // 3. Verify the user got removed getUserInfoResponse, getUserInfoError := GetUserInformation( "testremoveuser1") if getUserInfoError != nil { log.Println(getUserInfoError) assert.Fail("There was an error in the response") return } if getUserInfoResponse != nil { fmt.Println("StatusCode:", getUserInfoResponse.StatusCode) assert.Equal( 404, getUserInfoResponse.StatusCode, "Status Code is incorrect") } finalResponse := inspectHTTPResponse(getUserInfoResponse) fmt.Println(finalResponse) assert.True(strings.Contains( finalResponse, "The specified user does not exist"), finalResponse) } func TestUpdateGroupsForAUser(t *testing.T) { /* To test Update Groups For a User End Point. */ // 1. Create the user numberOfGroups := 3 groupName := "updategroupforausergroup" userName := "updategroupsforauser1" assert := assert.New(t) groups := []string{} policies := []string{} response, err := AddUser(userName, "secretKey", groups, policies) if err != nil { log.Println(err) return } if response != nil { fmt.Println("StatusCode:", response.StatusCode) assert.Equal(201, response.StatusCode, "Status Code is incorrect") } // 2. Update the groups of the created user with newGroups newGroups := make([]string, 3) for i := 0; i < numberOfGroups; i++ { newGroups[i] = groupName + strconv.Itoa(i) } response, err = UpdateGroupsForAUser(userName, newGroups) if err != nil { log.Println(err) return } if response != nil { fmt.Println("StatusCode:", response.StatusCode) assert.Equal(200, response.StatusCode, "Status Code is incorrect") } // 3. Verify the newGroups were updated accordingly getUserInfoResponse, getUserInfoErr := GetUserInformation(userName) if getUserInfoErr != nil { log.Println(getUserInfoErr) assert.Fail("There was an error in the response") return } if getUserInfoResponse != nil { fmt.Println("StatusCode:", getUserInfoResponse.StatusCode) assert.Equal( 200, getUserInfoResponse.StatusCode, "Status Code is incorrect") } finalResponse := inspectHTTPResponse(getUserInfoResponse) for i := 0; i < numberOfGroups; i++ { assert.True(strings.Contains( finalResponse, groupName+strconv.Itoa(i)), finalResponse) } } func TestCreateServiceAccountForUser(t *testing.T) { /* To test creation of service account for a user. */ // Test's variables userName := "testcreateserviceaccountforuser1" assert := assert.New(t) policy := "" // 1. Create the user groups := []string{} policies := []string{} response, err := AddUser(userName, "secretKey", groups, policies) if err != nil { log.Println(err) return } if response != nil { fmt.Println("StatusCode:", response.StatusCode) assert.Equal(201, response.StatusCode, "Status Code is incorrect") } // 2. Create the service account for the user createServiceAccountResponse, createServiceAccountError := CreateServiceAccountForUser( userName, policy, ) if createServiceAccountError != nil { log.Println(createServiceAccountError) assert.Fail("Error in createServiceAccountError") } if createServiceAccountResponse != nil { fmt.Println("StatusCode:", createServiceAccountResponse.StatusCode) assert.Equal( 201, createServiceAccountResponse.StatusCode, inspectHTTPResponse(createServiceAccountResponse), ) } // 3. Verify the service account for the user listOfAccountsResponse, listOfAccountsError := ReturnsAListOfServiceAccountsForAUser(userName) fmt.Println(listOfAccountsResponse, listOfAccountsError) if listOfAccountsError != nil { log.Println(listOfAccountsError) assert.Fail("Error in listOfAccountsError") } finalResponse := inspectHTTPResponse(listOfAccountsResponse) if listOfAccountsResponse != nil { fmt.Println("StatusCode:", listOfAccountsResponse.StatusCode) assert.Equal( 200, listOfAccountsResponse.StatusCode, finalResponse, ) } } func TestUsersGroupsBulk(t *testing.T) { /* To test UsersGroupsBulk End Point */ // Vars assert := assert.New(t) numberOfUsers := 5 numberOfGroups := 1 // var groups = []string{} policies := []string{} username := "testusersgroupbulk" groupName := "testusersgroupsbulkgroupone" members := []string{} users := make([]string, numberOfUsers) groups := make([]string, numberOfGroups) // 1. Create some users for i := 0; i < numberOfUsers; i++ { users[i] = username + strconv.Itoa(i) response, err := AddUser( users[i], "secretKey"+strconv.Itoa(i), []string{}, policies) if err != nil { log.Println(err) return } if response != nil { fmt.Println("POST StatusCode:", response.StatusCode) assert.Equal(201, response.StatusCode, "Status Code is incorrect on index: "+strconv.Itoa(i)) } } // 2. Create a group with no members responseAddGroup, errorAddGroup := AddGroup(groupName, members) if errorAddGroup != nil { log.Println(errorAddGroup) return } finalResponse := inspectHTTPResponse(responseAddGroup) if responseAddGroup != nil { fmt.Println("POST StatusCode:", responseAddGroup.StatusCode) assert.Equal( 201, responseAddGroup.StatusCode, finalResponse, ) } // 3. Add users to the group groups[0] = groupName responseUsersGroupsBulk, errorUsersGroupsBulk := UsersGroupsBulk( users, groups, ) if errorUsersGroupsBulk != nil { log.Println(errorUsersGroupsBulk) return } finalResponse = inspectHTTPResponse(responseUsersGroupsBulk) if responseUsersGroupsBulk != nil { fmt.Println("POST StatusCode:", responseUsersGroupsBulk.StatusCode) assert.Equal( 200, responseUsersGroupsBulk.StatusCode, finalResponse, ) } // 4. Verify users got added to the group for i := 0; i < numberOfUsers; i++ { responseGetUserInfo, errGetUserInfo := GetUserInformation( username + strconv.Itoa(i), ) if errGetUserInfo != nil { log.Println(errGetUserInfo) assert.Fail("There was an error in the response") return } finalResponse = inspectHTTPResponse(responseGetUserInfo) if responseGetUserInfo != nil { assert.Equal(200, responseGetUserInfo.StatusCode, finalResponse) } // Make sure the user belongs to the created group assert.True(strings.Contains(finalResponse, groupName)) } } func Test_GetUserPolicyAPI(t *testing.T) { assert := assert.New(t) // 1. Create an active user with valid policy groups := []string{} policies := []string{"readwrite"} addUserResponse, addUserError := AddUser( "getpolicyuser", "secretKey", groups, policies) if addUserError != nil { log.Println(addUserError) return } if addUserResponse != nil { fmt.Println("StatusCode:", addUserResponse.StatusCode) assert.Equal( 201, addUserResponse.StatusCode, "Status Code is incorrect") } type args struct { api string } tests := []struct { name string args args expectedStatus int expectedError error }{ { name: "Get User Policies", args: args{ api: "/user/policy", }, expectedStatus: 200, expectedError: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { client := &http.Client{ Timeout: 3 * time.Second, } request, err := http.NewRequest( "GET", fmt.Sprintf("http://localhost:9090/api/v1%s", tt.args.api), nil) if err != nil { log.Println(err) return } request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) request.Header.Add("Content-Type", "application/json") response, err := client.Do(request) if err != nil { log.Println(err) return } if response != nil { assert.Equal(tt.expectedStatus, response.StatusCode, tt.name+" Failed") } }) } }