From cd12fa584825889692d88d909132d684b6c6c778 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Wed, 1 Oct 2025 08:43:22 -0400 Subject: [PATCH] Add UUID field for LDAP configuration (#11462) * Add UUID field for LDAP configuration * move db changes to the lastest schema file * Add ID param to list ldapConf API & delete ldapConf API * fix ui test * fix 1 ui test * fix test * fix api description --------- Co-authored-by: dahn --- .../META-INF/db/schema-42100to42200.sql | 6 ++++++ .../command/LdapDeleteConfigurationCmd.java | 8 +++++++- .../api/command/LdapListConfigurationCmd.java | 7 +++++++ .../response/LdapConfigurationResponse.java | 19 +++++++++++++++--- .../cloudstack/ldap/LdapConfigurationVO.java | 20 +++++++++++++++---- .../cloudstack/ldap/LdapManagerImpl.java | 19 ++++++++++++++++-- .../ldap/dao/LdapConfigurationDao.java | 2 +- .../ldap/dao/LdapConfigurationDaoImpl.java | 19 +++++++++++------- ui/src/config/section/config.js | 2 +- ui/src/views/AutogenView.vue | 5 ----- ui/tests/unit/views/AutogenView.spec.js | 12 +++++------ 11 files changed, 89 insertions(+), 30 deletions(-) diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql index 4fcb2b75de5..62ae10b7cc9 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql @@ -26,5 +26,11 @@ CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('router_health_check', 'check_result', ' -- Increase length of scripts_version column to 128 due to md5sum to sha512sum change CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('cloud.domain_router', 'scripts_version', 'scripts_version', 'VARCHAR(128)'); +-- Add uuid column to ldap_configuration table +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.ldap_configuration', 'uuid', 'VARCHAR(40) NOT NULL'); + +-- Populate uuid for existing rows where uuid is NULL or empty +UPDATE `cloud`.`ldap_configuration` SET uuid = UUID() WHERE uuid IS NULL OR uuid = ''; + -- Add the column cross_zone_instance_creation to cloud.backup_repository. if enabled it means that new Instance can be created on all Zones from Backups on this Repository. CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_repository', 'cross_zone_instance_creation', 'TINYINT(1) DEFAULT NULL COMMENT ''Backup Repository can be used for disaster recovery on another zone'''); diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapDeleteConfigurationCmd.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapDeleteConfigurationCmd.java index 15e6c836d0d..0ce7daff432 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapDeleteConfigurationCmd.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapDeleteConfigurationCmd.java @@ -40,8 +40,10 @@ public class LdapDeleteConfigurationCmd extends BaseCmd { @Inject private LdapManager _ldapManager; + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, required = false, entityType = LdapConfigurationResponse.class, description = "ID of the LDAP configuration") + private Long id; - @Parameter(name = ApiConstants.HOST_NAME, type = CommandType.STRING, required = true, description = "Hostname") + @Parameter(name = ApiConstants.HOST_NAME, type = CommandType.STRING, description = "Hostname") private String hostname; @Parameter(name = ApiConstants.PORT, type = CommandType.INTEGER, required = false, description = "port") @@ -71,6 +73,10 @@ public class LdapDeleteConfigurationCmd extends BaseCmd { return domainId; } + public Long getId() { + return id; + } + @Override public void execute() throws ServerApiException { try { diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListConfigurationCmd.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListConfigurationCmd.java index c34d026f89b..c950554b0d1 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListConfigurationCmd.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListConfigurationCmd.java @@ -53,6 +53,9 @@ public class LdapListConfigurationCmd extends BaseListCmd { @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = false, entityType = DomainResponse.class, description = "linked domain") private Long domainId; + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = LdapConfigurationResponse.class, description = "list ldap configuration by ID; when passed, all other parameters are ignored") + private Long id; + @Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, description = "If set to true, " + " and no domainid specified, list all LDAP configurations irrespective of the linked domain", since = "4.13.2") private Boolean listAll; @@ -120,6 +123,10 @@ public class LdapListConfigurationCmd extends BaseListCmd { this.domainId = domainId; } + public Long getId() { + return id; + } + public boolean listAll() { return listAll != null && listAll; } diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/response/LdapConfigurationResponse.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/response/LdapConfigurationResponse.java index 744c73d8e77..4c24b366bb0 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/response/LdapConfigurationResponse.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/response/LdapConfigurationResponse.java @@ -23,10 +23,14 @@ import org.apache.cloudstack.api.BaseResponse; import com.cloud.serializer.Param; import org.apache.cloudstack.api.EntityReference; -import org.apache.cloudstack.ldap.LdapConfiguration; +import org.apache.cloudstack.ldap.LdapConfigurationVO; -@EntityReference(value = LdapConfiguration.class) +@EntityReference(value = LdapConfigurationVO.class) public class LdapConfigurationResponse extends BaseResponse { + @SerializedName("id") + @Param(description = "the ID of the LDAP configuration") + private String id; + @SerializedName(ApiConstants.HOST_NAME) @Param(description = "name of the host running the ldap server") private String hostname; @@ -53,9 +57,18 @@ public class LdapConfigurationResponse extends BaseResponse { setPort(port); } - public LdapConfigurationResponse(final String hostname, final int port, final String domainId) { + public LdapConfigurationResponse(final String hostname, final int port, final String domainId, final String id) { this(hostname, port); setDomainId(domainId); + setId(id); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; } public String getHostname() { diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapConfigurationVO.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapConfigurationVO.java index ee9f0930c47..7e51fe352d9 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapConfigurationVO.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapConfigurationVO.java @@ -23,19 +23,25 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; +import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; +import java.util.UUID; + @Entity @Table(name = "ldap_configuration") -public class LdapConfigurationVO implements InternalIdentity { - @Column(name = "hostname") - private String hostname; - +public class LdapConfigurationVO implements Identity, InternalIdentity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; + @Column(name = "hostname") + private String hostname; + + @Column(name = "uuid") + private String uuid; + @Column(name = "port") private int port; @@ -43,12 +49,14 @@ public class LdapConfigurationVO implements InternalIdentity { private Long domainId; public LdapConfigurationVO() { + this.uuid = UUID.randomUUID().toString(); } public LdapConfigurationVO(final String hostname, final int port, final Long domainId) { this.hostname = hostname; this.port = port; this.domainId = domainId; + this.uuid = UUID.randomUUID().toString(); } public String getHostname() { @@ -60,6 +68,10 @@ public class LdapConfigurationVO implements InternalIdentity { return id; } + public String getUuid() { + return uuid; + } + public int getPort() { return port; } diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java index 05b8578bb42..abf47d4094e 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java @@ -52,6 +52,7 @@ import org.apache.cloudstack.framework.messagebus.MessageSubscriber; import org.apache.cloudstack.ldap.dao.LdapConfigurationDao; import org.apache.cloudstack.ldap.dao.LdapTrustMapDao; import org.apache.commons.lang.Validate; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import com.cloud.domain.DomainVO; @@ -240,7 +241,7 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag domainUuid = domain.getUuid(); } } - return new LdapConfigurationResponse(configuration.getHostname(), configuration.getPort(), domainUuid); + return new LdapConfigurationResponse(configuration.getHostname(), configuration.getPort(), domainUuid, configuration.getUuid()); } @Override @@ -257,6 +258,19 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag @Override public LdapConfigurationResponse deleteConfiguration(final LdapDeleteConfigurationCmd cmd) throws InvalidParameterValueException { + Long id = cmd.getId(); + String hostname = cmd.getHostname(); + if (id == null && StringUtils.isEmpty(hostname)) { + throw new InvalidParameterValueException("Either id or hostname must be specified"); + } + if (id != null) { + final LdapConfigurationVO config = _ldapConfigurationDao.findById(cmd.getId()); + if (config != null) { + _ldapConfigurationDao.remove(config.getId()); + return createLdapConfigurationResponse(config); + } + throw new InvalidParameterValueException("Cannot find configuration with id " + id); + } return deleteConfigurationInternal(cmd.getHostname(), cmd.getPort(), cmd.getDomainId()); } @@ -377,7 +391,8 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag final int port = cmd.getPort(); final Long domainId = cmd.getDomainId(); final boolean listAll = cmd.listAll(); - final Pair, Integer> result = _ldapConfigurationDao.searchConfigurations(hostname, port, domainId, listAll); + final Long id = cmd.getId(); + final Pair, Integer> result = _ldapConfigurationDao.searchConfigurations(id, hostname, port, domainId, listAll); return new Pair, Integer>(result.first(), result.second()); } diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/dao/LdapConfigurationDao.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/dao/LdapConfigurationDao.java index af774b685ed..889efa3ef90 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/dao/LdapConfigurationDao.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/dao/LdapConfigurationDao.java @@ -41,5 +41,5 @@ public interface LdapConfigurationDao extends GenericDao, Integer> searchConfigurations(String hostname, int port, Long domainId); - Pair, Integer> searchConfigurations(String hostname, int port, Long domainId, boolean listAll); + Pair, Integer> searchConfigurations(Long id, String hostname, int port, Long domainId, boolean listAll); } diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/dao/LdapConfigurationDaoImpl.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/dao/LdapConfigurationDaoImpl.java index c053e87b6bf..67d09feed90 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/dao/LdapConfigurationDaoImpl.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/dao/LdapConfigurationDaoImpl.java @@ -48,6 +48,7 @@ public class LdapConfigurationDaoImpl extends GenericDaoBase sc = getSearchCriteria(hostname, port, domainId, false); + SearchCriteria sc = getSearchCriteria(null, hostname, port, domainId, false); return findOneBy(sc); } @Override public LdapConfigurationVO find(String hostname, int port, Long domainId, boolean listAll) { - SearchCriteria sc = getSearchCriteria(hostname, port, domainId, listAll); + SearchCriteria sc = getSearchCriteria(null, hostname, port, domainId, listAll); return findOneBy(sc); } @Override public Pair, Integer> searchConfigurations(final String hostname, final int port, final Long domainId) { - SearchCriteria sc = getSearchCriteria(hostname, port, domainId, false); + SearchCriteria sc = getSearchCriteria(null, hostname, port, domainId, false); return searchAndCount(sc, null); } @Override - public Pair, Integer> searchConfigurations(final String hostname, final int port, final Long domainId, final boolean listAll) { - SearchCriteria sc = getSearchCriteria(hostname, port, domainId, listAll); + public Pair, Integer> searchConfigurations(final Long id, final String hostname, final int port, final Long domainId, final boolean listAll) { + SearchCriteria sc = getSearchCriteria(id, hostname, port, domainId, listAll); return searchAndCount(sc, null); } - private SearchCriteria getSearchCriteria(String hostname, int port, Long domainId,boolean listAll) { + private SearchCriteria getSearchCriteria(Long id, String hostname, int port, Long domainId,boolean listAll) { SearchCriteria sc; - if (domainId != null) { + if (id != null) { + // If id is present, ignore all other parameters + sc = listDomainConfigurationsSearch.create(); + sc.setParameters("id", id); + } else if (domainId != null) { // If domainid is present, ignore listall sc = listDomainConfigurationsSearch.create(); sc.setParameters("domain_id", domainId); diff --git a/ui/src/config/section/config.js b/ui/src/config/section/config.js index e69f6d1e6c4..1961186e0bb 100644 --- a/ui/src/config/section/config.js +++ b/ui/src/config/section/config.js @@ -41,7 +41,7 @@ export default { permission: ['listLdapConfigurations'], searchFilters: ['domainid', 'hostname', 'port'], columns: ['hostname', 'port', 'domainid'], - details: ['hostname', 'port', 'domainid'], + details: ['id', 'hostname', 'port', 'domainid'], actions: [ { api: 'addLdapConfiguration', diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue index f55767ded2a..765515033ef 100644 --- a/ui/src/views/AutogenView.vue +++ b/ui/src/views/AutogenView.vue @@ -1078,8 +1078,6 @@ export default { } if (this.$route.path.startsWith('/vmsnapshot/')) { params.vmsnapshotid = this.$route.params.id - } else if (this.$route.path.startsWith('/ldapsetting/')) { - params.hostname = this.$route.params.id } if (this.$route.path.startsWith('/tungstenpolicy/')) { params.policyuuid = this.$route.params.id @@ -1192,9 +1190,6 @@ export default { this.items[idx][key] = func(this.items[idx]) } } - if (this.$route.path.startsWith('/ldapsetting')) { - this.items[idx].id = this.items[idx].hostname - } } if (this.items.length > 0) { if (!this.showAction || this.dataView) { diff --git a/ui/tests/unit/views/AutogenView.spec.js b/ui/tests/unit/views/AutogenView.spec.js index 6b1085637b3..16e8ae219e3 100644 --- a/ui/tests/unit/views/AutogenView.spec.js +++ b/ui/tests/unit/views/AutogenView.spec.js @@ -652,12 +652,12 @@ describe('Views > AutogenView.vue', () => { testapinamecase1response: { count: 0, testapinamecase1: [{ - id: 'test-id-1', + id: 'uuid1', name: 'test-name-1' }] } }) - await router.push({ name: 'testRouter13', params: { id: 'test-id' } }) + await router.push({ name: 'testRouter13', params: { id: 'uuid1' } }) await flushPromises() expect(mockAxios).toHaveBeenCalled() @@ -668,8 +668,7 @@ describe('Views > AutogenView.vue', () => { command: 'testApiNameCase1', response: 'json', listall: true, - id: 'test-id', - hostname: 'test-id', + id: 'uuid1', page: 1, pagesize: 20 }) @@ -777,6 +776,7 @@ describe('Views > AutogenView.vue', () => { testapinamecase1response: { count: 1, testapinamecase1: [{ + id: 'uuid1', name: 'test-name-value', hostname: 'test-hostname-value' }] @@ -786,13 +786,13 @@ describe('Views > AutogenView.vue', () => { await flushPromises() expect(wrapper.vm.items).toEqual([{ - id: 'test-hostname-value', + id: 'uuid1', name: 'test-name-value', hostname: 'test-hostname-value', key: 0 }]) expect(wrapper.vm.resource).toEqual({ - id: 'test-hostname-value', + id: 'uuid1', name: 'test-name-value', hostname: 'test-hostname-value', key: 0