diff --git a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java index 730182a1cb5..f4b2f646002 100644 --- a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java +++ b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java @@ -47,6 +47,8 @@ public interface ProjectAccountDao extends GenericDao { void removeAccountFromProjects(long accountId); + void removeUserFromProjects(long userId); + boolean canUserModifyProject(long projectId, long accountId, long userId); List listUsersOrAccountsByRole(long id); diff --git a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java index 8947cc600b3..b6eb6d44cea 100644 --- a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java @@ -192,6 +192,17 @@ public class ProjectAccountDaoImpl extends GenericDaoBase sc = AllFieldsSearch.create(); + sc.setParameters("userId", userId); + + int removedCount = remove(sc); + if (removedCount > 0) { + logger.debug(String.format("Removed user [%s] from %s project(s).", userId, removedCount)); + } + } + @Override public boolean canUserModifyProject(long projectId, long accountId, long userId) { SearchCriteria sc = AllFieldsSearch.create(); diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41910to41920.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41910to41920.java new file mode 100644 index 00000000000..6215021473e --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41910to41920.java @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 com.cloud.upgrade.dao; + +import com.cloud.utils.exception.CloudRuntimeException; + +import java.io.InputStream; +import java.sql.Connection; + +public class Upgrade41910to41920 implements DbUpgrade { + + @Override + public String[] getUpgradableVersionRange() { + return new String[]{"4.19.1.0", "4.19.2.0"}; + } + + @Override + public String getUpgradedVersion() { + return "4.19.2.0"; + } + + @Override + public boolean supportsRollingUpgrade() { + return false; + } + + @Override + public InputStream[] getPrepareScripts() { + final String scriptFile = "META-INF/db/schema-41910to41920.sql"; + final InputStream script = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile); + if (script == null) { + throw new CloudRuntimeException("Unable to find " + scriptFile); + } + + return new InputStream[]{script}; + } + + @Override + public void performDataMigration(Connection conn) { + } + + @Override + public InputStream[] getCleanupScripts() { + final String scriptFile = "META-INF/db/schema-41910to41920-cleanup.sql"; + final InputStream script = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile); + if (script == null) { + throw new CloudRuntimeException("Unable to find " + scriptFile); + } + + return new InputStream[]{script}; + } +} diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41910to41920-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41910to41920-cleanup.sql new file mode 100644 index 00000000000..cb317c69b79 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-41910to41920-cleanup.sql @@ -0,0 +1,23 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you 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. + +--; +-- Schema upgrade cleanup from 4.19.1.0 to 4.19.2.0 +--; + +-- Delete `project_account` entries for users that were removed +DELETE FROM `cloud`.`project_account` WHERE `user_id` IN (SELECT `id` FROM `cloud`.`user` WHERE `removed`); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41910to41920.sql b/engine/schema/src/main/resources/META-INF/db/schema-41910to41920.sql new file mode 100644 index 00000000000..d4c23eee0ed --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-41910to41920.sql @@ -0,0 +1,20 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you 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. + +--; +-- Schema upgrade from 4.19.1.0 to 4.19.2.0 +--; diff --git a/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java b/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java index 961c537d0da..e96b0a00894 100644 --- a/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java +++ b/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java @@ -656,7 +656,7 @@ public class EncryptionSecretKeyChanger { String sqlTemplateDeployAsIsDetails = "SELECT template_deploy_as_is_details.value " + "FROM template_deploy_as_is_details JOIN vm_instance " + "WHERE template_deploy_as_is_details.template_id = vm_instance.vm_template_id " + - "vm_instance.id = %s AND template_deploy_as_is_details.name = '%s' LIMIT 1"; + "AND vm_instance.id = %s AND template_deploy_as_is_details.name = '%s' LIMIT 1"; try (PreparedStatement selectPstmt = conn.prepareStatement("SELECT id, vm_id, name, value FROM user_vm_deploy_as_is_details"); ResultSet rs = selectPstmt.executeQuery(); PreparedStatement updatePstmt = conn.prepareStatement("UPDATE user_vm_deploy_as_is_details SET value=? WHERE id=?") diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index fa177428e51..bea799944be 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -1500,6 +1500,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M *
    *
  • If 'password' is blank, we throw an {@link InvalidParameterValueException}; *
  • If 'current password' is not provided and user is not an Admin, we throw an {@link InvalidParameterValueException}; + *
  • If the user whose password is being changed has a source equal to {@link User.Source#SAML2}, {@link User.Source#SAML2DISABLED} or {@link User.Source#LDAP}, + * we throw an {@link InvalidParameterValueException}; *
  • If a normal user is calling this method, we use {@link #validateCurrentPassword(UserVO, String)} to check if the provided old password matches the database one; *
* @@ -1514,6 +1516,12 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M throw new InvalidParameterValueException("Password cannot be empty or blank."); } + User.Source userSource = user.getSource(); + if (userSource == User.Source.SAML2 || userSource == User.Source.SAML2DISABLED || userSource == User.Source.LDAP) { + logger.warn(String.format("Unable to update the password for user [%d], as its source is [%s].", user.getId(), user.getSource().toString())); + throw new InvalidParameterValueException("CloudStack does not support updating passwords for SAML or LDAP users. Please contact your cloud administrator for assistance."); + } + passwordPolicy.verifyIfPasswordCompliesWithPasswordPolicies(newPassword, user.getUsername(), getAccount(user.getAccountId()).getDomainId()); Account callingAccount = getCurrentCallingAccount(); diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index 11fc69c538c..645c9e5aa67 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -874,6 +874,36 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase { accountManagerImpl.validateUserPasswordAndUpdateIfNeeded(newPassword, userVoMock, currentPassword, false); } + @Test(expected = InvalidParameterValueException.class) + public void validateUserPasswordAndUpdateIfNeededTestSaml2UserShouldNotBeAllowedToUpdateTheirPassword() { + String newPassword = "newPassword"; + String currentPassword = "theCurrentPassword"; + + Mockito.when(userVoMock.getSource()).thenReturn(User.Source.SAML2); + + accountManagerImpl.validateUserPasswordAndUpdateIfNeeded(newPassword, userVoMock, currentPassword, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateUserPasswordAndUpdateIfNeededTestSaml2DisabledUserShouldNotBeAllowedToUpdateTheirPassword() { + String newPassword = "newPassword"; + String currentPassword = "theCurrentPassword"; + + Mockito.when(userVoMock.getSource()).thenReturn(User.Source.SAML2DISABLED); + + accountManagerImpl.validateUserPasswordAndUpdateIfNeeded(newPassword, userVoMock, currentPassword, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateUserPasswordAndUpdateIfNeededTestLdapUserShouldNotBeAllowedToUpdateTheirPassword() { + String newPassword = "newPassword"; + String currentPassword = "theCurrentPassword"; + + Mockito.when(userVoMock.getSource()).thenReturn(User.Source.LDAP); + + accountManagerImpl.validateUserPasswordAndUpdateIfNeeded(newPassword, userVoMock, currentPassword, false); + } + private String configureUserMockAuthenticators(String newPassword) { accountManagerImpl._userPasswordEncoders = new ArrayList<>(); UserAuthenticator authenticatorMock1 = Mockito.mock(UserAuthenticator.class);