From 7f6857c777ebea2aa72afefa798f08b1ddb4d04a Mon Sep 17 00:00:00 2001 From: Shendge Date: Thu, 22 Feb 2024 17:09:06 +0530 Subject: [PATCH 1/5] adding sso user functionalities for 4.5 --- inttests/sso_user_test.go | 22 +++ sso_user.go | 68 +++++++++ sso_user_test.go | 291 ++++++++++++++++++++++++++++++++++++++ types/v1/ssoUserTypes.go | 56 ++++++++ 4 files changed, 437 insertions(+) create mode 100644 inttests/sso_user_test.go create mode 100644 sso_user.go create mode 100644 sso_user_test.go create mode 100644 types/v1/ssoUserTypes.go diff --git a/inttests/sso_user_test.go b/inttests/sso_user_test.go new file mode 100644 index 0000000..349e7fb --- /dev/null +++ b/inttests/sso_user_test.go @@ -0,0 +1,22 @@ +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) + + 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..ade89ba --- /dev/null +++ b/sso_user.go @@ -0,0 +1,68 @@ +// 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" + + types "github.com/dell/goscaleio/types/v1" +) + +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 +} + +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 +} + +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) +} + +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 +} + +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..2331f2f --- /dev/null +++ b/sso_user_test.go @@ -0,0 +1,291 @@ +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 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..e33f625 --- /dev/null +++ b/types/v1/ssoUserTypes.go @@ -0,0 +1,56 @@ +// 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"` +} + +// 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"` +} From e42b20005cf4b75cd7293d2469556a564b932ff2 Mon Sep 17 00:00:00 2001 From: Shendge Date: Thu, 22 Feb 2024 17:16:11 +0530 Subject: [PATCH 2/5] addressing linting issues --- sso_user.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sso_user.go b/sso_user.go index ade89ba..f067dbd 100644 --- a/sso_user.go +++ b/sso_user.go @@ -19,6 +19,7 @@ import ( 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{} @@ -31,6 +32,7 @@ func (c *Client) GetSSOUser(userID string) (*types.SSOUserDetails, error) { return user, 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) @@ -40,6 +42,7 @@ func (c *Client) CreateSSOUser(userParam *types.SSOUserCreateParam) (*types.SSOU 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) @@ -49,6 +52,7 @@ func (c *Client) ModifySSOUser(userID string, userParam *types.SSOUserModifyPara 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) @@ -58,6 +62,7 @@ func (c *Client) ResetSSOUserPassword(userID string, userParam *types.SSOUserMod 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) From 1c5ccb80a17b2a11fbfa8b6ff7f074d136500df1 Mon Sep 17 00:00:00 2001 From: Shendge Date: Fri, 23 Feb 2024 15:46:09 +0530 Subject: [PATCH 3/5] added filter for sso user --- inttests/sso_user_test.go | 17 +++++++++++++++++ sso_user.go | 12 ++++++++++++ sso_user_test.go | 37 +++++++++++++++++++++++++++++++++++++ types/v1/ssoUserTypes.go | 4 ++++ 4 files changed, 70 insertions(+) diff --git a/inttests/sso_user_test.go b/inttests/sso_user_test.go index 349e7fb..d1d7255 100644 --- a/inttests/sso_user_test.go +++ b/inttests/sso_user_test.go @@ -17,6 +17,23 @@ func TestCreateAndDeleteSSOUser(t *testing.T) { 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 index f067dbd..09523cd 100644 --- a/sso_user.go +++ b/sso_user.go @@ -15,6 +15,7 @@ package goscaleio import ( "fmt" "net/http" + "net/url" types "github.com/dell/goscaleio/types/v1" ) @@ -32,6 +33,17 @@ func (c *Client) GetSSOUser(userID string) (*types.SSOUserDetails, error) { return user, nil } +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{} diff --git a/sso_user_test.go b/sso_user_test.go index 2331f2f..c381ca8 100644 --- a/sso_user_test.go +++ b/sso_user_test.go @@ -105,6 +105,43 @@ func TestGetSSOUser(t *testing.T) { } } +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 diff --git a/types/v1/ssoUserTypes.go b/types/v1/ssoUserTypes.go index e33f625..646e72e 100644 --- a/types/v1/ssoUserTypes.go +++ b/types/v1/ssoUserTypes.go @@ -26,6 +26,10 @@ type SSOUserDetails struct { Permission Permission `json:"permission"` } +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"` From 1e76d9279f7bd890c52451c21ce26c8aa706a7b9 Mon Sep 17 00:00:00 2001 From: Shendge Date: Fri, 23 Feb 2024 15:47:44 +0530 Subject: [PATCH 4/5] added filter for sso user --- sso_user.go | 1 + types/v1/ssoUserTypes.go | 1 + 2 files changed, 2 insertions(+) diff --git a/sso_user.go b/sso_user.go index 09523cd..7869ef4 100644 --- a/sso_user.go +++ b/sso_user.go @@ -33,6 +33,7 @@ func (c *Client) GetSSOUser(userID string) (*types.SSOUserDetails, error) { 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` diff --git a/types/v1/ssoUserTypes.go b/types/v1/ssoUserTypes.go index 646e72e..070270d 100644 --- a/types/v1/ssoUserTypes.go +++ b/types/v1/ssoUserTypes.go @@ -26,6 +26,7 @@ type SSOUserDetails struct { Permission Permission `json:"permission"` } +// SSOUserList represents the details of an SSO users. type SSOUserList struct { SSOUsers []SSOUserDetails `json:"users"` } From 569e66f19c749a3848a860d7ea4875a4af41314b Mon Sep 17 00:00:00 2001 From: Shendge Date: Tue, 27 Feb 2024 09:25:13 +0530 Subject: [PATCH 5/5] Added copyright info --- inttests/sso_user_test.go | 12 ++++++++++++ sso_user_test.go | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/inttests/sso_user_test.go b/inttests/sso_user_test.go index d1d7255..b0db84f 100644 --- a/inttests/sso_user_test.go +++ b/inttests/sso_user_test.go @@ -1,3 +1,15 @@ +// 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 ( diff --git a/sso_user_test.go b/sso_user_test.go index c381ca8..1b33636 100644 --- a/sso_user_test.go +++ b/sso_user_test.go @@ -1,3 +1,15 @@ +// 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 (