From 4912395d775018e30e8c089e5d2db4f1ce83c897 Mon Sep 17 00:00:00 2001 From: dasarinaidu Date: Tue, 13 Aug 2024 10:34:29 -0700 Subject: [PATCH] Automation tests for userretention (#46341) --- .../last_login.go => actions/auth/auth.go} | 18 +- tests/v2/actions/rbac/rbac.go | 64 ++++ .../auth/lastlogin/last_login_test.go | 57 ++-- .../validation/auth/userretention/README.md | 28 ++ .../auth/userretention/userretention.go | 103 ++++++ .../userretention_delete_user_test.go | 248 +++++++++++++++ .../userretention_disable_user_test.go | 298 ++++++++++++++++++ .../userretention_settings_test.go | 221 +++++++++++++ 8 files changed, 1000 insertions(+), 37 deletions(-) rename tests/v2/{validation/auth/lastlogin/last_login.go => actions/auth/auth.go} (67%) create mode 100644 tests/v2/validation/auth/userretention/README.md create mode 100644 tests/v2/validation/auth/userretention/userretention.go create mode 100644 tests/v2/validation/auth/userretention/userretention_delete_user_test.go create mode 100644 tests/v2/validation/auth/userretention/userretention_disable_user_test.go create mode 100644 tests/v2/validation/auth/userretention/userretention_settings_test.go diff --git a/tests/v2/validation/auth/lastlogin/last_login.go b/tests/v2/actions/auth/auth.go similarity index 67% rename from tests/v2/validation/auth/lastlogin/last_login.go rename to tests/v2/actions/auth/auth.go index 8c196813dc5..7fb94288ec1 100644 --- a/tests/v2/validation/auth/lastlogin/last_login.go +++ b/tests/v2/actions/auth/auth.go @@ -1,4 +1,4 @@ -package lastlogin +package auth import ( "fmt" @@ -13,11 +13,11 @@ import ( ) const ( - lastloginLabel = "cattle.io/last-login" + LastloginLabel = "cattle.io/last-login" ) -func getLastLoginTime(labels map[string]string) (lastLogin time.Time, err error) { - value, exists := isLabelPresent(labels) +func GetLastLoginTime(labels map[string]string) (lastLogin time.Time, err error) { + value, exists := IsLabelPresent(labels) if !exists || value == "" { return @@ -26,20 +26,20 @@ func getLastLoginTime(labels map[string]string) (lastLogin time.Time, err error) if err != nil { return } - lastLogin = convertEpochToTime(epochTime) + lastLogin = ConvertEpochToTime(epochTime) return } -func convertEpochToTime(epochTime int64) time.Time { +func ConvertEpochToTime(epochTime int64) time.Time { return time.Unix(epochTime, 0) } -func isLabelPresent(labels map[string]string) (string, bool) { - value, exists := labels[lastloginLabel] +func IsLabelPresent(labels map[string]string) (string, bool) { + value, exists := labels[LastloginLabel] return value, exists } -func getUserAfterLogin(rancherClient *rancher.Client, user client.User) (userDetails *v3.User, err error) { +func GetUserAfterLogin(rancherClient *rancher.Client, user client.User) (userDetails *v3.User, err error) { _, err = rancherClient.AsUser(&user) if err != nil { diff --git a/tests/v2/actions/rbac/rbac.go b/tests/v2/actions/rbac/rbac.go index 9924c430d54..21b64b25104 100644 --- a/tests/v2/actions/rbac/rbac.go +++ b/tests/v2/actions/rbac/rbac.go @@ -1,12 +1,18 @@ package rbac import ( + "fmt" "strings" + "github.com/rancher/norman/types" "github.com/rancher/shepherd/clients/rancher" management "github.com/rancher/shepherd/clients/rancher/generated/management/v3" + rbacv2 "github.com/rancher/shepherd/extensions/kubeapi/rbac" "github.com/rancher/shepherd/extensions/rbac" "github.com/rancher/shepherd/extensions/users" + "github.com/sirupsen/logrus" + rbacv1 "k8s.io/api/rbac/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type Role string @@ -70,3 +76,61 @@ func SetupUser(client *rancher.Client, globalRole string) (user *management.User } return } + +//GetRoleBindings is a helper function to fetch rolebindings for a user +func GetRoleBindings(rancherClient *rancher.Client, clusterID string, userID string) ([]rbacv1.RoleBinding, error) { + logrus.Infof("Getting role bindings for user %s in cluster %s", userID, clusterID) + listOpt := v1.ListOptions{} + roleBindings, err := rbacv2.ListRoleBindings(rancherClient, clusterID, "", listOpt) + if err != nil { + return nil, fmt.Errorf("failed to fetch RoleBindings: %w", err) + } + + var userRoleBindings []rbacv1.RoleBinding + for _, rb := range roleBindings.Items { + for _, subject := range rb.Subjects { + if subject.Name == userID { + userRoleBindings = append(userRoleBindings, rb) + break + } + } + } + logrus.Infof("Found %d role bindings for user %s", len(userRoleBindings), userID) + return userRoleBindings, nil +} + +//GetBindings is a helper function to fetch bindings for a user +func GetBindings(rancherClient *rancher.Client, userID string) (map[string]interface{}, error) { + logrus.Infof("Getting all bindings for user %s", userID) + bindings := make(map[string]interface{}) + + roleBindings, err := GetRoleBindings(rancherClient, rbacv2.LocalCluster, userID) + if err != nil { + return nil, fmt.Errorf("failed to get role bindings: %w", err) + } + bindings["RoleBindings"] = roleBindings + + logrus.Info("Getting cluster role bindings") + clusterRoleBindings, err := rbacv2.ListClusterRoleBindings(rancherClient, rbacv2.LocalCluster, v1.ListOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to list cluster role bindings: %w", err) + } + bindings["ClusterRoleBindings"] = clusterRoleBindings.Items + + logrus.Info("Getting global role bindings") + globalRoleBindings, err := rancherClient.Management.GlobalRoleBinding.ListAll(&types.ListOpts{}) + if err != nil { + return nil, fmt.Errorf("failed to list global role bindings: %w", err) + } + bindings["GlobalRoleBindings"] = globalRoleBindings.Data + + logrus.Info("Getting cluster role template bindings") + clusterRoleTemplateBindings, err := rancherClient.Management.ClusterRoleTemplateBinding.List(&types.ListOpts{}) + if err != nil { + return nil, fmt.Errorf("failed to list cluster role template bindings: %w", err) + } + bindings["ClusterRoleTemplateBindings"] = clusterRoleTemplateBindings.Data + + logrus.Info("All bindings retrieved successfully") + return bindings, nil +} diff --git a/tests/v2/validation/auth/lastlogin/last_login_test.go b/tests/v2/validation/auth/lastlogin/last_login_test.go index 3734ed6c9f0..9035f4aa711 100644 --- a/tests/v2/validation/auth/lastlogin/last_login_test.go +++ b/tests/v2/validation/auth/lastlogin/last_login_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/rancher/rancher/tests/v2/actions/auth" "github.com/rancher/shepherd/clients/rancher" users "github.com/rancher/shepherd/extensions/users" password "github.com/rancher/shepherd/extensions/users/passwordgenerator" @@ -45,10 +46,10 @@ func (ll *LastLoginTestSuite) TestLastLoginAdmin() { adminUser, err := ll.client.Management.User.ByID(adminId) adminUser.Password = ll.client.RancherConfig.AdminPassword - adminRelogin, err := getUserAfterLogin(ll.client, *adminUser) + adminRelogin, err := auth.GetUserAfterLogin(ll.client, *adminUser) require.NoError(ll.T(), err) - lastLoginTime, err := getLastLoginTime(adminRelogin.Labels) + lastLoginTime, err := auth.GetLastLoginTime(adminRelogin.Labels) require.NoError(ll.T(), err) require.False(ll.T(), lastLoginTime.IsZero(), "Last login is empty") @@ -61,10 +62,10 @@ func (ll *LastLoginTestSuite) TestLastLoginStandardUser() { require.NoError(ll.T(), err) ll.T().Logf("Created a standard user: %v", newUser.Username) - userLogin, err := getUserAfterLogin(ll.client, *newUser) + userLogin, err := auth.GetUserAfterLogin(ll.client, *newUser) require.NoError(ll.T(), err) - lastLoginTime, err := getLastLoginTime(userLogin.Labels) + lastLoginTime, err := auth.GetLastLoginTime(userLogin.Labels) require.NoError(ll.T(), err) require.False(ll.T(), lastLoginTime.IsZero(), "Last login is empty") @@ -77,18 +78,18 @@ func (ll *LastLoginTestSuite) TestReLoginUpdatesLastLogin() { require.NoError(ll.T(), err) ll.T().Logf("Created a standard user: %v", newUser.Username) - userLogin, err := getUserAfterLogin(ll.client, *newUser) + userLogin, err := auth.GetUserAfterLogin(ll.client, *newUser) require.NoError(ll.T(), err) - lastLoginTime, err := getLastLoginTime(userLogin.Labels) + lastLoginTime, err := auth.GetLastLoginTime(userLogin.Labels) require.NoError(ll.T(), err) require.False(ll.T(), lastLoginTime.IsZero(), "Last login is empty") userAttributes, err := ll.client.WranglerContext.Mgmt.UserAttribute().Get(newUser.ID, v1.GetOptions{}) assert.Equal(ll.T(), lastLoginTime, userAttributes.LastLogin.Time) - userLogin, err = getUserAfterLogin(ll.client, *newUser) + userLogin, err = auth.GetUserAfterLogin(ll.client, *newUser) require.NoError(ll.T(), err) - reLoginLastLoginTime, err := getLastLoginTime(userLogin.Labels) + reLoginLastLoginTime, err := auth.GetLastLoginTime(userLogin.Labels) require.NoError(ll.T(), err) require.False(ll.T(), lastLoginTime.IsZero(), "Last login is empty") @@ -108,7 +109,7 @@ func (ll *LastLoginTestSuite) TestUserLastLoginNotUpdated() { user, err := ll.client.WranglerContext.Mgmt.User().Get(newUser.ID, v1.GetOptions{}) - _, exists := user.Labels[lastloginLabel] + _, exists := user.Labels[auth.LastloginLabel] if exists { ll.Assert().Fail("Expected the lastlogin to be not present and empty") } @@ -123,19 +124,19 @@ func (ll *LastLoginTestSuite) TestLastLoginNotUpdatedWhenLoginFails() { require.NoError(ll.T(), err) ll.T().Logf("Created a standard user: %v", newUser.Username) - userLogin, err := getUserAfterLogin(ll.client, *newUser) + userLogin, err := auth.GetUserAfterLogin(ll.client, *newUser) require.NoError(ll.T(), err) - lastLoginTimePreFailure, err := getLastLoginTime(userLogin.Labels) + lastLoginTimePreFailure, err := auth.GetLastLoginTime(userLogin.Labels) require.NoError(ll.T(), err) newUser.Password = password.GenerateUserPassword("testpass-") - _, err = getUserAfterLogin(ll.client, *newUser) + _, err = auth.GetUserAfterLogin(ll.client, *newUser) require.Error(ll.T(), err) k8sErrors.IsUnauthorized(err) userDetails, err := ll.client.WranglerContext.Mgmt.User().Get(newUser.ID, v1.GetOptions{}) require.NoError(ll.T(), err) - lastLoginTimePostFailure, err := getLastLoginTime(userDetails.Labels) + lastLoginTimePostFailure, err := auth.GetLastLoginTime(userDetails.Labels) require.NoError(ll.T(), err) userAttributes, err := ll.client.WranglerContext.Mgmt.UserAttribute().Get(newUser.ID, v1.GetOptions{}) require.NoError(ll.T(), err) @@ -150,16 +151,16 @@ func (ll *LastLoginTestSuite) TestStandardUserCantUpdateLastLogin() { require.NoError(ll.T(), err) ll.T().Logf("Created a standard user: %v", newUser.Username) - updatedUserLogin, err := getUserAfterLogin(ll.client, *newUser) + updatedUserLogin, err := auth.GetUserAfterLogin(ll.client, *newUser) require.NoError(ll.T(), err) - lastLoginTime, err := getLastLoginTime(updatedUserLogin.Labels) + lastLoginTime, err := auth.GetLastLoginTime(updatedUserLogin.Labels) require.NoError(ll.T(), err) require.False(ll.T(), lastLoginTime.IsZero(), "Last login is empty") lastLoginTime = lastLoginTime.Add(48 * time.Hour) newLastLogin := strconv.FormatInt(lastLoginTime.Unix(), 10) - updatedUserLogin.Labels[lastloginLabel] = newLastLogin + updatedUserLogin.Labels[auth.LastloginLabel] = newLastLogin standardClient, err := ll.client.AsUser(newUser) require.NoError(ll.T(), err) @@ -176,14 +177,14 @@ func (ll *LastLoginTestSuite) TestStandardUserCantDeleteLastLogin() { require.NoError(ll.T(), err) ll.T().Logf("Created a standard user: %v", newUser.Username) - updatedUserLogin, err := getUserAfterLogin(ll.client, *newUser) + updatedUserLogin, err := auth.GetUserAfterLogin(ll.client, *newUser) require.NoError(ll.T(), err) - _, exists := updatedUserLogin.Labels[lastloginLabel] + _, exists := updatedUserLogin.Labels[auth.LastloginLabel] if !exists { ll.Assert().Fail("Expected the lastlogin to be not present and empty") } - delete(updatedUserLogin.Labels, lastloginLabel) + delete(updatedUserLogin.Labels, auth.LastloginLabel) standardClient, err := ll.client.AsUser(newUser) require.NoError(ll.T(), err) @@ -199,14 +200,14 @@ func (ll *LastLoginTestSuite) TestAdminCanDeleteLastLogin() { require.NoError(ll.T(), err) ll.T().Logf("Created a standard user: %v", newUser.Username) - updatedUserLogin, err := getUserAfterLogin(ll.client, *newUser) + updatedUserLogin, err := auth.GetUserAfterLogin(ll.client, *newUser) require.NoError(ll.T(), err) - _, exists := updatedUserLogin.Labels[lastloginLabel] + _, exists := updatedUserLogin.Labels[auth.LastloginLabel] if !exists { ll.Assert().Fail("Expected the lastlogin to be not present and empty") } - delete(updatedUserLogin.Labels, lastloginLabel) + delete(updatedUserLogin.Labels, auth.LastloginLabel) _, err = ll.client.WranglerContext.Mgmt.User().Update(updatedUserLogin) require.NoError(ll.T(), err) @@ -223,10 +224,10 @@ func (ll *LastLoginTestSuite) TestConcurrentLoginAsMultipleUsers() { ll.T().Logf("Created a standard user: %v", newUser2.Username) go func() { - userLogin, err := getUserAfterLogin(ll.client, *newUser1) + userLogin, err := auth.GetUserAfterLogin(ll.client, *newUser1) require.NoError(ll.T(), err) - lastLoginTime, err := getLastLoginTime(userLogin.Labels) + lastLoginTime, err := auth.GetLastLoginTime(userLogin.Labels) require.NoError(ll.T(), err) require.False(ll.T(), lastLoginTime.IsZero(), "Last login is empty") @@ -234,10 +235,10 @@ func (ll *LastLoginTestSuite) TestConcurrentLoginAsMultipleUsers() { assert.Equal(ll.T(), lastLoginTime, userAttributes.LastLogin.Time) }() go func() { - userLogin, err := getUserAfterLogin(ll.client, *newUser2) + userLogin, err := auth.GetUserAfterLogin(ll.client, *newUser2) require.NoError(ll.T(), err) - lastLoginTime, err := getLastLoginTime(userLogin.Labels) + lastLoginTime, err := auth.GetLastLoginTime(userLogin.Labels) require.NoError(ll.T(), err) require.False(ll.T(), lastLoginTime.IsZero(), "Last login is empty") @@ -253,10 +254,10 @@ func (ll *LastLoginTestSuite) TestConcurrentRefreshAndLogin() { ll.T().Logf("Created a standard user: %v", newUser.Username) go func() { - userLogin, err := getUserAfterLogin(ll.client, *newUser) + userLogin, err := auth.GetUserAfterLogin(ll.client, *newUser) require.NoError(ll.T(), err) - lastLoginTime, err := getLastLoginTime(userLogin.Labels) + lastLoginTime, err := auth.GetLastLoginTime(userLogin.Labels) require.NoError(ll.T(), err) require.False(ll.T(), lastLoginTime.IsZero(), "Last login is empty") diff --git a/tests/v2/validation/auth/userretention/README.md b/tests/v2/validation/auth/userretention/README.md new file mode 100644 index 00000000000..a5c96e4e347 --- /dev/null +++ b/tests/v2/validation/auth/userretention/README.md @@ -0,0 +1,28 @@ +# Userretention + +The User Retention feature in Rancher allows administrators to manage inactive users by automatically disabling and deleting them based on predefined settings and a user retention cron job. This functionality is implemented based on the user's last login timestamp. The tests included in this package focus on validating the behavior of the User Retention feature, including updating retention settings and verifying the proper disabling and deletion of inactive users. + +## Pre-requisites + +- The tests are designed to be executed on a local cluster. They can be run on a Rancher server without any downstream clusters. +- Update the default admin password in the configuration file. The tests rely on the admin password to authenticate and perform actions as the default admin user in Rancher. + +## Getting Started + +Your GO suite should be set to `-run ^Test<>TestSuite$`. + +To run the userretention_test.go, set the GO suite to `-run ^TestUserRetentionSettingsSuite$` You can find specific tests by checking the test file you plan to run. +To run the userretention_disable_user_test.go, set the GO suite to `-run ^TestURDisableUserSuite$` You can find specific tests by checking the test file you plan to run. +To run the userretention_delete_user_test.go, set the GO suite to `-run ^TestURDeleteUserSuite$` You can find specific tests by checking the test file you plan to run. + +In your config file, set the following: + +```json +"rancher": { + "host": "rancher_server_address", + "adminToken": "rancher_admin_token", + "insecure": true/optional, + "cleanup": false/optional, + "adminPassword": "" +} +``` \ No newline at end of file diff --git a/tests/v2/validation/auth/userretention/userretention.go b/tests/v2/validation/auth/userretention/userretention.go new file mode 100644 index 00000000000..253b9efe1b0 --- /dev/null +++ b/tests/v2/validation/auth/userretention/userretention.go @@ -0,0 +1,103 @@ +package userretention + +import ( + "context" + "fmt" + "time" + + "github.com/rancher/rancher/tests/v2/actions/auth" + "github.com/rancher/shepherd/clients/rancher" + "github.com/rancher/shepherd/extensions/users" + "github.com/sirupsen/logrus" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" +) + +const ( + disableInactiveUserAfter = "disable-inactive-user-after" + deleteInactiveUserAfter = "delete-inactive-user-after" + userRetentionCron = "user-retention-cron" + userRetentionDryRun = "user-retention-dry-run" + lastloginLabel = "cattle.io/last-login" + defaultWaitDuration = 70 * time.Second + pollInterval = 10 * time.Second + isActive = true + isInActive = false + webhookErrorMessage = "admission webhook \"rancher.cattle.io.settings.management.cattle.io\" denied the request: value: Invalid value:" +) + +func setupUserRetentionSettings(client *rancher.Client, disableAfterValue string, deleteAfterValue string, userRetentionCronValue string, dryRunValue string) error { + logrus.Info("Setting up user retention settings TO : ") + logrus.Infof("DisableAfterValue as %s, DeleteAfterValue as %s, UserRetentionCronValue as %s, DryRunValue as %s", disableAfterValue, deleteAfterValue, userRetentionCronValue, dryRunValue) + err := updateUserRetentionSettings(client, disableInactiveUserAfter, disableAfterValue) + if err != nil { + return fmt.Errorf("failed to update disable-inactive-user-after setting: %w", err) + } + err = updateUserRetentionSettings(client, deleteInactiveUserAfter, deleteAfterValue) + if err != nil { + return fmt.Errorf("failed to update delete-inactive-user-after setting: %w", err) + } + + err = updateUserRetentionSettings(client, userRetentionCron, userRetentionCronValue) + if err != nil { + return fmt.Errorf("failed to update user-retention-cron setting: %w", err) + } + err = updateUserRetentionSettings(client, userRetentionDryRun, dryRunValue) + if err != nil { + return fmt.Errorf("failed to update user-retention-dry-run setting: %w", err) + } + logrus.Info("User retention settings setup completed") + return nil +} + +func updateUserRetentionSettings(client *rancher.Client, settingName string, settingValue string) error { + logrus.Infof("Updating setting: %s, value: %s", settingName, settingValue) + setting, err := client.WranglerContext.Mgmt.Setting().Get(settingName, v1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get setting %s: %w", settingName, err) + } + + setting.Value = settingValue + _, err = client.WranglerContext.Mgmt.Setting().Update(setting) + if err != nil { + return fmt.Errorf("failed to update setting %s: %w", settingName, err) + } + return nil +} + +func pollUserStatus(rancherClient *rancher.Client, userID string, expectedStatus bool) error { + logrus.Infof("Polling user status for user %s, expected status: %v", userID, expectedStatus) + ctx, cancel := context.WithTimeout(context.Background(), defaultWaitDuration) + defer cancel() + + adminID, err := users.GetUserIDByName(rancherClient, "admin") + if err != nil { + return fmt.Errorf("failed to get admin user ID: %v", err) + } + adminUser, err := rancherClient.Management.User.ByID(adminID) + if err != nil { + return fmt.Errorf("failed to get admin user: %v", err) + } + adminUser.Password = rancherClient.RancherConfig.AdminPassword + + return wait.PollUntilContextTimeout(ctx, pollInterval, defaultWaitDuration, true, func(ctx context.Context) (bool, error) { + logrus.Info("Logging in with default admin user") + _, err := auth.GetUserAfterLogin(rancherClient, *adminUser) + if err != nil { + return false, fmt.Errorf("failed to login with admin user: %v", err) + } + + logrus.Info("Searching for the user status using the admin client") + user, err := rancherClient.Management.User.ByID(userID) + if err != nil { + return false, fmt.Errorf("failed to get user by ID: %v", err) + } + if user.Enabled == nil { + return false, fmt.Errorf("user.Enabled is nil") + } + + currentStatus := *user.Enabled + logrus.Infof("Current user status: %v, Expected status: %v", currentStatus, expectedStatus) + return currentStatus == expectedStatus, nil + }) +} diff --git a/tests/v2/validation/auth/userretention/userretention_delete_user_test.go b/tests/v2/validation/auth/userretention/userretention_delete_user_test.go new file mode 100644 index 00000000000..300e3301769 --- /dev/null +++ b/tests/v2/validation/auth/userretention/userretention_delete_user_test.go @@ -0,0 +1,248 @@ +//go:build (validation || infra.any || cluster.any || sanity) && !stress && !extended + +package userretention + +import ( + "testing" + "time" + + "github.com/rancher/norman/types" + "github.com/rancher/rancher/tests/v2/actions/auth" + "github.com/rancher/rancher/tests/v2/actions/rbac" + "github.com/rancher/shepherd/clients/rancher" + "github.com/rancher/shepherd/extensions/users" + "github.com/rancher/shepherd/pkg/session" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + rbac1 "k8s.io/api/rbac/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type URDeleteTestSuite struct { + suite.Suite + client *rancher.Client + session *session.Session +} + +func (ur *URDeleteTestSuite) SetupSuite() { + logrus.Info("Setting up URDeleteTestSuite") + ur.session = session.NewSession() + + logrus.Info("Creating new Rancher client") + client, err := rancher.NewClient("", ur.session) + require.NoError(ur.T(), err) + ur.client = client +} + +func (ur *URDeleteTestSuite) TearDownSuite() { + logrus.Info("Tearing down URDeleteTestSuite") + ur.session.Cleanup() +} + +func (ur *URDeleteTestSuite) TestDefaultAdminUserIsNotDeleted() { + logrus.Info("Setting up user retention settings") + setupUserRetentionSettings(ur.client, "", "400h", "*/1 * * * *", "false") + + logrus.Info("Getting admin user details") + adminID, err := users.GetUserIDByName(ur.client, "admin") + require.NoError(ur.T(), err) + adminUser, err := ur.client.Management.User.ByID(adminID) + require.NoError(ur.T(), err) + adminUser.Password = ur.client.RancherConfig.AdminPassword + + logrus.Info("Updating user attributes") + userAttributes, err := ur.client.WranglerContext.Mgmt.UserAttribute().Get(adminUser.ID, v1.GetOptions{}) + require.NoError(ur.T(), err) + userAttributes.DeleteAfter = &v1.Duration{Duration: time.Second * 10} + _, err = ur.client.WranglerContext.Mgmt.UserAttribute().Update(userAttributes) + require.NoError(ur.T(), err) + + logrus.Info("Attempting initial login") + _, err = auth.GetUserAfterLogin(ur.client, *adminUser) + require.NoError(ur.T(), err) + + logrus.Info("Waiting for default duration") + time.Sleep(defaultWaitDuration) + + logrus.Info("Attempting login after wait period") + _, err = auth.GetUserAfterLogin(ur.client, *adminUser) + require.NoError(ur.T(), err) +} + +func (ur *URDeleteTestSuite) TestAdminUserGetDeleted() { + logrus.Info("Setting up user retention settings") + setupUserRetentionSettings(ur.client, "", "400h", "*/1 * * * *", "false") + + logrus.Info("Creating new admin user") + newAdminUser, err := users.CreateUserWithRole(ur.client, users.UserConfig(), "admin") + require.NoError(ur.T(), err) + userReLogin, err := auth.GetUserAfterLogin(ur.client, *newAdminUser) + require.NoError(ur.T(), err) + + logrus.Info("Updating user attributes") + userAttributes, err := ur.client.WranglerContext.Mgmt.UserAttribute().Get(newAdminUser.ID, v1.GetOptions{}) + require.NoError(ur.T(), err) + userAttributes.DeleteAfter = &v1.Duration{Duration: time.Second * 10} + _, err = ur.client.WranglerContext.Mgmt.UserAttribute().Update(userAttributes) + require.NoError(ur.T(), err) + + logrus.Info("Verifying user status") + userReLogin, err = ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) + require.NoError(ur.T(), err) + + logrus.Info("Waiting for default duration") + pollUserStatus(ur.client, newAdminUser.ID, isInActive) + + logrus.Info("Attempting login after wait period") + _, err = auth.GetUserAfterLogin(ur.client, *newAdminUser) + assert.ErrorContains(ur.T(), err, "401 Unauthorized") + + logrus.Info("Checking bindings after user deletion") + bindingsAfter, err := rbac.GetBindings(ur.client, newAdminUser.ID) + require.NoError(ur.T(), err) + assert.Empty(ur.T(), bindingsAfter["RoleBindings"], "Expected no RoleBindings after user deletion") + assert.Equal(ur.T(), 0, len(bindingsAfter["RoleBindings"].([]rbac1.RoleBinding)), "RoleBindings slice should be empty after user deletion") + + logrus.Info("Checking global role bindings after user deletion") + globalRoleBindingsAfter, err := ur.client.Management.GlobalRoleBinding.List(&types.ListOpts{ + Filters: map[string]interface{}{ + "userId": newAdminUser.ID, + }, + }) + require.NoError(ur.T(), err) + assert.Empty(ur.T(), globalRoleBindingsAfter.Data, "Expected no GlobalRoleBindings after user deletion") +} + +func (ur *URDeleteTestSuite) TestStandardUserGetDeleted() { + logrus.Info("Setting up user retention settings") + setupUserRetentionSettings(ur.client, "", "400h", "*/1 * * * *", "false") + + logrus.Info("Creating new standard user") + newStdUser, err := users.CreateUserWithRole(ur.client, users.UserConfig(), "user") + require.NoError(ur.T(), err) + + logrus.Info("Logging in with new standard user") + userReLogin, err := auth.GetUserAfterLogin(ur.client, *newStdUser) + require.NoError(ur.T(), err) + + logrus.Info("Updating user attributes") + userAttributes, err := ur.client.WranglerContext.Mgmt.UserAttribute().Get(newStdUser.ID, v1.GetOptions{}) + require.NoError(ur.T(), err) + userAttributes.DeleteAfter = &v1.Duration{Duration: time.Second * 10} + _, err = ur.client.WranglerContext.Mgmt.UserAttribute().Update(userAttributes) + require.NoError(ur.T(), err) + + logrus.Info("Verifying user status") + userReLogin, err = ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) + require.NoError(ur.T(), err) + + logrus.Info("Polling user status to disable") + pollUserStatus(ur.client, newStdUser.ID, isInActive) + + logrus.Info("Attempting login after wait period") + _, err = auth.GetUserAfterLogin(ur.client, *newStdUser) + assert.ErrorContains(ur.T(), err, "401 Unauthorized") + + logrus.Info("Checking bindings after user deletion") + bindingsAfter, err := rbac.GetBindings(ur.client, newStdUser.ID) + require.NoError(ur.T(), err) + assert.Empty(ur.T(), bindingsAfter["RoleBindings"], "Expected no RoleBindings after user deletion") + assert.Equal(ur.T(), 0, len(bindingsAfter["RoleBindings"].([]rbac1.RoleBinding)), "RoleBindings slice should be empty after user deletion") + + logrus.Info("Checking global role bindings after user deletion") + globalRoleBindingsAfter, err := ur.client.Management.GlobalRoleBinding.List(&types.ListOpts{ + Filters: map[string]interface{}{ + "userId": newStdUser.ID, + }, + }) + require.NoError(ur.T(), err) + assert.Empty(ur.T(), globalRoleBindingsAfter.Data, "Expected no GlobalRoleBindings after user deletion") +} + +func (ur *URDeleteTestSuite) TestUserIsNotDeletedWithBlankSettings() { + logrus.Info("Setting up user retention settings with blank values") + setupUserRetentionSettings(ur.client, "", "400h", "*/1 * * * *", "false") + + logrus.Info("Creating new standard user") + newUser, err := users.CreateUserWithRole(ur.client, users.UserConfig(), "user") + require.NoError(ur.T(), err) + + logrus.Info("Logging in with new user") + userReLogin, err := auth.GetUserAfterLogin(ur.client, *newUser) + require.NoError(ur.T(), err) + + logrus.Info("Updating user attributes") + userAttributes, err := ur.client.WranglerContext.Mgmt.UserAttribute().Get(newUser.ID, v1.GetOptions{}) + require.NoError(ur.T(), err) + userAttributes.DeleteAfter = nil + _, err = ur.client.WranglerContext.Mgmt.UserAttribute().Update(userAttributes) + require.NoError(ur.T(), err) + + logrus.Info("Verifying user status") + userReLogin, err = ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) + require.NoError(ur.T(), err) + + logrus.Info("Waiting for default duration") + time.Sleep(defaultWaitDuration) + + logrus.Info("Verifying user status after wait period") + userReLogin1, err := ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isActive, *(userReLogin1).Enabled) +} + +func (ur *URDeleteTestSuite) TestUserIsNotGetDeletedWithDryRun() { + logrus.Info("Setting up user retention settings with dry run") + setupUserRetentionSettings(ur.client, "", "400h", "*/1 * * * *", "true") + + logrus.Info("Creating new standard user") + newUser, err := users.CreateUserWithRole(ur.client, users.UserConfig(), "user") + require.NoError(ur.T(), err) + + logrus.Info("Logging in with new user") + userReLogin, err := auth.GetUserAfterLogin(ur.client, *newUser) + require.NoError(ur.T(), err) + + logrus.Info("Updating user attributes") + userAttributes, err := ur.client.WranglerContext.Mgmt.UserAttribute().Get(newUser.ID, v1.GetOptions{}) + require.NoError(ur.T(), err) + userAttributes.DeleteAfter = &v1.Duration{Duration: time.Second * 10} + _, err = ur.client.WranglerContext.Mgmt.UserAttribute().Update(userAttributes) + require.NoError(ur.T(), err) + + logrus.Info("Verifying user status") + userReLogin, err = ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) + require.NoError(ur.T(), err) + + logrus.Info("Getting initial bindings") + bindingsBefore, _ := rbac.GetBindings(ur.client, newUser.ID) + + logrus.Info("Waiting for default duration") + time.Sleep(2 * defaultWaitDuration) + + logrus.Info("Verifying user status after wait period") + userReLogin, err = ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) + + logrus.Info("Checking bindings after wait period") + bindingsAfter, _ := rbac.GetBindings(ur.client, newUser.ID) + assert.Equal(ur.T(), bindingsBefore, bindingsAfter, "RoleBindings") + assert.Equal(ur.T(), bindingsBefore, bindingsAfter, "ClusterRoleBindings") + assert.Equal(ur.T(), bindingsBefore, bindingsAfter, "GlobalRoleBindings") + assert.Equal(ur.T(), bindingsBefore, bindingsAfter, "ClusterRoleTemplateBindings") + require.NoError(ur.T(), err) + + logrus.Info("Resetting user retention dry run setting") + updateUserRetentionSettings(ur.client, userRetentionDryRun, "false") +} + +func TestURDeleteUserSuite(t *testing.T) { + suite.Run(t, new(URDeleteTestSuite)) +} diff --git a/tests/v2/validation/auth/userretention/userretention_disable_user_test.go b/tests/v2/validation/auth/userretention/userretention_disable_user_test.go new file mode 100644 index 00000000000..76f7d5ade7d --- /dev/null +++ b/tests/v2/validation/auth/userretention/userretention_disable_user_test.go @@ -0,0 +1,298 @@ +package userretention + +import ( + "testing" + "time" + + "github.com/rancher/rancher/tests/v2/actions/auth" + "github.com/rancher/rancher/tests/v2/actions/rbac" + "github.com/rancher/shepherd/clients/rancher" + managementv3 "github.com/rancher/shepherd/clients/rancher/generated/management/v3" + "github.com/rancher/shepherd/extensions/users" + "github.com/rancher/shepherd/pkg/session" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type URDisableTestSuite struct { + suite.Suite + client *rancher.Client + session *session.Session + adminClient *rancher.Client + adminUser *managementv3.User +} + +func (ur *URDisableTestSuite) SetupSuite() { + logrus.Info("Setting up URDisableTestSuite") + ur.session = session.NewSession() + + logrus.Info("Creating new Rancher client") + client, err := rancher.NewClient("", ur.session) + require.NoError(ur.T(), err) + ur.client = client +} + +func (ur *URDisableTestSuite) TearDownSuite() { + logrus.Info("Tearing down URDisableTestSuite") + if ur.adminUser != nil { + logrus.Info("Deleting admin user") + err := ur.client.Management.User.Delete(ur.adminUser) + if err != nil { + logrus.Errorf("Failed to delete admin user: %v", err) + } + } + logrus.Info("Cleaning up session") + ur.session.Cleanup() +} + +func (ur *URDisableTestSuite) assertBindingsEqual(before, after map[string]interface{}) { + assert.Equal(ur.T(), before["RoleBindings"], after["RoleBindings"], "RoleBindings mismatch") + assert.Equal(ur.T(), before["ClusterRoleBindings"], after["ClusterRoleBindings"], "ClusterRoleBindings mismatch") + assert.Equal(ur.T(), before["GlobalRoleBindings"], after["GlobalRoleBindings"], "GlobalRoleBindings mismatch") + assert.Equal(ur.T(), before["ClusterRoleTemplateBindings"], after["ClusterRoleTemplateBindings"], "ClusterRoleTemplateBindings mismatch") +} + +func (ur *URDisableTestSuite) TestDefaultAdminUserIsNotDisabled() { + logrus.Info("Setting up user retention settings") + setupUserRetentionSettings(ur.client, "10s", "", "*/1 * * * *", "false") + logrus.Info("Retrieving admin user details") + adminID, err := users.GetUserIDByName(ur.client, "admin") + require.NoError(ur.T(), err) + adminUser, err := ur.client.Management.User.ByID(adminID) + require.NoError(ur.T(), err) + adminUser.Password = ur.client.RancherConfig.AdminPassword + + logrus.Info("Attempting initial login for admin user") + _, err = auth.GetUserAfterLogin(ur.client, *adminUser) + require.NoError(ur.T(), err) + + logrus.Info("Waiting for default duration") + time.Sleep(defaultWaitDuration) + + logrus.Info("Attempting login after wait period") + _, err = auth.GetUserAfterLogin(ur.client, *adminUser) + require.NoError(ur.T(), err) +} + +func (ur *URDisableTestSuite) TestAdminUserGetDisabled() { + logrus.Info("Setting up user retention settings") + setupUserRetentionSettings(ur.client, "10s", "", "*/1 * * * *", "false") + + logrus.Info("Creating new admin user") + newAdminUser, err := users.CreateUserWithRole(ur.client, users.UserConfig(), "admin") + require.NoError(ur.T(), err) + userReLogin, err := auth.GetUserAfterLogin(ur.client, *newAdminUser) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) + + bindingsBefore, err := rbac.GetBindings(ur.client, newAdminUser.ID) + require.NoError(ur.T(), err, "Failed to get initial bindings") + + logrus.Info("Polling user status") + pollUserStatus(ur.client, newAdminUser.ID, isInActive) + + logrus.Info("Verifying user status after polling") + userReLogin, err = ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isInActive, *(userReLogin).Enabled) + + logrus.Info("Attempting login with disabled user") + _, err = auth.GetUserAfterLogin(ur.client, *newAdminUser) + assert.ErrorContains(ur.T(), err, "403 Forbidden") + bindingsAfter, err := rbac.GetBindings(ur.client, newAdminUser.ID) + require.NoError(ur.T(), err, "Failed to get final bindings") + ur.assertBindingsEqual(bindingsBefore, bindingsAfter) + +} + +func (ur *URDisableTestSuite) TestStandardUserGetDisabled() { + logrus.Info("Setting up user retention settings") + setupUserRetentionSettings(ur.client, "10s", "", "*/1 * * * *", "false") + + logrus.Info("Creating new standard user") + newStdUser, err := users.CreateUserWithRole(ur.client, users.UserConfig(), "user") + require.NoError(ur.T(), err) + userReLogin, err := auth.GetUserAfterLogin(ur.client, *newStdUser) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) + + logrus.Info("Getting initial bindings") + bindingsBefore, err := rbac.GetBindings(ur.client, newStdUser.ID) + require.NoError(ur.T(), err, "Failed to get initial bindings") + + logrus.Info("Polling user status") + pollUserStatus(ur.client, newStdUser.ID, isInActive) + + logrus.Info("Verifying user status after polling") + userReLogin, err = ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isInActive, *(userReLogin).Enabled) + + logrus.Info("Attempting login with disabled user") + _, err = auth.GetUserAfterLogin(ur.client, *newStdUser) + assert.ErrorContains(ur.T(), err, "403 Forbidden") + + logrus.Info("Getting final bindings") + bindingsAfter, err := rbac.GetBindings(ur.client, newStdUser.ID) + require.NoError(ur.T(), err, "Failed to get final bindings") + ur.assertBindingsEqual(bindingsBefore, bindingsAfter) +} + +func (ur *URDisableTestSuite) TestDisabledUserGetEnabled() { + logrus.Info("Setting up user retention settings") + setupUserRetentionSettings(ur.client, "10s", "", "*/1 * * * *", "false") + + logrus.Info("Creating new standard user") + newStdUser, err := users.CreateUserWithRole(ur.client, users.UserConfig(), "user") + require.NoError(ur.T(), err) + userReLogin, err := auth.GetUserAfterLogin(ur.client, *newStdUser) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) + + logrus.Info("Getting initial bindings") + bindingsBefore, err := rbac.GetBindings(ur.client, newStdUser.ID) + require.NoError(ur.T(), err, "Failed to get initial bindings") + + logrus.Info("Polling user status to disable") + pollUserStatus(ur.client, newStdUser.ID, isInActive) + + logrus.Info("Verifying user status after polling") + userReLogin, err = ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isInActive, *(userReLogin).Enabled) + + logrus.Info("Attempting login with disabled user") + _, err = auth.GetUserAfterLogin(ur.client, *newStdUser) + assert.ErrorContains(ur.T(), err, "403 Forbidden") + + logrus.Info("Enabling the disabled user") + enabled := true + userReLogin.Enabled = &enabled + activateUser, err := ur.client.WranglerContext.Mgmt.User().Update(userReLogin) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isActive, *(activateUser).Enabled) + + logrus.Info("Verifying user status after enabling") + activeUserReLogin, err := ur.client.WranglerContext.Mgmt.User().Get(activateUser.Name, v1.GetOptions{}) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isActive, *(activeUserReLogin).Enabled) + + logrus.Info("Getting final bindings") + bindingsAfterEnabled, err := rbac.GetBindings(ur.client, newStdUser.ID) + require.NoError(ur.T(), err, "Failed to get final bindings") + ur.assertBindingsEqual(bindingsBefore, bindingsAfterEnabled) +} + +func (ur *URDisableTestSuite) TestStandardUserDidNotGetDisabledWithBlankSettings() { + logrus.Info("Setting up user retention settings with blank values") + setupUserRetentionSettings(ur.client, "", "", "*/1 * * * *", "false") + + logrus.Info("Creating new standard user") + newUser, err := users.CreateUserWithRole(ur.client, users.UserConfig(), "user") + require.NoError(ur.T(), err) + userReLogin, err := auth.GetUserAfterLogin(ur.client, *newUser) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) + + logrus.Info("Waiting for default duration") + time.Sleep(defaultWaitDuration) + + logrus.Info("Attempting login after wait period") + userReLogin, err = auth.GetUserAfterLogin(ur.client, *newUser) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) +} + +func (ur *URDisableTestSuite) TestUserDisableByUpdatingUserattributes() { + logrus.Info("Setting up user retention settings") + setupUserRetentionSettings(ur.client, "10s", "", "*/1 * * * *", "false") + + logrus.Info("Creating new standard user") + newStdUser, err := users.CreateUserWithRole(ur.client, users.UserConfig(), "user") + require.NoError(ur.T(), err) + userReLogin, err := auth.GetUserAfterLogin(ur.client, *newStdUser) + require.NoError(ur.T(), err) + + logrus.Info("Getting initial bindings") + bindingsBefore, err := rbac.GetBindings(ur.client, newStdUser.ID) + require.NoError(ur.T(), err, "Failed to get initial bindings") + + logrus.Info("Updating user attributes") + userAttributes, err := ur.client.WranglerContext.Mgmt.UserAttribute().Get(newStdUser.ID, v1.GetOptions{}) + require.NoError(ur.T(), err) + userAttributes.DisableAfter = &v1.Duration{Duration: time.Second * 10} + _, err = ur.client.WranglerContext.Mgmt.UserAttribute().Update(userAttributes) + require.NoError(ur.T(), err) + + logrus.Info("Verifying user status after updating attributes") + userReLogin, err = ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) + require.NoError(ur.T(), err) + + logrus.Info("Polling user status") + pollUserStatus(ur.client, newStdUser.ID, isInActive) + + logrus.Info("Verifying user status after polling") + userReLogin, err = ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isInActive, *(userReLogin).Enabled) + + logrus.Info("Attempting login with disabled user") + _, err = auth.GetUserAfterLogin(ur.client, *newStdUser) + assert.ErrorContains(ur.T(), err, "403 Forbidden") + + logrus.Info("Getting final bindings") + bindingsAfter, err := rbac.GetBindings(ur.client, newStdUser.ID) + require.NoError(ur.T(), err, "Failed to get final bindings") + ur.assertBindingsEqual(bindingsBefore, bindingsAfter) +} + +func (ur *URDisableTestSuite) TestUserIsNotDisabledWithDryRun() { + logrus.Info("Setting up user retention settings with dry run") + setupUserRetentionSettings(ur.client, "10s", "", "*/1 * * * *", "true") + + logrus.Info("Creating new standard user") + newStdUser, err := users.CreateUserWithRole(ur.client, users.UserConfig(), "user") + require.NoError(ur.T(), err) + userReLogin, err := auth.GetUserAfterLogin(ur.client, *newStdUser) + require.NoError(ur.T(), err) + + logrus.Info("Getting initial bindings") + bindingsBefore, err := rbac.GetBindings(ur.client, newStdUser.ID) + require.NoError(ur.T(), err, "Failed to get initial bindings") + + logrus.Info("Updating user attributes") + userAttributes, err := ur.client.WranglerContext.Mgmt.UserAttribute().Get(newStdUser.ID, v1.GetOptions{}) + require.NoError(ur.T(), err) + userAttributes.DisableAfter = &v1.Duration{Duration: time.Second * 10} + _, err = ur.client.WranglerContext.Mgmt.UserAttribute().Update(userAttributes) + require.NoError(ur.T(), err) + + logrus.Info("Verifying user status after updating attributes") + userReLogin, err = ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) + require.NoError(ur.T(), err) + + logrus.Info("Waiting for default duration") + time.Sleep(2 * defaultWaitDuration) + + logrus.Info("Attempting login after wait period") + userReLogin, err = ur.client.WranglerContext.Mgmt.User().Get(userReLogin.Name, v1.GetOptions{}) + require.NoError(ur.T(), err) + assert.Equal(ur.T(), isActive, *(userReLogin).Enabled) + + logrus.Info("Getting final bindings") + bindingsAfter, err := rbac.GetBindings(ur.client, newStdUser.ID) + require.NoError(ur.T(), err, "Failed to get final bindings") + ur.assertBindingsEqual(bindingsBefore, bindingsAfter) + + logrus.Info("User retention settings:dry run settings back to default value") + updateUserRetentionSettings(ur.client, userRetentionDryRun, "false") +} + +func TestURDisableUserSuite(t *testing.T) { + suite.Run(t, new(URDisableTestSuite)) +} diff --git a/tests/v2/validation/auth/userretention/userretention_settings_test.go b/tests/v2/validation/auth/userretention/userretention_settings_test.go new file mode 100644 index 00000000000..8bfa0f8d59c --- /dev/null +++ b/tests/v2/validation/auth/userretention/userretention_settings_test.go @@ -0,0 +1,221 @@ +//go:build (validation || infra.any || cluster.any || sanity) && !stress && !extended + +package userretention + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/rancher/shepherd/clients/rancher" + "github.com/rancher/shepherd/pkg/session" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +type UserRetentionSettingsTestSuite struct { + suite.Suite + client *rancher.Client + session *session.Session +} + +func (ur *UserRetentionSettingsTestSuite) SetupSuite() { + ur.session = session.NewSession() + client, err := rancher.NewClient("", ur.session) + require.NoError(ur.T(), err) + ur.client = client +} + +func (ur *UserRetentionSettingsTestSuite) TearDownSuite() { + ur.session.Cleanup() +} + +func (ur *UserRetentionSettingsTestSuite) testPositiveInputValues(settingName string, tests []struct { + value string + description string +}) { + logrus.Infof("Updating %s settings with positive values:", settingName) + for _, inputValue := range tests { + ur.T().Run(inputValue.value, func(*testing.T) { + err := updateUserRetentionSettings(ur.client, settingName, inputValue.value) + assert.NoError(ur.T(), err, "Unexpected error for input '%s'", inputValue.value) + + if err == nil { + settings, err1 := ur.client.Management.Setting.ByID(settingName) + if assert.NoError(ur.T(), err1, "Failed to retrieve settings") { + assert.Equal(ur.T(), settingName, settings.Name) + assert.Equal(ur.T(), inputValue.value, settings.Value) + } + } + logrus.Infof("%s setting is updated to %s ; %s", settingName, inputValue.value, inputValue.description) + }) + } +} + +func (ur *UserRetentionSettingsTestSuite) testNegativeInputValues(settingName string, tests []struct { + value string + description string +}) { + logrus.Infof("Updating %s settings with negative values:", settingName) + for _, inputValue := range tests { + ur.T().Run(inputValue.value, func(*testing.T) { + err := updateUserRetentionSettings(ur.client, settingName, inputValue.value) + assert.Error(ur.T(), err, "Expected an error for input '%s', but got nil", inputValue.value) + + if err != nil { + ur.validateError(err, inputValue.description) + ur.validateSettingsNotUpdated(settingName, inputValue.value) + } + logrus.Infof("Failed to update %s settings to %s; %s", settingName, inputValue.value, inputValue.description) + }) + } +} + +func (ur *UserRetentionSettingsTestSuite) validateError(err error, expectedDescription string) { + var statusErr *apierrors.StatusError + var found bool + + switch e := err.(type) { + case interface{ Unwrap() error }: + if innerErr := e.Unwrap(); innerErr != nil { + if innerWrapper, ok := innerErr.(interface{ Unwrap() error }); ok { + if deepestErr := innerWrapper.Unwrap(); deepestErr != nil { + statusErr, found = deepestErr.(*apierrors.StatusError) + } + } + } + } + + if found && statusErr != nil { + assert.Equal(ur.T(), int32(400), statusErr.ErrStatus.Code, "Status code should be 400") + assert.Equal(ur.T(), metav1.StatusReasonBadRequest, statusErr.ErrStatus.Reason, "Reason should be BadRequest") + assert.Contains(ur.T(), statusErr.ErrStatus.Message, expectedDescription, "Error should contain the expected description") + } else { + errMsg := err.Error() + assert.Contains(ur.T(), errMsg, "denied the request", "Error should mention denied request") + assert.Contains(ur.T(), errMsg, expectedDescription, "Error should contain the expected description") + } +} + +func (ur *UserRetentionSettingsTestSuite) validateSettingsNotUpdated(settingName, inputValue string) { + settings, err := ur.client.Management.Setting.ByID(settingName) + if assert.NoError(ur.T(), err, "Failed to retrieve settings") { + assert.Equal(ur.T(), settingName, settings.Name) + assert.NotEqual(ur.T(), inputValue, settings.Value) + } +} + +func (ur *UserRetentionSettingsTestSuite) TestUpdateSettingsForDisableInactiveUserAfterWithPositiveInputValues() { + tests := []struct { + value string + description string + }{ + {"", "No action - users will not be deactivated"}, + {"0s", "Users will be deactivated after 0s"}, + {"0m", "Users will be deactivated after 0m"}, + {"0h", "Users will be deactivated after 0h"}, + {"10s", "Users will be deactivated after 10s"}, + {"10m", "Users will be deactivated after 10m"}, + {"20h", "Users will be deactivated after 20h"}, + {"10000s", "Users will be deactivated after 10000s"}, + {"10000m", "Users will be deactivated after 10000m"}, + {"10000h", "Users will be deactivated after 10000h"}, + } + ur.testPositiveInputValues(disableInactiveUserAfter, tests) +} + +func (ur *UserRetentionSettingsTestSuite) TestUpdateSettingsForDisableInactiveUserAfterWithNegativeInputValues() { + tests := []struct { + value string + description string + }{ + {"10", "Invalid value: \"10\": time: missing unit in duration \"10\""}, + {"10S", "Invalid value: \"10S\": time: unknown unit \"S\" in duration \"10S\""}, + {"10M", "Invalid value: \"10M\": time: unknown unit \"M\" in duration \"10M\""}, + {"10H", "Invalid value: \"10H\": time: unknown unit \"H\" in duration \"10H\""}, + {"10sec", "Invalid value: \"10sec\": time: unknown unit \"sec\" in duration \"10sec\""}, + {"10min", "Invalid value: \"10min\": time: unknown unit \"min\" in duration \"10min\""}, + {"20hour", "Invalid value: \"20hour\": time: unknown unit \"hour\" in duration \"20hour\""}, + {"1d", "Invalid value: \"1d\": time: unknown unit \"d\" in duration \"1d\""}, + {"-20m", "Invalid value: \"-20m\": negative duration"}, + {"tens", "Invalid value: \"tens\": time: invalid duration \"tens\""}, + } + ur.testNegativeInputValues(disableInactiveUserAfter, tests) +} + +func (ur *UserRetentionSettingsTestSuite) TestUpdateSettingsForDeleteInactiveUserAfterWithPositiveInputValues() { + tests := []struct { + value string + description string + }{ + {"", "No action - users will not be deleted"}, + {"100000000s", "Users will delete after 100000000s"}, + {"200000m", "Users will delete after 200000m"}, + {"10000h", "Users will delete after 10000h"}, + } + ur.testPositiveInputValues(deleteInactiveUserAfter, tests) +} + +func (ur *UserRetentionSettingsTestSuite) TestUpdateSettingsForDeleteInactiveUserAfterWithNegativeInputValues() { + tests := []struct { + value string + description string + }{ + {"10", "Invalid value: \"10\": time: missing unit in duration \"10\""}, + {"10s", "Invalid value: \"10s\": must be at least 336h0m0s"}, + {"10m", "Invalid value: \"10m\": must be at least 336h0m0s"}, + {"10h", "Invalid value: \"10h\": must be at least 336h0m0s"}, + {"10S", "Invalid value: \"10S\": time: unknown unit \"S\" in duration \"10S\""}, + {"10M", "Invalid value: \"10M\": time: unknown unit \"M\" in duration \"10M\""}, + {"10H", "Invalid value: \"10H\": time: unknown unit \"H\" in duration \"10H\""}, + {"10sec", "Invalid value: \"10sec\": time: unknown unit \"sec\" in duration \"10sec\""}, + {"10min", "Invalid value: \"10min\": time: unknown unit \"min\" in duration \"10min\""}, + {"20hour", "Invalid value: \"20hour\": time: unknown unit \"hour\" in duration \"20hour\""}, + {"1d", "Invalid value: \"1d\": time: unknown unit \"d\" in duration \"1d\""}, + {"-20m", "Invalid value: \"-20m\": negative duration"}, + } + ur.testNegativeInputValues(deleteInactiveUserAfter, tests) +} + +func (ur *UserRetentionSettingsTestSuite) TestUpdateSettingsForUserRetentionCronWithPositiveInputValues() { + tests := []struct { + value string + description string + }{ + {"0 * * * *", "every 1 hour"}, + {"0 0 * * *", "every 1 day"}, + {"*/5 * * * *", "every 5 mins"}, + {"*/1 * * * *", "every min"}, + {"* * * * *", "every min"}, + {"30/1 * * * *", "every 30 sec"}, + {"0-5 14 * * *", "every minute starting at 2:00 PM and ending at 2:05 PM, every day"}, + {"0 0 1,2 * *", "at midnight of 1st, 2nd day of each month"}, + {"0 0 1,2 * 3", "at midnight of 1st, 2nd day of each month, and each Wednesday"}, + } + ur.testPositiveInputValues(userRetentionCron, tests) +} + +func (ur *UserRetentionSettingsTestSuite) TestUpdateSettingsForUserRetentionCronWithNegativeInputValues() { + tests := []struct { + value string + description string + }{ + {"* * * * * *", "Invalid value: \"* * * * * *\": Expected exactly 5 fields, found 6: * * * * * *"}, + {"*/-1 * * * *", "Invalid value: \"*/-1 * * * *\": Negative number (-1) not allowed: -1"}, + {"60/1 * * * *", "Invalid value: \"60/1 * * * *\": Beginning of range (60) beyond end of range (59): 60/1"}, + {"-30/1 * * * *", "Invalid value: \"-30/1 * * * *\": Failed to parse int from : strconv.Atoi: parsing \"\": invalid syntax"}, + {"(*/1) * * * *", "Invalid value: \"(*/1) * * * *\": Failed to parse int from (*: strconv.Atoi: parsing \"(*\": invalid syntax"}, + {"10min", "Invalid value: \"10min\": Expected exactly 5 fields, found 1: 10min"}, + {"* * * * * */2", "Invalid value: \"* * * * * */2\": Expected exactly 5 fields, found 6: * * * * * */2"}, + {"1d", "Invalid value: \"1d\": Expected exactly 5 fields, found 1: 1d"}, + {"-20m", "Invalid value: \"-20m\": Expected exactly 5 fields, found 1: -20m"}, + } + ur.testNegativeInputValues(userRetentionCron, tests) +} + +func TestUserRetentionSettingsSuite(t *testing.T) { + suite.Run(t, new(UserRetentionSettingsTestSuite)) +}