From 6ec06aecd76d4a30c0cc532821a3cfa2ad5d5b3e Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 5 Jul 2022 20:27:40 +0530 Subject: [PATCH 1/4] refactor: new line, lint error fix (#6529) Signed-off-by: Abhishek Kumar --- test/integration/smoke/test_events_resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() From 2c49d714b5361d3c0af27a00e7f3d42a6994b733 Mon Sep 17 00:00:00 2001 From: Harikrishna Date: Wed, 6 Jul 2022 09:20:13 +0530 Subject: [PATCH 2/4] scripts: Excluded fe80 or link local address in keystore setup (#6530) This PR fixes the issue #6471 where in keystore setup does not exclude link-local addresses. We have fixed the keystore setup process to exclude the fe80 or link local addresses --- scripts/util/keystore-setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From f1bce69b5dd3a59d22ee54483092b4866a1e5d46 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Wed, 6 Jul 2022 09:26:31 +0530 Subject: [PATCH 3/4] ui: allow instances to be filtered by group (#6495) Fixes #6428 Signed-off-by: Abhishek Kumar --- ui/src/components/view/SearchView.vue | 50 +++++++++++++++++++++++++-- ui/src/config/section/compute.js | 2 +- 2 files changed, 48 insertions(+), 4 deletions(-) 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) From c6b611433b98298fbdf3d6e37fa64051a13b4e2f Mon Sep 17 00:00:00 2001 From: Luis Moreira Date: Wed, 6 Jul 2022 04:58:37 +0100 Subject: [PATCH 4/4] saml: Fix SAML SSO plugin redirect URL (#6457) This PR fixes the issue #6427 -> SAML request must be appended to an IdP URL as a query param with an ampersand, if the URL already contains a question mark, as opposed to always assume that IdP URLs don't have any query params. Google's IdP URL for instance looks like this: https://accounts.google.com/o/saml2/idp?idpid=, therefore the expected redirect URL would be https://accounts.google.com/o/saml2/idp?idpid=&SAMLRequest= This code change is backwards compatible with the current behaviour. --- plugins/user-authenticators/saml2/pom.xml | 6 ++ .../org/apache/cloudstack/saml/SAMLUtils.java | 3 +- .../org/apache/cloudstack/SAMLUtilsTest.java | 73 +++++++++++++++++-- pom.xml | 1 + 4 files changed, 76 insertions(+), 7 deletions(-) diff --git a/plugins/user-authenticators/saml2/pom.xml b/plugins/user-authenticators/saml2/pom.xml index 9cfe5cb390c..11e34ee4a9e 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 08d8d0ba704..eb67edc485c 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