diff --git a/inttests/sso_user_test.go b/inttests/sso_user_test.go new file mode 100644 index 0000000..b0db84f --- /dev/null +++ b/inttests/sso_user_test.go @@ -0,0 +1,51 @@ +// Copyright © 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package inttests + +import ( + "testing" + + types "github.com/dell/goscaleio/types/v1" + "github.com/stretchr/testify/assert" +) + +func TestCreateAndDeleteSSOUser(t *testing.T) { + details, err := C.CreateSSOUser(&types.SSOUserCreateParam{ + UserName: "IntegrationTestSSOUser", + Role: "Monitor", + Password: "Ssouser123!", + Type: "Local", + }) + assert.Nil(t, err) + assert.NotEmpty(t, details) + + details, err = C.GetSSOUser(details.ID) + assert.Nil(t, err) + assert.NotEmpty(t, details) + + details1, err := C.GetSSOUserByFilters("username", "IntegrationTestSSOUser") + assert.Nil(t, err) + assert.NotEmpty(t, details1) + + details, err = C.ModifySSOUser(details.ID, &types.SSOUserModifyParam{ + Role: "Technician", + }) + assert.Nil(t, err) + assert.NotEmpty(t, details) + + err = C.ResetSSOUserPassword(details.ID, &types.SSOUserModifyParam{Password: "Ssouser1234#"}) + assert.Nil(t, err) + + err = C.DeleteSSOUser(details.ID) + assert.Nil(t, err) +} diff --git a/sso_user.go b/sso_user.go new file mode 100644 index 0000000..7869ef4 --- /dev/null +++ b/sso_user.go @@ -0,0 +1,86 @@ +// Copyright © 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goscaleio + +import ( + "fmt" + "net/http" + "net/url" + + types "github.com/dell/goscaleio/types/v1" +) + +// GetSSOUser retrieves the details of an SSO user by their ID. +func (c *Client) GetSSOUser(userID string) (*types.SSOUserDetails, error) { + path := fmt.Sprintf("/rest/v1/users/%s", userID) + user := &types.SSOUserDetails{} + err := c.getJSONWithRetry(http.MethodGet, path, nil, &user) + + if err != nil { + return nil, err + } + + return user, nil +} + +// GetSSOUserByFilters retrieves the details of an SSO user by filter. +func (c *Client) GetSSOUserByFilters(key string, value string) (*types.SSOUserList, error) { + encodedValue := url.QueryEscape(value) + path := `/rest/v1/users?filter=` + key + `%20eq%20%22` + encodedValue + `%22` + users := &types.SSOUserList{} + err := c.getJSONWithRetry(http.MethodGet, path, nil, &users) + if err != nil { + return nil, err + } + return users, nil +} + +// CreateSSOUser creates a new SSO user with the given parameters. +func (c *Client) CreateSSOUser(userParam *types.SSOUserCreateParam) (*types.SSOUserDetails, error) { + userResp := &types.SSOUserDetails{} + err := c.getJSONWithRetry(http.MethodPost, "/rest/v1/users", userParam, &userResp) + if err != nil { + return nil, err + } + return userResp, nil +} + +// ModifySSOUser modifies the details of an SSO user by their ID. +func (c *Client) ModifySSOUser(userID string, userParam *types.SSOUserModifyParam) (*types.SSOUserDetails, error) { + path := fmt.Sprintf("/rest/v1/users/%s", userID) + err := c.getJSONWithRetry(http.MethodPatch, path, userParam, nil) + if err != nil { + return nil, err + } + return c.GetSSOUser(userID) +} + +// ResetSSOUserPassword resets the password of an SSO user by their ID. +func (c *Client) ResetSSOUserPassword(userID string, userParam *types.SSOUserModifyParam) error { + path := fmt.Sprintf("/rest/v1/users/%s/reset-password", userID) + err := c.getJSONWithRetry(http.MethodPost, path, userParam, nil) + if err != nil { + return err + } + return nil +} + +// DeleteSSOUser deletes an SSO user by their ID. +func (c *Client) DeleteSSOUser(userID string) error { + path := fmt.Sprintf("/rest/v1/users/%s", userID) + err := c.getJSONWithRetry(http.MethodDelete, path, nil, nil) + if err != nil { + return err + } + return nil +} diff --git a/sso_user_test.go b/sso_user_test.go new file mode 100644 index 0000000..1b33636 --- /dev/null +++ b/sso_user_test.go @@ -0,0 +1,340 @@ +// Copyright © 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goscaleio + +import ( + "errors" + "fmt" + "math" + "net/http" + "net/http/httptest" + "testing" + + types "github.com/dell/goscaleio/types/v1" + "github.com/stretchr/testify/assert" +) + +func TestCreateSSOUser(t *testing.T) { + type testCase struct { + user types.SSOUserCreateParam + expected error + } + + cases := []testCase{ + { + user: types.SSOUserCreateParam{ + UserName: "testUser", + Role: "Monitor", + Password: "default", + Type: "Local", + }, + expected: nil, + }, + { + user: types.SSOUserCreateParam{ + UserName: "admin", + Role: "Monitor", + Password: "default", + Type: "Local", + }, + expected: errors.New("Invalid enum value"), + }, + } + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + _, err2 := client.CreateSSOUser(&tc.user) + if err2 != nil { + if tc.expected == nil { + t.Errorf("Creating user did not work as expected, \n\tgot: %s \n\twant: %v", err2, tc.expected) + } else { + if err2.Error() != tc.expected.Error() { + t.Errorf("Creating user did not work as expected, \n\tgot: %s \n\twant: %s", err2, tc.expected) + } + } + } + }) + } +} + +func TestGetSSOUser(t *testing.T) { + type testCase struct { + id string + expected error + } + cases := []testCase{ + { + id: "eeb2dec800000001", + expected: nil, + }, + { + id: "123", + expected: errors.New("error getting user details"), + }, + } + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + _, err2 := client.GetSSOUser(tc.id) + if err2 != nil { + if tc.expected == nil { + t.Errorf("Getting user details did not work as expected, \n\tgot: %s \n\twant: %v", err2, tc.expected) + } else { + if err2.Error() != tc.expected.Error() { + t.Errorf("Getting user details did not work as expected, \n\tgot: %s \n\twant: %s", err2, tc.expected) + } + } + } + }) + } +} + +func TestGetSSOUserByFilters(t *testing.T) { + type testCase struct { + username string + expected error + } + cases := []testCase{ + { + username: "admin", + expected: nil, + }, + } + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + _, err2 := client.GetSSOUserByFilters(tc.username, "admin") + if err2 != nil { + if tc.expected == nil { + t.Errorf("Getting user details did not work as expected, \n\tgot: %s \n\twant: %v", err2, tc.expected) + } else { + if err2.Error() != tc.expected.Error() { + t.Errorf("Getting user details did not work as expected, \n\tgot: %s \n\twant: %s", err2, tc.expected) + } + } + } + }) + } +} + +func TestModifySSOUser(t *testing.T) { + type testCase struct { + user types.SSOUserModifyParam + id string + expected error + } + cases := []testCase{ + { + user: types.SSOUserModifyParam{ + Role: "Monitor", + }, + id: "eeb2dec800000001", + expected: nil, + }, + { + user: types.SSOUserModifyParam{ + Role: "Monitor1", + }, + id: "eeb2dec800000001", + expected: errors.New("Invalid enum value"), + }, + } + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + _, err2 := client.ModifySSOUser(tc.id, &tc.user) + if err2 != nil { + if tc.expected == nil { + t.Errorf("Modifying user did not work as expected, \n\tgot: %s \n\twant: %v", err2, tc.expected) + } else { + if err2.Error() != tc.expected.Error() { + t.Errorf("Modifying user did not work as expected, \n\tgot: %s \n\twant: %s", err2, tc.expected) + } + } + } + }) + } +} + +func TestResetSSOUserPassword(t *testing.T) { + type testCase struct { + user types.SSOUserModifyParam + id string + expected error + } + cases := []testCase{ + { + user: types.SSOUserModifyParam{ + Password: "default", + }, + id: "eeb2dec800000001", + expected: nil, + }, + } + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + err2 := client.ResetSSOUserPassword(tc.id, &tc.user) + if err2 != nil { + if tc.expected == nil { + t.Errorf("Resetting user password did not work as expected, \n\tgot: %s \n\twant: %v", err2, tc.expected) + } else { + if err2.Error() != tc.expected.Error() { + t.Errorf("Resetting user password did not work as expected, \n\tgot: %s \n\twant: %s", err2, tc.expected) + } + } + } + }) + } +} + +func TestDeleteSSOUser(t *testing.T) { + type testCase struct { + id string + expected error + } + cases := []testCase{ + { + id: "eeb2dec800000001", + expected: nil, + }, + { + id: "eeb2dec800000005", + expected: errors.New("HTTP 404 Not Found"), + }, + } + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + err2 := client.DeleteSSOUser(tc.id) + if err2 != nil { + if tc.expected == nil { + t.Errorf("Deleting user did not work as expected, \n\tgot: %s \n\twant: %v", err2, tc.expected) + } else { + if err2.Error() != tc.expected.Error() { + t.Errorf("Deleting user did not work as expected, \n\tgot: %s \n\twant: %s", err2, tc.expected) + } + } + } + }) + } +} + +func TestGetSSOUserNegative(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintln(w, `{"error":"User 93634330-6ffd-4d17-a22a-d3ec701e73d4 not found"}`) + })) + defer svr.Close() + + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + details, err := client.GetSSOUser("93634330-6ffd-4d17-a22a-d3ec701e73d4") + assert.Nil(t, details) + assert.NotNil(t, err) +} + +func TestDeleteSSOUserNegative(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintln(w, `{"error":"User 93634330-6ffd-4d17-a22a-d3ec701e73d4 not found"}`) + })) + defer svr.Close() + + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + err = client.DeleteSSOUser("93634330-6ffd-4d17-a22a-d3ec701e73d4") + assert.NotNil(t, err) +} + +func TestCreateSSOUserNegative(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(w, `{"error":"Invalid enum value"}`) + })) + defer svr.Close() + + user := &types.SSOUserCreateParam{ + UserName: "testUser", + Role: "Monitor1", + Password: "default", + Type: "Local", + } + + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + _, err = client.CreateSSOUser(user) + assert.NotNil(t, err) +} diff --git a/types/v1/ssoUserTypes.go b/types/v1/ssoUserTypes.go new file mode 100644 index 0000000..070270d --- /dev/null +++ b/types/v1/ssoUserTypes.go @@ -0,0 +1,61 @@ +// Copyright © 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goscaleio + +// SSOUserDetails represents the details of an SSO user. +type SSOUserDetails struct { + ID string `json:"id"` + Username string `json:"username"` + CreatedTimestamp string `json:"created_timestamp"` + IsEnabled bool `json:"is_enabled"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + EmailAddress string `json:"email_address"` + IsBuiltin bool `json:"is_builtin"` + Type string `json:"type"` + Permission Permission `json:"permission"` +} + +// SSOUserList represents the details of an SSO users. +type SSOUserList struct { + SSOUsers []SSOUserDetails `json:"users"` +} + +// Permission represents a permission that can be granted to an SSO user. +type Permission struct { + Role string `json:"role"` + Scopes []Scope `json:"scopes"` +} + +// Scope represents a scope that can be granted to an SSO user. +type Scope struct { + ScopeID string `json:"scope_id"` + ScopeType string `json:"scope_type"` +} + +// SSOUserCreateParam represents the parameters for creating an SSO user. +type SSOUserCreateParam struct { + UserName string `json:"username"` + Role string `json:"role"` + Password string `json:"password"` + Type string `json:"type"` + IsEnabled bool `json:"is_enabled,omitempty"` +} + +// SSOUserModifyParam represents the parameters for modifying an SSO user. +type SSOUserModifyParam struct { + UserName string `json:"username,omitempty"` + Role string `json:"role,omitempty"` + Password string `json:"password,omitempty"` + IsEnabled bool `json:"is_enabled,omitempty"` +}