diff --git a/plugins/user-authenticators/saml2/pom.xml b/plugins/user-authenticators/saml2/pom.xml index e0e6688d637..7de21c05caf 100644 --- a/plugins/user-authenticators/saml2/pom.xml +++ b/plugins/user-authenticators/saml2/pom.xml @@ -53,5 +53,11 @@ ${cs.xercesImpl.version} test + + org.assertj + assertj-core + ${cs.assertj.version} + test + diff --git a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java index cbbdbd28bf8..c65e4c09be6 100644 --- a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java +++ b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java @@ -150,7 +150,8 @@ public class SAMLUtils { if (spMetadata.getKeyPair() != null) { privateKey = spMetadata.getKeyPair().getPrivate(); } - redirectUrl = idpMetadata.getSsoUrl() + "?" + SAMLUtils.generateSAMLRequestSignature("SAMLRequest=" + SAMLUtils.encodeSAMLRequest(authnRequest), privateKey, signatureAlgorithm); + String appendOperator = idpMetadata.getSsoUrl().contains("?") ? "&" : "?"; + redirectUrl = idpMetadata.getSsoUrl() + appendOperator + SAMLUtils.generateSAMLRequestSignature("SAMLRequest=" + SAMLUtils.encodeSAMLRequest(authnRequest), privateKey, signatureAlgorithm); } catch (ConfigurationException | FactoryConfigurationError | MarshallingException | IOException | NoSuchAlgorithmException | InvalidKeyException | java.security.SignatureException e) { s_logger.error("SAML AuthnRequest message building error: " + e.getMessage()); } diff --git a/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/SAMLUtilsTest.java b/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/SAMLUtilsTest.java index 433fdf3224a..cf265199b42 100644 --- a/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/SAMLUtilsTest.java +++ b/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/SAMLUtilsTest.java @@ -19,18 +19,22 @@ package org.apache.cloudstack; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.regex.Pattern; - +import junit.framework.TestCase; +import org.apache.cloudstack.saml.SAML2AuthManager; +import org.apache.cloudstack.saml.SAMLProviderMetadata; import org.apache.cloudstack.saml.SAMLUtils; import org.apache.cloudstack.utils.security.CertUtils; import org.junit.Test; import org.opensaml.saml2.core.AuthnRequest; import org.opensaml.saml2.core.LogoutRequest; -import junit.framework.TestCase; +import java.net.URI; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.assertThat; public class SAMLUtilsTest extends TestCase { @@ -60,6 +64,63 @@ public class SAMLUtilsTest extends TestCase { assertEquals(req.getIssuer().getValue(), spId); } + @Test + public void testBuildAuthnRequestUrlWithoutQueryParam() throws Exception { + String urlScheme = "http"; + + String spDomain = "sp.domain.example"; + String spUrl = urlScheme + "://" + spDomain; + String spId = "serviceProviderId"; + + String idpDomain = "idp.domain.example"; + String idpUrl = urlScheme + "://" + idpDomain; + String idpId = "identityProviderId"; + + String authnId = SAMLUtils.generateSecureRandomId(); + + SAMLProviderMetadata spMetadata = new SAMLProviderMetadata(); + spMetadata.setEntityId(spId); + spMetadata.setSsoUrl(spUrl); + + SAMLProviderMetadata idpMetadata = new SAMLProviderMetadata(); + idpMetadata.setSsoUrl(idpUrl); + idpMetadata.setEntityId(idpId); + + URI redirectUrl = new URI(SAMLUtils.buildAuthnRequestUrl(authnId, spMetadata, idpMetadata, SAML2AuthManager.SAMLSignatureAlgorithm.value())); + assertThat(redirectUrl).hasScheme(urlScheme).hasHost(idpDomain).hasParameter("SAMLRequest"); + assertEquals(urlScheme, redirectUrl.getScheme()); + assertEquals(idpDomain, redirectUrl.getHost()); + } + + @Test + public void testBuildAuthnRequestUrlWithQueryParam() throws Exception { + String urlScheme = "http"; + + String spDomain = "sp.domain.example"; + String spUrl = urlScheme + "://" + spDomain; + String spId = "cloudstack"; + + String idpDomain = "idp.domain.example"; + String idpQueryParam = "idpid=CX1298373"; + String idpUrl = urlScheme + "://" + idpDomain + "?" + idpQueryParam; + String idpId = "identityProviderId"; + + String authnId = SAMLUtils.generateSecureRandomId(); + + SAMLProviderMetadata spMetadata = new SAMLProviderMetadata(); + spMetadata.setEntityId(spId); + spMetadata.setSsoUrl(spUrl); + + SAMLProviderMetadata idpMetadata = new SAMLProviderMetadata(); + idpMetadata.setSsoUrl(idpUrl); + idpMetadata.setEntityId(idpId); + + URI redirectUrl = new URI(SAMLUtils.buildAuthnRequestUrl(authnId, spMetadata, idpMetadata, SAML2AuthManager.SAMLSignatureAlgorithm.value())); + assertThat(redirectUrl).hasScheme(urlScheme).hasHost(idpDomain).hasParameter("idpid").hasParameter("SAMLRequest"); + assertEquals(urlScheme, redirectUrl.getScheme()); + assertEquals(idpDomain, redirectUrl.getHost()); + } + @Test public void testBuildLogoutRequest() throws Exception { String logoutUrl = "http://logoutUrl"; diff --git a/pom.xml b/pom.xml index f2dfbca68dc..6fafb5fd017 100644 --- a/pom.xml +++ b/pom.xml @@ -118,6 +118,7 @@ 7.1.0 2.27.2 2.12.2 + 3.23.1 5.10.0 diff --git a/scripts/util/keystore-setup b/scripts/util/keystore-setup index 65f04c48d57..8ca6cc77baa 100755 --- a/scripts/util/keystore-setup +++ b/scripts/util/keystore-setup @@ -44,7 +44,7 @@ keytool -genkey -storepass "$KS_PASS" -keypass "$KS_PASS" -alias "$ALIAS" -keyal # Generate CSR rm -f "$CSR_FILE" -addresses=$(ip address | grep inet | awk '{print $2}' | sed 's/\/.*//g' | grep -v '^169.254.' | grep -v '^127.0.0.1' | grep -v '^::1' | sed 's/^/ip:/g' | tr '\r\n' ',') +addresses=$(ip address | grep inet | awk '{print $2}' | sed 's/\/.*//g' | grep -v '^169.254.' | grep -v '^127.0.0.1' | egrep -v '^::1|^fe80' | grep -v '^::1' | sed 's/^/ip:/g' | tr '\r\n' ',') keytool -certreq -storepass "$KS_PASS" -alias "$ALIAS" -file $CSR_FILE -keystore "$KS_FILE" -ext san="$addresses" > /dev/null 2>&1 cat "$CSR_FILE" diff --git a/test/integration/smoke/test_events_resource.py b/test/integration/smoke/test_events_resource.py index 352771443b2..4d6872af57e 100644 --- a/test/integration/smoke/test_events_resource.py +++ b/test/integration/smoke/test_events_resource.py @@ -194,4 +194,4 @@ class TestEventsResource(cloudstackTestCase): @classmethod def tearDownClass(cls): - super(TestEventsResource, cls).tearDownClass() \ No newline at end of file + super(TestEventsResource, cls).tearDownClass() diff --git a/ui/src/components/view/SearchView.vue b/ui/src/components/view/SearchView.vue index 48345218693..2c350de816d 100644 --- a/ui/src/components/view/SearchView.vue +++ b/ui/src/components/view/SearchView.vue @@ -54,8 +54,7 @@ :ref="field.name" :name="field.name" :key="index" - :label="field.name==='keyword' ? - ('listAnnotations' in $store.getters.apis ? $t('label.annotation') : $t('label.name')) : $t('label.' + field.name)"> + :label="retrieveFieldLabel(field.name)"> item.name === 'groupid') + this.fields[groupIndex].loading = true + promises.push(await this.fetchInstanceGroups()) + } + if (arrayField.includes('entitytype')) { const entityTypeIndex = this.fields.findIndex(item => item.name === 'entitytype') this.fields[entityTypeIndex].loading = true @@ -378,6 +400,12 @@ export default { this.fields[clusterIndex].opts = this.sortArray(cluster[0].data) } } + if (groupIndex > -1) { + const groups = response.filter(item => item.type === 'groupid') + if (groups && groups.length > 0) { + this.fields[groupIndex].opts = this.sortArray(groups[0].data) + } + } }).finally(() => { if (zoneIndex > -1) { this.fields[zoneIndex].loading = false @@ -391,6 +419,9 @@ export default { if (clusterIndex > -1) { this.fields[clusterIndex].loading = false } + if (groupIndex > -1) { + this.fields[groupIndex].loading = false + } this.fillFormFieldValues() }) }, @@ -468,6 +499,19 @@ export default { }) }) }, + fetchInstanceGroups () { + return new Promise((resolve, reject) => { + api('listInstanceGroups', { listAll: true }).then(json => { + const instancegroups = json.listinstancegroupsresponse.instancegroup + resolve({ + type: 'groupid', + data: instancegroups + }) + }).catch(error => { + reject(error.response.headers['x-description']) + }) + }) + }, fetchGuestNetworkTypes () { const types = [] if (this.apiName.indexOf('listNetworks') > -1) { diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index 447bcf3243b..5249185651d 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -75,7 +75,7 @@ export default { } return fields }, - searchFilters: ['name', 'zoneid', 'domainid', 'account', 'tags'], + searchFilters: ['name', 'zoneid', 'domainid', 'account', 'groupid', 'tags'], details: () => { var fields = ['displayname', 'name', 'id', 'state', 'ipaddress', 'ip6address', 'templatename', 'ostypename', 'serviceofferingname', 'isdynamicallyscalable', 'haenable', 'hypervisor', 'boottype', 'bootmode', 'account', 'domain', 'zonename'] const listZoneHaveSGEnabled = store.getters.zones.filter(zone => zone.securitygroupsenabled === true)