From fbae2251e1fbb00b3f3bb33bdaf29c8720f35fad Mon Sep 17 00:00:00 2001 From: Alex Szczuczko Date: Tue, 3 Oct 2023 01:06:55 -0600 Subject: [PATCH 01/19] Add setup erase command to example for adding more RPMs to the server container (#23639) Closes #23637 --- docs/guides/server/containers.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/guides/server/containers.adoc b/docs/guides/server/containers.adoc index d7629193b834..d76f9a84cbc0 100644 --- a/docs/guides/server/containers.adoc +++ b/docs/guides/server/containers.adoc @@ -101,7 +101,9 @@ It is possible to install new RPMs if absolutely required, following this two-st ---- FROM registry.access.redhat.com/ubi9 AS ubi-micro-build RUN mkdir -p /mnt/rootfs -RUN dnf install --installroot /mnt/rootfs --releasever 9 --setopt install_weak_deps=false --nodocs -y; dnf --installroot /mnt/rootfs clean all +RUN dnf install --installroot /mnt/rootfs --releasever 9 --setopt install_weak_deps=false --nodocs -y && \ + dnf --installroot /mnt/rootfs clean all && \ + rpm --root /mnt/rootfs -e --nodeps setup FROM quay.io/keycloak/keycloak COPY --from=ubi-micro-build /mnt/rootfs / From ceea11d044e05ef7dfdc2e6638ee1eed77b3926b Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 3 Oct 2023 02:42:57 -0500 Subject: [PATCH 02/19] Fix various bugs and issues in crypto/elytron (#23102) closes #23173 --- .../elytron/ElytronCertificateUtils.java | 30 +++++-- .../elytron/ElytronECDSACryptoProvider.java | 30 ++++++- .../crypto/elytron/ElytronOCSPProvider.java | 72 ++++++++++++----- .../ElytronUserIdentityExtractorProvider.java | 39 +++++---- .../test/CRLDistributionPointTest.java | 81 +++++++++++++++++-- .../crypto/elytron/test/ElytronHmacTest.java | 8 +- .../test/ElytronKeyStoreTypesTest.java | 4 +- .../elytron/test/ElytronPemUtilsTest.java | 25 ++++++ .../elytron/test/ElytronSignatureAlgTest.java | 52 ++++++++++++ 9 files changed, 286 insertions(+), 55 deletions(-) create mode 100644 crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronSignatureAlgTest.java diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronCertificateUtils.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronCertificateUtils.java index 0bf862b937d2..88add31161e4 100644 --- a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronCertificateUtils.java +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronCertificateUtils.java @@ -37,6 +37,7 @@ import javax.security.auth.x500.X500Principal; +import org.jboss.logging.Logger; import org.keycloak.common.crypto.CertificateUtilsProvider; import org.wildfly.security.asn1.ASN1; import org.wildfly.security.asn1.DERDecoder; @@ -60,6 +61,8 @@ */ public class ElytronCertificateUtils implements CertificateUtilsProvider { + Logger log = Logger.getLogger(getClass()); + /** * Generates version 3 {@link java.security.cert.X509Certificate}. * @@ -249,16 +252,29 @@ public List getCRLDistributionPoints(X509Certificate cert) throws IOExce case ASN1.UTF8_STRING_TYPE: distPointUrls.add(der.decodeUtf8String()); break; - case 0xa0: - der.decodeImplicit(0xa0); - byte[] edata = der.decodeOctetString(); - while(!Character.isLetterOrDigit(edata[0])) { - edata = Arrays.copyOfRange(edata, 1, edata.length); - } - distPointUrls.add(new String(edata)); + case 0xa0: // Decode CRLDistributionPoint FullName list + der.startExplicit(0xa0); + break; + case 0x86: // Decode CRLDistributionPoint FullName + der.decodeImplicit(0x86); + distPointUrls.add(der.decodeOctetStringAsString()); + log.debug("Adding Dist point name: " + distPointUrls.get(distPointUrls.size()-1)); break; default: der.skipElement(); + } + // Check to see if there is another sequence to process + try { + if(!der.hasNextElement() && der.peekType() == ASN1.SEQUENCE_TYPE) { + der.startSequence(); + } else if (!der.hasNextElement() && der.peekType() == 0xa0) { + der.startExplicit(0xa0); + } + + } catch(Exception e) { + // Just log this error. Likely the Dist points have been parsed, but + // the end of the cert is failing to parse. + log.warn("There is an issue parsing the certificate for Distribution Points", e); } } diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronECDSACryptoProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronECDSACryptoProvider.java index ca75b06d02ea..b5f296ebc3b8 100644 --- a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronECDSACryptoProvider.java +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronECDSACryptoProvider.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.math.BigInteger; +import org.jboss.logging.Logger; import org.keycloak.common.crypto.ECDSACryptoProvider; import org.wildfly.security.asn1.DERDecoder; import org.wildfly.security.asn1.DEREncoder; @@ -28,6 +29,8 @@ */ public class ElytronECDSACryptoProvider implements ECDSACryptoProvider { + Logger log = Logger.getLogger(getClass()); + @Override public byte[] concatenatedRSToASN1DER(final byte[] signature, int signLength) throws IOException { int len = signLength / 2; @@ -45,6 +48,7 @@ public byte[] concatenatedRSToASN1DER(final byte[] signature, int signLength) th seq.startSequence(); seq.encodeInteger(rBigInteger); seq.encodeInteger(sBigInteger); + seq.endSequence(); return seq.getEncoded(); @@ -56,13 +60,35 @@ public byte[] asn1derToConcatenatedRS(final byte[] derEncodedSignatureValue, int DERDecoder der = new DERDecoder(derEncodedSignatureValue); der.startSequence(); - byte[] r = der.decodeInteger().toByteArray(); - byte[] s = der.decodeInteger().toByteArray(); + byte[] r = convertToBytes(der.decodeInteger(),len); + byte[] s = convertToBytes(der.decodeInteger(),len); + der.endSequence(); byte[] concatenatedSignatureValue = new byte[signLength]; + System.arraycopy(r, 0, concatenatedSignatureValue, 0, len); System.arraycopy(s, 0, concatenatedSignatureValue, len, len); return concatenatedSignatureValue; } + // If byte array length doesn't match expected length, copy to new + // byte array of the expected length + private byte[] convertToBytes(BigInteger decodeInteger, int len) { + + byte[] bytes = decodeInteger.toByteArray(); + + if(len < bytes.length) { + log.debug("Decoded integer byte length greater than expected."); + byte[] t = new byte[len]; + System.arraycopy(bytes, bytes.length - len, t, 0, len); + bytes = t; + } else if (len > bytes.length) { + log.debug("Decoded integer byte length less than expected."); + byte[] t = new byte[len]; + System.arraycopy(bytes, 0, t, len - bytes.length, bytes.length); + bytes = t; + } + return bytes; + } + } diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronOCSPProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronOCSPProvider.java index 7054578da4ea..f312ffa3c6a5 100644 --- a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronOCSPProvider.java +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronOCSPProvider.java @@ -16,24 +16,42 @@ */ package org.keycloak.crypto.elytron; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidAlgorithmParameterException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CRLReason; +import java.security.cert.CertPath; +import java.security.cert.CertPathBuilder; +import java.security.cert.CertPathBuilderException; +import java.security.cert.CertPathValidator; import java.security.cert.CertPathValidatorException; +import java.security.cert.CertPathValidatorResult; +import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.PKIXBuilderParameters; +import java.security.cert.PKIXCertPathValidatorResult; +import java.security.cert.PKIXParameters; +import java.security.cert.PKIXRevocationChecker; +import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Date; +import java.util.EnumSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import javax.net.ssl.TrustManagerFactory; import org.jboss.logging.Logger; +import org.keycloak.common.util.PemUtils; import org.keycloak.models.KeycloakSession; import org.keycloak.utils.OCSPProvider; import org.wildfly.security.asn1.ASN1; @@ -61,31 +79,46 @@ public class ElytronOCSPProvider extends OCSPProvider { * @throws CertPathValidatorException */ @Override - protected OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert, X509Certificate issuerCertificate, List responderURIs, X509Certificate responderCert, Date date) throws CertPathValidatorException { + protected OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert, + X509Certificate issuerCertificate, List responderURIs, X509Certificate responderCert, Date date) + throws CertPathValidatorException { if (responderURIs == null || responderURIs.size() == 0) throw new IllegalArgumentException("Need at least one responder"); - try { + try { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - trustStore.load(null,"pass".toCharArray()); - trustStore.setCertificateEntry("trust", cert); - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - - - X509RevocationTrustManager trustMgr = X509RevocationTrustManager.builder() - .setOcspResponderCert(responderCert) - .setTrustStore(trustStore) - .setTrustManagerFactory(trustManagerFactory) - .build() - ; - - X509Certificate[] certs = { cert }; - trustMgr.checkClientTrusted(certs, cert.getType()); - } catch (NoSuchAlgorithmException | CertificateException | IOException | KeyStoreException e) { + trustStore.load(null, "pass".toCharArray()); + trustStore.setCertificateEntry("trust", issuerCertificate); + + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX"); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX"); + PKIXRevocationChecker rc = (PKIXRevocationChecker) cpb.getRevocationChecker(); + X509CertSelector certSelector = new X509CertSelector(); + + X509Certificate[] certs = { cert }; + certSelector.setCertificate(cert); + certSelector.setCertificateValid(date); + + CertPath cp = cf.generateCertPath(Arrays.asList(certs)); + + PKIXParameters params = new PKIXBuilderParameters(trustStore, certSelector); + + rc.setOcspResponder(responderURIs.get(0)); + rc.setOcspResponderCert(responderCert); + rc.setOptions(EnumSet.noneOf(PKIXRevocationChecker.Option.class)); + params.setRevocationEnabled(false); + params.addCertPathChecker(rc); + + PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) cpv.validate(cp, params); + logger.debug("Certificate validated by CA: " + result.getTrustAnchor().getCAName()); + + } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | CertificateException | IOException + | KeyStoreException e) { logger.warn("OSCP Response check failed.", e); return unknownStatus(); } - + return new OCSPRevocationStatus() { @Override @@ -102,7 +135,7 @@ public Date getRevocationTime() { public CRLReason getRevocationReason() { return null; } - + }; } @@ -155,6 +188,7 @@ protected List getResponderURIs(X509Certificate cert) throws Certificate } } + logger.warn("OCSP Responder URIs" + Arrays.toString(responderURIs.toArray())); return responderURIs; } } diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronUserIdentityExtractorProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronUserIdentityExtractorProvider.java index eb5c4283326d..57ddba578cd1 100644 --- a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronUserIdentityExtractorProvider.java +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronUserIdentityExtractorProvider.java @@ -39,7 +39,7 @@ */ public class ElytronUserIdentityExtractorProvider extends UserIdentityExtractorProvider { - private static final Logger logger = Logger.getLogger(ElytronUserIdentityExtractorProvider.class.getName()); + private Logger log = Logger.getLogger(this.getClass()); class X500NameRDNExtractorElytronProvider extends X500NameRDNExtractor { @@ -47,8 +47,13 @@ class X500NameRDNExtractorElytronProvider extends X500NameRDNExtractor { Function x500Name; public X500NameRDNExtractorElytronProvider(String attrName, Function x500Name) { - //this.x500NameStyle = BCStyle.INSTANCE.attrNameToOID(attrName); + // The OidsUtil fails to map 'EmailAddress', instead 'E' is mapped to the OID. + // TODO: Open an issue with wildfly-elytron to include 'EmailAddress' in the oid mapping + if(attrName.equals("EmailAddress")) { + attrName = "E"; + } this.x500NameStyle = OidsUtil.attributeNameToOid(OidsUtil.Category.RDN, attrName); + log.debug("Attribute Name: " + attrName + " X500NameStyle OID: " + x500NameStyle); this.x500Name = x500Name; } @@ -59,6 +64,7 @@ public Object extractUserIdentity(X509Certificate[] certs) { throw new IllegalArgumentException(); Principal name = x500Name.apply(certs); + log.debug("Principal Name " + name.getName()); X500AttributePrincipalDecoder xDecoder = new X500AttributePrincipalDecoder(x500NameStyle); String cn = xDecoder.apply(name); @@ -95,13 +101,14 @@ public Object extractUserIdentity(X509Certificate[] certs) { } String subjectName = null; - logger.info("SubjPrinc " + certs[0].getSubjectX500Principal()); + log.debug("SubjPrinc " + certs[0].getSubjectX500Principal()); Collection> subjectAlternativeNames; try { subjectAlternativeNames = certs[0].getSubjectAlternativeNames(); if (subjectAlternativeNames == null) { return null; } + log.info(Arrays.toString(subjectAlternativeNames.toArray())); for (List sbjAltName : subjectAlternativeNames) { if (sbjAltName == null) continue; @@ -109,15 +116,13 @@ public Object extractUserIdentity(X509Certificate[] certs) { Integer nameType = (Integer) sbjAltName.get(0); if (nameType == generalName) { - logger.info("sbjAltName Type " + nameType); - logger.info("sbjAltName[1]: " + sbjAltName.get(1)); - Object sbjObj = sbjAltName.get(1); switch (nameType) { case GeneralName.RFC_822_NAME: case GeneralName.DNS_NAME: case GeneralName.DIRECTORY_NAME: + case GeneralName.URI_NAME: subjectName = (String) sbjObj; break; case GeneralName.OTHER_NAME: @@ -126,12 +131,12 @@ public Object extractUserIdentity(X509Certificate[] certs) { boolean upnOidFound = false; while (derDecoder.hasNextElement() && !upnOidFound) { int asn1Type = derDecoder.peekType(); - logger.info("ASN.1 Type: " + derDecoder.peekType()); + log.debug("ASN.1 Type: " + derDecoder.peekType()); switch (asn1Type) { case ASN1.OBJECT_IDENTIFIER_TYPE: String oid = derDecoder.decodeObjectIdentifier(); - logger.info("OID: " + oid); + log.debug("OID: " + oid); if(UPN_OID.equals(oid)) { derDecoder.decodeImplicit(160); byte[] sb = derDecoder.drainElementValue(); @@ -154,22 +159,28 @@ public Object extractUserIdentity(X509Certificate[] certs) { case ASN1.OCTET_STRING_TYPE: subjectName = derDecoder.decodeOctetStringAsString(); break; + case 0xa0: + derDecoder.startExplicit(asn1Type); + break; + case ASN1.SEQUENCE_TYPE: + derDecoder.startSequence(); + default: + derDecoder.skipElement(); } } - logger.info("Subject Alt Name: " + subjectName); } - + } - + } } catch (CertificateParsingException | UnsupportedEncodingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + log.error("Failed to parse Subject Name:",e); } - + + log.debug("Subject Alt Name: " + subjectName); return subjectName; } } diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/CRLDistributionPointTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/CRLDistributionPointTest.java index d42e60483c40..6f5fb4df3297 100644 --- a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/CRLDistributionPointTest.java +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/CRLDistributionPointTest.java @@ -18,18 +18,22 @@ import static org.junit.Assert.assertArrayEquals; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import javax.security.auth.x500.X500Principal; import org.junit.Test; +import org.keycloak.common.util.PemUtils; import org.keycloak.crypto.elytron.ElytronCertificateUtils; import org.wildfly.security.x500.GeneralName; import org.wildfly.security.x500.cert.CRLDistributionPoint; @@ -46,9 +50,9 @@ public class CRLDistributionPointTest { @Test public void getCrlDistPoint() throws CertificateException, NoSuchAlgorithmException, IOException { - X509Certificate cert = createCRLcert(); + X509Certificate cert = createCRLcert(1,1); List expect = new ArrayList<>(); - expect.add("http://crl.test.com"); + expect.add("http://crl0.test0.com"); ElytronCertificateUtils bcutil = new ElytronCertificateUtils(); @@ -58,16 +62,67 @@ public void getCrlDistPoint() throws CertificateException, NoSuchAlgorithmExcept } - private X509Certificate createCRLcert() throws CertificateException, NoSuchAlgorithmException { + @Test + public void getCrlDistPointMultiNames() throws CertificateException, NoSuchAlgorithmException, IOException { + + X509Certificate cert = createCRLcert(1,2); + List expect = new ArrayList<>(); + expect.add("http://crl0.test0.com"); + expect.add("http://crl0.test1.com"); + + ElytronCertificateUtils bcutil = new ElytronCertificateUtils(); + List crldp = bcutil.getCRLDistributionPoints(cert); + + assertArrayEquals(expect.toArray(), crldp.toArray()); + + } + + @Test + public void getMultiCrlDistPointMultiNames() throws CertificateException, NoSuchAlgorithmException, IOException { + + X509Certificate cert = createCRLcert(2,2); + List expect = new ArrayList<>(); + expect.add("http://crl0.test0.com"); + expect.add("http://crl0.test1.com"); + expect.add("http://crl1.test0.com"); + expect.add("http://crl1.test1.com"); + + ElytronCertificateUtils bcutil = new ElytronCertificateUtils(); + List crldp = bcutil.getCRLDistributionPoints(cert); + + assertArrayEquals(expect.toArray(), crldp.toArray()); + + } + + @Test + public void revokedCertCRLDistTest() throws CertificateException, IOException { + X509Certificate cert = revokedCert(); + List expect = new ArrayList<>(); + expect.add("http://localhost:8889/empty.crl"); + expect.add("http://localhost:8889/intermediate-ca.crl"); + + ElytronCertificateUtils bcutil = new ElytronCertificateUtils(); + List crldp = bcutil.getCRLDistributionPoints(cert); + + assertArrayEquals(expect.toArray(), crldp.toArray()); + + } + + private X509Certificate createCRLcert(int crldistcount, int namecount) throws CertificateException, NoSuchAlgorithmException { X500Principal dn = new X500Principal("CN=testuser,OU=UNIT,O=TST"); List distributionPoints = new ArrayList<>(); - List fullName = new ArrayList<>(); - fullName.add(new GeneralName.URIName("http://crl.test.com")); - DistributionPointName distributionPoint = new FullNameDistributionPointName(fullName); - CRLDistributionPoint arg0 = new CRLDistributionPoint(distributionPoint, null, null); - distributionPoints.add(arg0); + for(int x = 0; x fullName = new ArrayList<>(); + for(int y = 0; yMarek Posolda */ @@ -44,7 +42,7 @@ public class ElytronKeyStoreTypesTest { @Test public void testKeystoreFormats() { Set supportedKeystoreFormats = CryptoIntegration.getProvider().getSupportedKeyStoreTypes().collect(Collectors.toSet()); - assertThat(supportedKeystoreFormats, Matchers.containsInAnyOrder( + Assert.assertThat(supportedKeystoreFormats, Matchers.containsInAnyOrder( KeystoreUtil.KeystoreFormat.JKS, KeystoreUtil.KeystoreFormat.PKCS12 )); diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronPemUtilsTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronPemUtilsTest.java index 4653cbc78e17..862ec91b923d 100644 --- a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronPemUtilsTest.java +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronPemUtilsTest.java @@ -18,10 +18,14 @@ import static org.junit.Assert.assertEquals; +import java.security.KeyPair; import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import org.junit.ClassRule; import org.junit.Test; +import org.keycloak.common.crypto.CryptoIntegration; +import org.keycloak.common.crypto.PemUtilsProvider; import org.keycloak.rule.CryptoInitRule; public class ElytronPemUtilsTest { @@ -42,5 +46,26 @@ public void testGenerateThumbprintSha256() throws NoSuchAlgorithmException { String encoded = org.keycloak.common.util.PemUtils.generateThumbprint(test, "SHA-256"); assertEquals(43, encoded.length()); } + + + @Test + public void testenocdedecode() throws NoSuchAlgorithmException, NoSuchProviderException { + PemUtilsProvider pemutil = CryptoIntegration.getProvider().getPemUtils(); + + KeyPair keypair = CryptoIntegration.getProvider().getKeyPairGen("RSA").generateKeyPair(); + String pem = pemutil.encodeKey(keypair.getPrivate()); + + Object decodekey = pemutil.decodePrivateKey(pem); + + } + + @Test + public void testtrkey() { + String key = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKtWsK5O0CtuBpnMvWG+HTG0vmZzujQ2o9WdheQu+BzCILcGMsbDW0YQaglpcO5JpGWWhubnckGGPHfdQ2/7nP9QwbiTK0FbGF41UqcvoaCqU1psxoV88s8IXyQCAqeyLv00yj6foqdJjxh5SZ5z+na+M7Y2OxIBVxYRAxWEnfUvAgMBAAECgYB+Y7yBWHIHF2qXGYi6CVvPxtyNBuFcktHYShLyeBNeY3VujYv3QzSZQpJ1zuoXXQuARMHOovyNiVAhu357pMfx9wSkoKNSXKrQx/+9Vt9lI1pXJxjXedPOjbuI/JZAcrk0u4nOfXG/HGtR5cjoDZYWkYQEtsePCnHlZAb0D7axwQJBAO92f00Tvkc9NU/EGqwR3bPXRMqSX0JnG7XRBvLeJBCZYsQn0s2bLdpy8qsTeAyJg1ZvrEc8qIio5HVqzsvbhpMCQQC3K9A6UK+vmQCNWqsQpdqWPRPN7CPB67FzSmyS8CtMjY6jTvSHrkamggotz2N/5QDr1xG2q7A/3dpkq1bTpTx1AkAXZjjiSz+Yrn57IOqKTeSgIjTypoLwdirbBWXsbZCQnqxsBogu1y8P3ZOg6/IbJ4TR+W+YNnExiW9pmdpDSVxJAkEAplTq6YmLf/F4RuQmox94tyUPbtcYQWg942uZ3HSrXQDOng18kBj5nwpHJAJHYEQb6g2K0E5n5hcX0oKkfdx2YQJAcSKAmFiD7KQ6+vVqJlQwVPvYdTSOeZB7YVV6S4b4slS3ZObsa0yNMWgal/QnCtW5k3f185gCWj6dOLGB5btfxg=="; + + PemUtilsProvider pemutil = CryptoIntegration.getProvider().getPemUtils(); + + pemutil.decodePrivateKey(key); + } } diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronSignatureAlgTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronSignatureAlgTest.java new file mode 100644 index 000000000000..a9e27a190e85 --- /dev/null +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronSignatureAlgTest.java @@ -0,0 +1,52 @@ +package org.keycloak.crypto.elytron.test; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.PSSParameterSpec; + +import org.junit.Test; +import org.keycloak.common.crypto.CryptoIntegration; +import org.keycloak.crypto.JavaAlgorithm; +import org.keycloak.crypto.KeyWrapper; + +public class ElytronSignatureAlgTest { + + private byte[] data = "Test String to Encrypt".getBytes(); + + @Test + public void signatureDefaultAlg() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, InvalidAlgorithmParameterException, InvalidKeySpecException { + KeyPair keyPair = KeyPairGenerator.getInstance("RSASSA-PSS").genKeyPair(); + KeyWrapper key = new KeyWrapper(); + //key.setPrivateKey(keyPair.getPrivate()); + key.setAlgorithm("PS256"); + + KeySpec kspec = new PKCS8EncodedKeySpec(keyPair.getPrivate().getEncoded()); + key.setPrivateKey(KeyFactory.getInstance("RSASSA-PSS").generatePrivate(kspec)); + + Signature signature = Signature.getInstance("RSASSA-PSS"); + MGF1ParameterSpec ps = MGF1ParameterSpec.SHA256; + AlgorithmParameterSpec params = new PSSParameterSpec(ps.getDigestAlgorithm(), "MGF1", ps, 32, 1); + + signature.setParameter(params); + signature.initSign(keyPair.getPrivate()); + //signature.initSign((PrivateKey) key.getPrivateKey()); + signature.update(data); + System.out.println(signature.getProvider() + " Alg ###########"); + if(signature.getAlgorithm().equals("RSASSA-PSS")) { + } + signature.sign(); + } + +} From d351290c0cbd3a3b370e56367322724853b61143 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Tue, 3 Oct 2023 06:31:23 -0400 Subject: [PATCH 03/19] switching the operator role to clusterrole for ingresses config (#23641) closes #23629 --- .../default-namespace/kustomization.yaml | 12 +++++++++++ operator/src/main/kubernetes/kubernetes.yml | 21 +++++++++++++++++++ .../integration/BaseOperatorTest.java | 19 ++++++++++++++--- .../operator/testsuite/utils/K8sUtils.java | 7 ++++++- 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/operator/overlays/default-namespace/kustomization.yaml b/operator/overlays/default-namespace/kustomization.yaml index cbec1674ec61..20498a818c0f 100644 --- a/operator/overlays/default-namespace/kustomization.yaml +++ b/operator/overlays/default-namespace/kustomization.yaml @@ -5,3 +5,15 @@ resources: - ../../target namespace: default + +transformers: +- |- + apiVersion: builtin + kind: NamespaceTransformer + metadata: + name: notImportantHere + namespace: default + setRoleBindingSubjects: allServiceAccounts + fieldSpecs: + - path: metadata/namespace + create: true diff --git a/operator/src/main/kubernetes/kubernetes.yml b/operator/src/main/kubernetes/kubernetes.yml index c1d225923416..45c60438c585 100644 --- a/operator/src/main/kubernetes/kubernetes.yml +++ b/operator/src/main/kubernetes/kubernetes.yml @@ -58,6 +58,12 @@ rules: - delete - patch - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: keycloak-operator-clusterrole +rules: - apiGroups: - config.openshift.io resources: @@ -78,3 +84,18 @@ roleRef: subjects: - kind: ServiceAccount name: keycloak-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/name: keycloak-operator + name: keycloak-operator-clusterrole-binding +roleRef: + kind: ClusterRole + apiGroup: rbac.authorization.k8s.io + name: keycloak-operator-clusterrole +subjects: + - kind: ServiceAccount + name: keycloak-operator + namespace: keycloak diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java index 795fb71bfbb2..4eb588763c38 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java @@ -25,6 +25,7 @@ import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.StatefulSet; +import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBinding; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.ConfigBuilder; import io.fabric8.kubernetes.client.KubernetesClient; @@ -62,6 +63,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileWriter; +import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; @@ -69,6 +71,7 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.stream.Stream; import jakarta.enterprise.inject.Instance; import jakarta.enterprise.inject.spi.CDI; @@ -151,7 +154,12 @@ private static void createK8sClient() { private static void createRBACresourcesAndOperatorDeployment() throws FileNotFoundException { Log.info("Creating RBAC and Deployment into Namespace " + namespace); - K8sUtils.set(k8sclient, new FileInputStream(TARGET_KUBERNETES_GENERATED_YML_FOLDER + deploymentTarget + ".yml")); + K8sUtils.set(k8sclient, new FileInputStream(TARGET_KUBERNETES_GENERATED_YML_FOLDER + deploymentTarget + ".yml"), obj -> { + if (obj instanceof ClusterRoleBinding) { + ((ClusterRoleBinding)obj).getSubjects().forEach(s -> s.setNamespace(namespace)); + } + return obj; + }); } private static void cleanRBACresourcesAndOperatorDeployment() throws FileNotFoundException { @@ -262,10 +270,15 @@ public void cleanup() { @Override public void afterEach(QuarkusTestMethodContext context) { if (!(context.getTestInstance() instanceof BaseOperatorTest)) { - return; + return; // this hook gets called for all quarkus tests, not all are operator tests } try { - if (!context.getTestStatus().isTestFailed() || context.getTestStatus().getTestErrorCause() instanceof TestAbortedException) { + Method testMethod = context.getTestMethod(); + if (context.getTestStatus().getTestErrorCause() == null + || context.getTestStatus().getTestErrorCause() instanceof TestAbortedException + || !Stream.of(context.getTestStatus().getTestErrorCause().getStackTrace()) + .anyMatch(ste -> ste.getMethodName().equals(testMethod.getName()) + && ste.getClassName().equals(testMethod.getDeclaringClass().getName()))) { return; } Log.warnf("Test failed with %s: %s", context.getTestStatus().getTestErrorCause().getMessage(), context.getTestStatus().getTestErrorCause().getClass().getName()); diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/utils/K8sUtils.java b/operator/src/test/java/org/keycloak/operator/testsuite/utils/K8sUtils.java index a6d89fc68661..8bb1561f1543 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/utils/K8sUtils.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/utils/K8sUtils.java @@ -39,6 +39,7 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -63,7 +64,11 @@ public static void deployKeycloak(KubernetesClient client, Keycloak kc, boolean } public static List set(KubernetesClient client, InputStream stream) { - return client.load(stream).items().stream().map(i -> set(client, i)).collect(Collectors.toList()); + return set(client, stream, Function.identity()); + } + + public static List set(KubernetesClient client, InputStream stream, Function modifier) { + return client.load(stream).items().stream().map(modifier).map(i -> set(client, i)).collect(Collectors.toList()); } public static T set(KubernetesClient client, T hasMetadata) { From e9d8ecb07a8211f1b7569d47501400d205f18ef6 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Tue, 3 Oct 2023 14:24:37 +0200 Subject: [PATCH 04/19] try and make, (#23550) identity_providers_test.spec.ts > should revert and save options less flaky --- js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts b/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts index 2443472a953a..30a073864318 100644 --- a/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts @@ -353,6 +353,7 @@ describe("Identity provider test", () => { advancedSettings.assertClaimValueInputEqual("claim-value"); cy.findByTestId("idp-details-save").click(); + masthead.checkNotificationMessage("Provider successfully updated"); }); it("should revert and save options", () => { From e6e724d585ae6162aaf24aff1be72507c55f7871 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 3 Oct 2023 14:57:26 +0200 Subject: [PATCH 05/19] Disable uploading heapdumps on Windows runners (#23665) (#23666) Closes #23661 --- .github/actions/upload-heapdumps/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/upload-heapdumps/action.yml b/.github/actions/upload-heapdumps/action.yml index e57dc72dc07b..d9c71b4d6f90 100644 --- a/.github/actions/upload-heapdumps/action.yml +++ b/.github/actions/upload-heapdumps/action.yml @@ -6,6 +6,8 @@ runs: steps: - id: upload-jvm-heapdumps name: Upload JVM Heapdumps + # Windows runners are running into https://github.com/actions/upload-artifact/issues/240 + if: runner.os != 'Windows' uses: actions/upload-artifact@v3 with: name: jvm-heap-dumps From 1332e53a97d87ef169cf5409566aa771c7e4d250 Mon Sep 17 00:00:00 2001 From: andymunro <48995441+andymunro@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:50:23 -0400 Subject: [PATCH 06/19] Code certain features as upstream only (#23603) Closes #23581 --- docs/documentation/securing_apps/topics.adoc | 3 +++ docs/documentation/server_admin/topics.adoc | 2 ++ .../server_admin/topics/admin-console-permissions.adoc | 8 ++++++-- .../topics/clients/oidc/con-advanced-settings.adoc | 5 ++++- .../server_admin/topics/threat/auth-sessions-limit.adoc | 2 ++ 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/documentation/securing_apps/topics.adoc b/docs/documentation/securing_apps/topics.adoc index 78f774ea538e..462e8f88758d 100644 --- a/docs/documentation/securing_apps/topics.adoc +++ b/docs/documentation/securing_apps/topics.adoc @@ -77,4 +77,7 @@ include::topics/saml/mod-auth-mellon.adoc[] include::topics/docker/docker-overview.adoc[] include::topics/client-registration.adoc[] include::topics/client-registration/client-registration-cli.adoc[] +ifeval::[{project_community}==true] include::topics/token-exchange/token-exchange.adoc[] +endif::[] + diff --git a/docs/documentation/server_admin/topics.adoc b/docs/documentation/server_admin/topics.adoc index 91f0148e2cab..b4ed30afd684 100644 --- a/docs/documentation/server_admin/topics.adoc +++ b/docs/documentation/server_admin/topics.adoc @@ -55,7 +55,9 @@ include::topics/sso-protocols.adoc[] include::topics/admin-console-permissions.adoc[] include::topics/admin-console-permissions/master-realm.adoc[] include::topics/admin-console-permissions/per-realm.adoc[] +ifeval::[{project_community}==true] include::topics/admin-console-permissions/fine-grain.adoc[] +endif::[] include::topics/assembly-managing-clients.adoc[] include::topics/vault.adoc[] include::topics/events.adoc[] diff --git a/docs/documentation/server_admin/topics/admin-console-permissions.adoc b/docs/documentation/server_admin/topics/admin-console-permissions.adoc index e938839de8ce..a9f191e062d4 100644 --- a/docs/documentation/server_admin/topics/admin-console-permissions.adoc +++ b/docs/documentation/server_admin/topics/admin-console-permissions.adoc @@ -3,5 +3,9 @@ == Controlling access to the Admin Console Each realm created on the {project_name} has a dedicated Admin Console from which that realm can be managed. -The `master` realm is a special realm that allows admins to manage more than one realm on the system. You can also -define fine-grained access to users in different realms to manage the server. This chapter goes over all the scenarios for this. +The `master` realm is a special realm that allows admins to manage more than one realm on the system. +ifeval::[{project_community}==true] +You can also +define fine-grained access to users in different realms to manage the server. +endif::[] +This chapter goes over all the scenarios for this. diff --git a/docs/documentation/server_admin/topics/clients/oidc/con-advanced-settings.adoc b/docs/documentation/server_admin/topics/clients/oidc/con-advanced-settings.adoc index 08c98b16db25..b70fafcee179 100644 --- a/docs/documentation/server_admin/topics/clients/oidc/con-advanced-settings.adoc +++ b/docs/documentation/server_admin/topics/clients/oidc/con-advanced-settings.adoc @@ -2,7 +2,10 @@ = Advanced configuration [role="_abstract"] -After completing the fields on the *Settings* tab, you can use the other tabs to perform advanced configuration. For example, you can use the *Permissions* and *Roles* tabs to configure fine-grained authentication for administrators. See <<_fine_grain_permissions, Fine grain admin permissions>>. Also, see the remaining sections in this chapter for other capabilities. +After completing the fields on the *Settings* tab, you can use the other tabs to perform advanced configuration. +ifeval::[{project_community}==true] +For example, you can use the *Permissions* and *Roles* tabs to configure fine-grained authentication for administrators. See <<_fine_grain_permissions, Fine grain admin permissions>>. Also, see the remaining sections in this chapter for other capabilities. +endif::[] == Advanced tab diff --git a/docs/documentation/server_admin/topics/threat/auth-sessions-limit.adoc b/docs/documentation/server_admin/topics/threat/auth-sessions-limit.adoc index 988d18ced7c4..c6961230ff5c 100644 --- a/docs/documentation/server_admin/topics/threat/auth-sessions-limit.adoc +++ b/docs/documentation/server_admin/topics/threat/auth-sessions-limit.adoc @@ -31,9 +31,11 @@ The following example shows how to limit the number of active `AuthenticationSes bin/kc.[sh|bat] start --spi-authentication-sessions-infinispan-auth-sessions-limit=100 ---- +ifeval::[{project_community}==true] The equivalent command for the new map storage: [source,bash] ---- bin/kc.[sh|bat] start --spi-authentication-sessions-map-auth-sessions-limit=100 ---- +endif::[] From 7953085ed2b559cb7b4fafade3198d7f12ccbab8 Mon Sep 17 00:00:00 2001 From: Alex Szczuczko Date: Wed, 4 Oct 2023 00:00:53 -0600 Subject: [PATCH 07/19] Fix set-version.sh's handling of NPM versions (#23638) This introduces a maven property, `project.version.npm`, to allow maven to know what NPM version is, in scenarios that it's not identical to `project.version`. This occurs when the set-version's semver translation code is activated Closes #23635 --- js/libs/keycloak-js/pom.xml | 4 ++-- pom.xml | 2 ++ set-version.sh | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/js/libs/keycloak-js/pom.xml b/js/libs/keycloak-js/pom.xml index 429140eb448d..584f8377d447 100644 --- a/js/libs/keycloak-js/pom.xml +++ b/js/libs/keycloak-js/pom.xml @@ -83,8 +83,8 @@ - ${project.basedir}/assembly.xml - target/keycloak-js-${project.version}.tgz + + target/keycloak-js-${project.version.npm}.tgz tar.gz diff --git a/pom.xml b/pom.xml index bb69387419d9..cec73caa3830 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,8 @@ pom + 999.0.0-SNAPSHOT + 1.5.8 https://s01.oss.sonatype.org/ diff --git a/set-version.sh b/set-version.sh index b8e442b3915b..21886a202163 100755 --- a/set-version.sh +++ b/set-version.sh @@ -11,6 +11,7 @@ fi # Maven mvn versions:set -DnewVersion=$NEW_VERSION -DgenerateBackupPoms=false -DgroupId=org.keycloak* -DartifactId=* +mvn versions:set-property --non-recursive -Dproperty=project.version.npm -DnewVersion="$NEW_NPM_VERSION" # Docker sed -i "s/ENV KEYCLOAK_VERSION .*/ENV KEYCLOAK_VERSION $NEW_VERSION/" quarkus/container/Dockerfile @@ -29,3 +30,6 @@ echo "$(jq '. += {"version": "'$NEW_NPM_VERSION'"}' js/libs/keycloak-js/package. # Keycloak Admin Client echo "$(jq '. += {"version": "'$NEW_NPM_VERSION'"}' js/libs/keycloak-admin-client/package.json)" > js/libs/keycloak-admin-client/package.json + +echo "New Mvn Version: $NEW_VERSION" >&2 +echo "New NPM Version: $NEW_NPM_VERSION" >&2 From ca2c5f688f03c0674eafd3368b93ab0762ad9b86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:50:49 +0200 Subject: [PATCH 08/19] Bump vite from 4.4.9 to 4.4.10 in /js (#23677) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.9 to 4.4.10. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v4.4.10/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v4.4.10/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- js/apps/account-ui/package.json | 2 +- js/apps/admin-ui/package.json | 2 +- js/libs/keycloak-masthead/package.json | 2 +- js/libs/ui-shared/package.json | 2 +- js/pnpm-lock.yaml | 74 ++++++++++++-------------- 5 files changed, 37 insertions(+), 45 deletions(-) diff --git a/js/apps/account-ui/package.json b/js/apps/account-ui/package.json index 6b4e7b52fe15..3be9a2feaddd 100644 --- a/js/apps/account-ui/package.json +++ b/js/apps/account-ui/package.json @@ -32,7 +32,7 @@ "@types/react": "^18.2.22", "@types/react-dom": "^18.2.8", "@vitejs/plugin-react-swc": "^3.4.0", - "vite": "^4.4.9", + "vite": "^4.4.10", "vite-plugin-checker": "^0.6.2" }, "wireit": { diff --git a/js/apps/admin-ui/package.json b/js/apps/admin-ui/package.json index 1d7ae182d280..1f97b11632eb 100644 --- a/js/apps/admin-ui/package.json +++ b/js/apps/admin-ui/package.json @@ -106,7 +106,7 @@ "ldap-server-mock": "^6.0.1", "ts-node": "^10.9.1", "uuid": "^9.0.1", - "vite": "^4.4.9", + "vite": "^4.4.10", "vite-plugin-checker": "^0.6.2", "vitest": "^0.34.5" } diff --git a/js/libs/keycloak-masthead/package.json b/js/libs/keycloak-masthead/package.json index 823d69906199..dc65a682c856 100644 --- a/js/libs/keycloak-masthead/package.json +++ b/js/libs/keycloak-masthead/package.json @@ -51,7 +51,7 @@ "@types/react-dom": "^18.2.8", "@vitejs/plugin-react-swc": "^3.4.0", "rollup-plugin-peer-deps-external": "^2.2.4", - "vite": "^4.4.9", + "vite": "^4.4.10", "vite-plugin-checker": "^0.6.2", "vite-plugin-dts": "^3.5.4" } diff --git a/js/libs/ui-shared/package.json b/js/libs/ui-shared/package.json index 34519eb4ff9f..64b3996f7983 100644 --- a/js/libs/ui-shared/package.json +++ b/js/libs/ui-shared/package.json @@ -45,7 +45,7 @@ "@types/react-dom": "^18.2.8", "@vitejs/plugin-react-swc": "^3.4.0", "rollup-plugin-peer-deps-external": "^2.2.4", - "vite": "^4.4.9", + "vite": "^4.4.10", "vite-plugin-checker": "^0.6.2", "vite-plugin-dts": "^3.5.4", "vitest": "^0.34.5" diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index ca6bf8d40abf..50a3368e15f8 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -131,13 +131,13 @@ importers: version: 18.2.8 '@vitejs/plugin-react-swc': specifier: ^3.4.0 - version: 3.4.0(vite@4.4.9) + version: 3.4.0(vite@4.4.10) vite: - specifier: ^4.4.9 - version: 4.4.9(@types/node@20.7.0) + specifier: ^4.4.10 + version: 4.4.10(@types/node@20.7.0) vite-plugin-checker: specifier: ^0.6.2 - version: 0.6.2(eslint@8.49.0)(typescript@5.2.2)(vite@4.4.9) + version: 0.6.2(eslint@8.49.0)(typescript@5.2.2)(vite@4.4.10) apps/admin-ui: dependencies: @@ -255,7 +255,7 @@ importers: version: 9.0.4 '@vitejs/plugin-react-swc': specifier: ^3.4.0 - version: 3.4.0(vite@4.4.9) + version: 3.4.0(vite@4.4.10) cypress: specifier: ^13.2.0 version: 13.2.0 @@ -275,11 +275,11 @@ importers: specifier: ^9.0.1 version: 9.0.1 vite: - specifier: ^4.4.9 - version: 4.4.9(@types/node@20.7.0) + specifier: ^4.4.10 + version: 4.4.10(@types/node@20.7.0) vite-plugin-checker: specifier: ^0.6.2 - version: 0.6.2(eslint@8.49.0)(typescript@5.2.2)(vite@4.4.9) + version: 0.6.2(eslint@8.49.0)(typescript@5.2.2)(vite@4.4.10) vitest: specifier: ^0.34.5 version: 0.34.5(jsdom@22.1.0) @@ -405,19 +405,19 @@ importers: version: 18.2.8 '@vitejs/plugin-react-swc': specifier: ^3.4.0 - version: 3.4.0(vite@4.4.9) + version: 3.4.0(vite@4.4.10) rollup-plugin-peer-deps-external: specifier: ^2.2.4 version: 2.2.4(rollup@3.29.4) vite: - specifier: ^4.4.9 - version: 4.4.9(@types/node@20.7.0) + specifier: ^4.4.10 + version: 4.4.10(@types/node@20.7.0) vite-plugin-checker: specifier: ^0.6.2 - version: 0.6.2(eslint@8.49.0)(typescript@5.2.2)(vite@4.4.9) + version: 0.6.2(eslint@8.49.0)(typescript@5.2.2)(vite@4.4.10) vite-plugin-dts: specifier: ^3.5.4 - version: 3.5.4(@types/node@20.7.0)(rollup@3.29.4)(typescript@5.2.2)(vite@4.4.9) + version: 3.5.4(@types/node@20.7.0)(rollup@3.29.4)(typescript@5.2.2)(vite@4.4.10) libs/ui-shared: dependencies: @@ -445,19 +445,19 @@ importers: version: 18.2.8 '@vitejs/plugin-react-swc': specifier: ^3.4.0 - version: 3.4.0(vite@4.4.9) + version: 3.4.0(vite@4.4.10) rollup-plugin-peer-deps-external: specifier: ^2.2.4 version: 2.2.4(rollup@3.29.4) vite: - specifier: ^4.4.9 - version: 4.4.9(@types/node@20.7.0) + specifier: ^4.4.10 + version: 4.4.10(@types/node@20.7.0) vite-plugin-checker: specifier: ^0.6.2 - version: 0.6.2(eslint@8.49.0)(typescript@5.2.2)(vite@4.4.9) + version: 0.6.2(eslint@8.49.0)(typescript@5.2.2)(vite@4.4.10) vite-plugin-dts: specifier: ^3.5.4 - version: 3.5.4(@types/node@20.7.0)(rollup@3.29.4)(typescript@5.2.2)(vite@4.4.9) + version: 3.5.4(@types/node@20.7.0)(rollup@3.29.4)(typescript@5.2.2)(vite@4.4.10) vitest: specifier: ^0.34.5 version: 0.34.5 @@ -2070,13 +2070,13 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@vitejs/plugin-react-swc@3.4.0(vite@4.4.9): + /@vitejs/plugin-react-swc@3.4.0(vite@4.4.10): resolution: {integrity: sha512-m7UaA4Uvz82N/0EOVpZL4XsFIakRqrFKeSNxa1FBLSXGvWrWRBwmZb4qxk+ZIVAZcW3c3dn5YosomDgx62XWcQ==} peerDependencies: vite: ^4 dependencies: '@swc/core': 1.3.88 - vite: 4.4.9(@types/node@20.7.0) + vite: 4.4.10(@types/node@20.7.0) transitivePeerDependencies: - '@swc/helpers' dev: true @@ -5944,14 +5944,6 @@ packages: rollup: 3.29.4 dev: true - /rollup@3.29.3: - resolution: {integrity: sha512-T7du6Hum8jOkSWetjRgbwpM6Sy0nECYrYRSmZjayFcOddtKJWU4d17AC3HNUk7HRuqy4p+G7aEZclSHytqUmEg==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.3 - dev: true - /rollup@3.29.4: resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} @@ -6727,7 +6719,7 @@ packages: mlly: 1.4.1 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.4.9(@types/node@20.6.5) + vite: 4.4.10(@types/node@20.6.5) transitivePeerDependencies: - '@types/node' - less @@ -6749,7 +6741,7 @@ packages: mlly: 1.4.1 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.4.9(@types/node@20.7.0) + vite: 4.4.10(@types/node@20.7.0) transitivePeerDependencies: - '@types/node' - less @@ -6761,7 +6753,7 @@ packages: - terser dev: true - /vite-plugin-checker@0.6.2(eslint@8.49.0)(typescript@5.2.2)(vite@4.4.9): + /vite-plugin-checker@0.6.2(eslint@8.49.0)(typescript@5.2.2)(vite@4.4.10): resolution: {integrity: sha512-YvvvQ+IjY09BX7Ab+1pjxkELQsBd4rPhWNw8WLBeFVxu/E7O+n6VYAqNsKdK/a2luFlX/sMpoWdGFfg4HvwdJQ==} engines: {node: '>=14.16'} peerDependencies: @@ -6807,14 +6799,14 @@ packages: strip-ansi: 6.0.1 tiny-invariant: 1.3.1 typescript: 5.2.2 - vite: 4.4.9(@types/node@20.7.0) + vite: 4.4.10(@types/node@20.7.0) vscode-languageclient: 7.0.0 vscode-languageserver: 7.0.0 vscode-languageserver-textdocument: 1.0.8 vscode-uri: 3.0.7 dev: true - /vite-plugin-dts@3.5.4(@types/node@20.7.0)(rollup@3.29.4)(typescript@5.2.2)(vite@4.4.9): + /vite-plugin-dts@3.5.4(@types/node@20.7.0)(rollup@3.29.4)(typescript@5.2.2)(vite@4.4.10): resolution: {integrity: sha512-BJLBj1Vg9kV7ZMXAULT9UGogrElwz5s+k8TzC7LsFkHv5Jy90OWnHKUp8qm7sypu2pkF5pTJ5McUuHudnT0Imw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -6830,7 +6822,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) kolorist: 1.8.0 typescript: 5.2.2 - vite: 4.4.9(@types/node@20.7.0) + vite: 4.4.10(@types/node@20.7.0) vue-tsc: 1.8.8(typescript@5.2.2) transitivePeerDependencies: - '@types/node' @@ -6838,8 +6830,8 @@ packages: - supports-color dev: true - /vite@4.4.9(@types/node@20.6.5): - resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} + /vite@4.4.10(@types/node@20.6.5): + resolution: {integrity: sha512-TzIjiqx9BEXF8yzYdF2NTf1kFFbjMjUSV0LFZ3HyHoI3SGSPLnnFUKiIQtL3gl2AjHvMrprOvQ3amzaHgQlAxw==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -6874,8 +6866,8 @@ packages: fsevents: 2.3.3 dev: true - /vite@4.4.9(@types/node@20.7.0): - resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} + /vite@4.4.10(@types/node@20.7.0): + resolution: {integrity: sha512-TzIjiqx9BEXF8yzYdF2NTf1kFFbjMjUSV0LFZ3HyHoI3SGSPLnnFUKiIQtL3gl2AjHvMrprOvQ3amzaHgQlAxw==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -6905,7 +6897,7 @@ packages: '@types/node': 20.7.0 esbuild: 0.18.20 postcss: 8.4.28 - rollup: 3.29.3 + rollup: 3.29.4 optionalDependencies: fsevents: 2.3.3 dev: true @@ -6962,7 +6954,7 @@ packages: strip-literal: 1.3.0 tinybench: 2.5.0 tinypool: 0.7.0 - vite: 4.4.9(@types/node@20.7.0) + vite: 4.4.10(@types/node@20.7.0) vite-node: 0.34.5(@types/node@20.7.0) why-is-node-running: 2.2.2 transitivePeerDependencies: @@ -7028,7 +7020,7 @@ packages: strip-literal: 1.3.0 tinybench: 2.5.0 tinypool: 0.7.0 - vite: 4.4.9(@types/node@20.6.5) + vite: 4.4.10(@types/node@20.6.5) vite-node: 0.34.5(@types/node@20.6.5) why-is-node-running: 2.2.2 transitivePeerDependencies: From 3e3fb6277000a372201dd4a8b869690a5b05b519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Barto=C5=A1?= Date: Wed, 4 Oct 2023 12:50:18 +0200 Subject: [PATCH 09/19] Improve test coverage for Admin hostname properties (#23535) Closes #23534 --- .../it/cli/dist/HostnameDistTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HostnameDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HostnameDistTest.java index 5b615d311580..db4eebbd2c6a 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HostnameDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HostnameDistTest.java @@ -19,6 +19,7 @@ import io.quarkus.test.junit.main.Launch; import io.restassured.RestAssured; +import org.apache.commons.lang3.StringUtils; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.jupiter.api.BeforeAll; @@ -28,7 +29,10 @@ import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; import org.keycloak.quarkus.runtime.services.resources.DebugHostnameSettingsResource; +import java.util.function.Consumer; + import static io.restassured.RestAssured.when; +import static org.hamcrest.MatcherAssert.assertThat; @DistributionTest(keepAlive = true, enableTls = true, defaultOptions = { "--http-enabled=true" }) @RawDistOnly(reason = "Containers are immutable") @@ -153,6 +157,27 @@ public void testDebugHostnameSettingsDisabledByDefault() { public void testHostnameAdminSet() { when().get("https://mykeycloak.org:8443/admin/master/console").then().body(Matchers.containsString("\"authUrl\": \"https://mykeycloakadmin.org:8443\"")); when().get("https://mykeycloak.org:8443/realms/master/protocol/openid-connect/auth?client_id=security-admin-console&redirect_uri=https://mykeycloakadmin.org:8443/admin/master/console&state=02234324-d91e-4bf2-8396-57498e96b12a&response_mode=fragment&response_type=code&scope=openid&nonce=f8f3812e-e349-4bbf-8d15-cbba4927f5e5&code_challenge=7qjD_v11WGkt1ig-ZFHxJdrEvuTlzjFRgRGQ_5ADcko&code_challenge_method=S256").then().body(Matchers.containsString("Sign in to your account")); + + when().get("http://localhost:8080/admin/master/console").then().body(Matchers.containsString("\"authUrl\": \"http://mykeycloakadmin.org:8080\"")); + } + + @Test + @Launch({"start", "--hostname=mykeycloak.org", "--hostname-debug=true"}) + public void testHostnameAdminFromHeaders() { + when().get("https://mykeycloak.org:8443/admin/master/console").then().body(Matchers.containsString("\"authUrl\": \"https://mykeycloak.org:8443\"")); + when().get("https://mykeycloak.org:8443/realms/master/protocol/openid-connect/auth?client_id=security-admin-console&redirect_uri=https://mykeycloak.org:8443/admin/master/console&state=02234324-d91e-4bf2-8396-57498e96b12a&response_mode=fragment&response_type=code&scope=openid&nonce=f8f3812e-e349-4bbf-8d15-cbba4927f5e5&code_challenge=7qjD_v11WGkt1ig-ZFHxJdrEvuTlzjFRgRGQ_5ADcko&code_challenge_method=S256").then().body(Matchers.containsString("Sign in to your account")); + + // Admin URL should be resolved from headers + when().get("http://localhost:8080/admin/master/console").then().body(Matchers.containsString("\"authUrl\": \"http://localhost:8080\"")); + when().get("http://localhost:8080/realms/master/protocol/openid-connect/auth?client_id=security-admin-console&redirect_uri=http://localhost:8080/admin/master/console&state=02234324-d91e-4bf2-8396-57498e96b12a&response_mode=fragment&response_type=code&scope=openid&nonce=f8f3812e-e349-4bbf-8d15-cbba4927f5e5&code_challenge=7qjD_v11WGkt1ig-ZFHxJdrEvuTlzjFRgRGQ_5ADcko&code_challenge_method=S256").then().body(Matchers.containsString("Sign in to your account")); + + Consumer assertDebugAdmin = (url) -> { + final var body = StringUtils.deleteWhitespace(when().get(url + "/realms/master/hostname-debug").then().extract().response().body().asString()); + assertThat(body, Matchers.containsString("Admin" + url)); + }; + + assertDebugAdmin.accept("https://mykeycloak.org:8443"); + assertDebugAdmin.accept("http://localhost:8080"); } @Test @@ -171,6 +196,7 @@ public void testFrontendUrl() { @Launch({ "start", "--proxy=edge", "--hostname=mykeycloak.org", "--hostname-admin-url=http://mykeycloakadmin.org:1234" }) public void testAdminUrl() { when().get("https://mykeycloak.org:8443").then().body(Matchers.containsString("http://mykeycloakadmin.org:1234/admin/")); + when().get("http://localhost:8080/admin/master/console").then().body(Matchers.containsString("\"authUrl\": \"http://mykeycloakadmin.org:1234\"")); } @Test From 9a93b9a2734d2f8c9192c68c39efa1f9d0359613 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Wed, 4 Oct 2023 09:49:19 -0400 Subject: [PATCH 10/19] allows csv output to handle missing requested fields (#23459) * allows csv output to handle missing requested fields Closes #12330 * fixes the handling of the content type also makes it more explicit the expectation of applying csv and return fields * fix: consolidating the logic dealing with the content-type Closes #23580 --- .../cli/commands/AbstractRequestCmd.java | 19 +++++--- .../client/admin/cli/util/Headers.java | 7 +++ .../client/admin/cli/util/HeadersBody.java | 16 +++---- .../client/admin/cli/util/OutputUtil.java | 22 ++++----- .../client/admin/cli/util/OuputUtilTest.java | 47 +++++++++++++++++++ .../testsuite/cli/admin/KcAdmTest.java | 26 ++++++++++ 6 files changed, 110 insertions(+), 27 deletions(-) create mode 100644 integration/client-cli/admin-cli/src/test/java/org/keycloak/client/admin/cli/util/OuputUtilTest.java diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AbstractRequestCmd.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AbstractRequestCmd.java index c401a9f57dc4..1fb4c792dd83 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AbstractRequestCmd.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AbstractRequestCmd.java @@ -18,6 +18,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; + +import org.apache.http.entity.ContentType; import org.jboss.aesh.console.command.CommandException; import org.jboss.aesh.console.command.CommandResult; import org.jboss.aesh.console.command.invocation.CommandInvocation; @@ -398,11 +400,10 @@ public CommandResult process(CommandInvocation commandInvocation) throws Command } } - Header contentType = response.getHeaders().get("content-type"); - boolean canPrettyPrint = contentType != null && contentType.getValue().equals("application/json"); - boolean pretty = !compressed; + boolean json = response.getHeaders().getContentType().map(ContentType::getMimeType) + .filter("application/json"::equals).isPresent(); - if (canPrettyPrint && (pretty || returnFields != null)) { + if (json && !compressed) { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); copyStream(response.getBody(), buffer); @@ -417,10 +418,16 @@ public CommandResult process(CommandInvocation commandInvocation) throws Command } else { printAsCsv(rootNode, returnFields, unquoted); } - } catch (Exception ignored) { - copyStream(new ByteArrayInputStream(buffer.toByteArray()), abos); + } catch (Exception e) { + throw new RuntimeException("Error processing results: " + e.getMessage(), e); } } else { + if (outputFormat != OutputFormat.JSON || returnFields != null) { + printErr("Cannot create CSV nor filter returned fields because the response is " + (compressed ? "compressed":"not json")); + return CommandResult.SUCCESS; + } + // in theory the user could explicitly request json, but this could be a non-json response + // since there's no option for raw and we don't differentiate the default, there's no error about this copyStream(response.getBody(), abos); } } diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/Headers.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/Headers.java index 338971fce238..57fc034e5a8c 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/Headers.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/Headers.java @@ -16,8 +16,11 @@ */ package org.keycloak.client.admin.cli.util; +import org.apache.http.entity.ContentType; + import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.Optional; /** * @author Marko Strukelj @@ -52,4 +55,8 @@ public Header get(String header) { public Iterator
iterator() { return headers.values().iterator(); } + + public Optional getContentType() { + return Optional.ofNullable(headers.get("content-type")).map(Header::getValue).map(ContentType::parse); + } } diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/HeadersBody.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/HeadersBody.java index c217fd2196f1..eba553b0f87d 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/HeadersBody.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/HeadersBody.java @@ -16,6 +16,8 @@ */ package org.keycloak.client.admin.cli.util; +import org.apache.http.entity.ContentType; + import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.nio.charset.Charset; @@ -50,7 +52,7 @@ public InputStream getBody() { public String readBodyString() { byte [] buffer = readBodyBytes(); - return new String(buffer, Charset.forName(getContentCharset())); + return new String(buffer, getContentCharset()); } public byte[] readBodyBytes() { @@ -59,14 +61,8 @@ public byte[] readBodyBytes() { return os.toByteArray(); } - public String getContentCharset() { - Header contentType = headers.get("Content-Type"); - if (contentType != null) { - int pos = contentType.getValue().lastIndexOf("charset="); - if (pos != -1) { - return contentType.getValue().substring(pos + 8); - } - } - return "iso-8859-1"; + public Charset getContentCharset() { + return headers.getContentType().map(ContentType::getCharset).orElseGet(() -> Charset.forName("iso-8859-1")); } + } diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/OutputUtil.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/OutputUtil.java index a9931d7c2198..ef49f98b508d 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/OutputUtil.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/OutputUtil.java @@ -22,14 +22,11 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.TextNode; -import org.keycloak.util.JsonSerialization; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Iterator; import java.util.Map; - -import static org.keycloak.client.admin.cli.util.IoUtil.printOut; +import java.util.function.Consumer; /** * @author Marko Strukelj @@ -48,13 +45,14 @@ public static JsonNode convertToJsonNode(Object object) throws IOException { return (JsonNode) object; } - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - buffer.write(JsonSerialization.writeValueAsBytes(object)); - return MAPPER.readValue(buffer.toByteArray(), JsonNode.class); + return MAPPER.convertValue(object, JsonNode.class); } - public static void printAsCsv(Object object, ReturnFields fields, boolean unquoted) throws IOException { + printAsCsv(object, fields, unquoted, IoUtil::printOut); + } + + public static void printAsCsv(Object object, ReturnFields fields, boolean unquoted, Consumer printer) throws IOException { JsonNode node = convertToJsonNode(object); if (!node.isArray()) { @@ -67,7 +65,7 @@ public static void printAsCsv(Object object, ReturnFields fields, boolean unquot StringBuilder buffer = new StringBuilder(); printObjectAsCsv(buffer, item, fields, unquoted); - printOut(buffer.length() > 0 ? buffer.substring(1) : ""); + printer.accept(buffer.length() > 0 ? buffer.substring(1) : ""); } } @@ -77,7 +75,9 @@ static void printObjectAsCsv(StringBuilder out, JsonNode node, boolean unquoted) static void printObjectAsCsv(StringBuilder out, JsonNode node, ReturnFields fields, boolean unquoted) { - if (node.isObject()) { + if (node == null) { + out.append(","); + } else if (node.isObject()) { if (fields == null) { Iterator> it = node.fields(); while (it.hasNext()) { @@ -95,7 +95,7 @@ static void printObjectAsCsv(StringBuilder out, JsonNode node, ReturnFields fiel for (JsonNode item: node) { printObjectAsCsv(out, item, fields, unquoted); } - } else if (node != null) { + } else { out.append(","); if (unquoted && node instanceof TextNode) { out.append(node.asText()); diff --git a/integration/client-cli/admin-cli/src/test/java/org/keycloak/client/admin/cli/util/OuputUtilTest.java b/integration/client-cli/admin-cli/src/test/java/org/keycloak/client/admin/cli/util/OuputUtilTest.java new file mode 100644 index 000000000000..d91b0b8e1a31 --- /dev/null +++ b/integration/client-cli/admin-cli/src/test/java/org/keycloak/client/admin/cli/util/OuputUtilTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed 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 org.keycloak.client.admin.cli.util; + +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +import com.fasterxml.jackson.databind.JsonNode; + +import static org.junit.Assert.assertEquals; + +public class OuputUtilTest { + + @Test + public void testConversionToCsv() throws IOException { + HashMap map1 = new HashMap<>(); + map1.put("not-x", "omit"); + map1.put("y", "v1"); + HashMap map2 = new HashMap<>(); + map2.put("x", "v2"); + JsonNode node = OutputUtil.convertToJsonNode(Arrays.asList(map1, map2)); + ArrayList result = new ArrayList<>(); + OutputUtil.printAsCsv(node, new ReturnFields("x,y"), false, result::add); + assertEquals(",\"v1\"", result.get(0)); + assertEquals("\"v2\",", result.get(1)); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java index 6238b6bb5fb0..27441982d651 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java @@ -610,4 +610,30 @@ public void testGetUserNameExact() { KcAdmExec exec = execute("add-roles --uusername=testuser --rolename offline_access --target-realm=demorealm"); Assert.assertEquals(0, exec.exitCode()); } + + @Test + public void testCsvFormat() { + execute("config credentials --server " + serverUrl + " --realm master --user admin --password admin"); + KcAdmExec exec = execute("get realms/master --format csv"); + assertExitCodeAndStreamSizes(exec, 0, 1, 0); + Assert.assertTrue(exec.stdoutString().startsWith("\"")); + } + + @Test + public void testCsvFormatWithMissingFields() { + execute("config credentials --server " + serverUrl + " --realm master --user admin --password admin"); + KcAdmExec exec = execute("get realms/master --format csv --fields foo"); + // nothing valid was selected, should be blank + assertExitCodeAndStreamSizes(exec, 0, 1, 0); + Assert.assertTrue(exec.stdoutString().isBlank()); + } + + @Test + public void testCompressedCsv() { + execute("config credentials --server " + serverUrl + " --realm master --user admin --password admin"); + KcAdmExec exec = execute("get realms/master --format csv --compressed"); + // should contain an error message + assertExitCodeAndStreamSizes(exec, 0, 0, 1); + } + } From 469c306cd5d119480473dce89afffedb2439c3d6 Mon Sep 17 00:00:00 2001 From: andymunro <48995441+andymunro@users.noreply.github.com> Date: Thu, 5 Oct 2023 01:42:37 -0400 Subject: [PATCH 11/19] Remove recommendation to file a GitHub issue (#23712) #Close 23711 --- docs/guides/operator/advanced-configuration.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/operator/advanced-configuration.adoc b/docs/guides/operator/advanced-configuration.adoc index b6eb1820a76b..c775d174bf6d 100644 --- a/docs/guides/operator/advanced-configuration.adoc +++ b/docs/guides/operator/advanced-configuration.adoc @@ -107,7 +107,7 @@ The `unsupported` field of the CR contains highly experimental configuration opt ==== Pod Template The Pod Template is a raw API representation that is used for the Kubernetes Deployment Template. -This field is a temporary workaround in case no supported field exists at the top level of the CR for your use case. For a long-term solution, consider opening a GitHub issue to address your needs. +This field is a temporary workaround in case no supported field exists at the top level of the CR for your use case. The Operator merges the fields of the provided template with the values generated by the Operator for the specific Deployment. With this feature, you have access to a high level of customizations. However, no guarantee exists that the Deployment will work as expected. From 58131f1dccb7113cb708407d6cf90842216c8d00 Mon Sep 17 00:00:00 2001 From: Tomas Ondrusko Date: Wed, 23 Aug 2023 15:29:21 +0200 Subject: [PATCH 12/19] Update the Instagram login process Signed-off-by: Tomas Ondrusko --- .../pages/social/InstagramLoginPage.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/InstagramLoginPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/InstagramLoginPage.java index a57cc6bb57c0..2e2e4d61b2c7 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/InstagramLoginPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/InstagramLoginPage.java @@ -37,11 +37,8 @@ public class InstagramLoginPage extends AbstractSocialLoginPage { @FindBy(xpath = "//button[text()='Save Info']") private WebElement saveInfoBtn; - @FindBy(xpath = "//button[text()='Authorize']") - private WebElement authorizeBtn; - - @FindBy(xpath = "//button[text()='Continue']") - private WebElement continueBtn; + @FindBy(xpath = "//span[text()='Allow']") + private WebElement allowSpan; @Override public void login(String user, String password) { @@ -50,26 +47,26 @@ public void login(String user, String password) { usernameInput.sendKeys(user); passwordInput.sendKeys(password); passwordInput.sendKeys(Keys.RETURN); - pause(2000); // wait for the login screen a bit + pause(3000); try { saveInfoBtn.click(); + pause(3000); } catch (NoSuchElementException e) { log.info("'Save Info' button not found, ignoring"); - pause(2000); // wait for the login screen a bit + pause(3000); } } catch (NoSuchElementException e) { log.info("Instagram is already logged in, just confirmation is expected"); } + // Approval dialog try { - continueBtn.click(); - } - catch (NoSuchElementException e) { - log.info("'Continue' button not found, trying 'Authorize'..."); - authorizeBtn.click(); + allowSpan.click(); + } catch (NoSuchElementException e) { + log.info("'Allow' button not found, ignoring"); } } } From 50589d765782e3cc9a40b3d349fb3205fab46ded Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 4 Oct 2023 12:13:05 -0300 Subject: [PATCH 13/19] Weak hashing algorithm usage in SSSD User federation Closes #23713 --- .../org/freedesktop/dbus/bin/DBusDaemon.java | 875 ------------------ .../dbus/bin/EmbeddedDBusDaemon.java | 224 ----- .../connections/impl/DirectConnection.java | 300 ------ .../impl/DirectConnectionBuilder.java | 64 -- 4 files changed, 1463 deletions(-) delete mode 100644 federation/sssd/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java delete mode 100644 federation/sssd/src/main/java/org/freedesktop/dbus/bin/EmbeddedDBusDaemon.java delete mode 100644 federation/sssd/src/main/java/org/freedesktop/dbus/connections/impl/DirectConnection.java delete mode 100644 federation/sssd/src/main/java/org/freedesktop/dbus/connections/impl/DirectConnectionBuilder.java diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java b/federation/sssd/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java deleted file mode 100644 index 286fa8ffb065..000000000000 --- a/federation/sssd/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java +++ /dev/null @@ -1,875 +0,0 @@ -package org.freedesktop.dbus.bin; - -import org.freedesktop.dbus.Marshalling; -import org.freedesktop.dbus.connections.BusAddress; -import org.freedesktop.dbus.connections.transports.AbstractTransport; -import org.freedesktop.dbus.connections.transports.TransportBuilder; -import org.freedesktop.dbus.connections.transports.TransportBuilder.SaslAuthMode; -import org.freedesktop.dbus.connections.transports.TransportConnection; -import org.freedesktop.dbus.errors.AccessDenied; -import org.freedesktop.dbus.errors.Error; -import org.freedesktop.dbus.errors.MatchRuleInvalid; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; -import org.freedesktop.dbus.interfaces.DBus; -import org.freedesktop.dbus.interfaces.DBus.NameOwnerChanged; -import org.freedesktop.dbus.interfaces.FatalException; -import org.freedesktop.dbus.interfaces.Introspectable; -import org.freedesktop.dbus.interfaces.Peer; -import org.freedesktop.dbus.messages.DBusSignal; -import org.freedesktop.dbus.messages.Message; -import org.freedesktop.dbus.messages.MethodCall; -import org.freedesktop.dbus.messages.MethodReturn; -import org.freedesktop.dbus.types.UInt32; -import org.freedesktop.dbus.types.Variant; -import org.freedesktop.dbus.utils.Hexdump; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Closeable; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; -import java.lang.ref.WeakReference; -import java.lang.reflect.InvocationTargetException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.BlockingDeque; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * A replacement DBusDaemon - */ -public class DBusDaemon extends Thread implements Closeable { - public static final int QUEUE_POLL_WAIT = 500; - - private static final Logger LOGGER = - LoggerFactory.getLogger(DBusDaemon.class); - - private final Map conns = - new ConcurrentHashMap<>(); - private final Map names = - Collections.synchronizedMap(new HashMap<>()); // required because of "null" key - - private final BlockingDeque>> outqueue = - new LinkedBlockingDeque<>(); - private final BlockingDeque>> inqueue = - new LinkedBlockingDeque<>(); - - private final List sigrecips = new ArrayList<>(); - private final DBusServer dbusServer = new DBusServer(); - - private final DBusDaemonSenderThread sender = - new DBusDaemonSenderThread(); - private final AtomicBoolean run = - new AtomicBoolean(false); - private final AtomicInteger nextUnique = new AtomicInteger(0); - - private final AbstractTransport transport; - - public DBusDaemon(AbstractTransport _transport) { - setName(getClass().getSimpleName() + "-Thread"); - transport = _transport; - names.put("org.freedesktop.DBus", null); - } - - private void send(ConnectionStruct _connStruct, Message _msg) { - send(_connStruct, _msg, false); - } - - private void send(ConnectionStruct _connStruct, Message _msg, boolean _head) { - - // send to all connections - if (null == _connStruct) { - LOGGER.trace("Queuing message {} for all connections", _msg); - synchronized (conns) { - for (ConnectionStruct d : conns.keySet()) { - if (_head) { - outqueue.addFirst(new Pair<>(_msg, new WeakReference<>(d))); - } else { - outqueue.addLast(new Pair<>(_msg, new WeakReference<>(d))); - } - } - } - } else { - LOGGER.trace("Queuing message {} for {}", _msg, _connStruct.unique); - if (_head) { - outqueue.addFirst(new Pair<>(_msg, new WeakReference<>(_connStruct))); - } else { - outqueue.addLast(new Pair<>(_msg, new WeakReference<>(_connStruct))); - } - } - } - - @Override - public void run() { - run.set(true); - sender.start(); - - while (isRunning()) { - try { - Pair> pollFirst = inqueue.take(); - ConnectionStruct connectionStruct = pollFirst.second.get(); - if (connectionStruct != null) { - Message m = pollFirst.first; - logMessage(" Got message {} from {}", m, connectionStruct.unique); - - // check if they have hello'd - if (null == connectionStruct.unique && (!(m instanceof MethodCall) || !"org.freedesktop.DBus".equals(m.getDestination()) || !"Hello".equals(m.getName()))) { - send(connectionStruct, new Error("org.freedesktop.DBus", null, "org.freedesktop.DBus.Error.AccessDenied", m.getSerial(), "s", "You must send a Hello message")); - } else { - try { - if (null != connectionStruct.unique) { - m.setSource(connectionStruct.unique); - LOGGER.trace("Updated source to {}", connectionStruct.unique); - } - } catch (DBusException _ex) { - LOGGER.debug("Error setting source", _ex); - send(connectionStruct, new Error("org.freedesktop.DBus", null, "org.freedesktop.DBus.Error.GeneralError", m.getSerial(), "s", "Sending message failed")); - } - - if ("org.freedesktop.DBus".equals(m.getDestination())) { - dbusServer.handleMessage(connectionStruct, pollFirst.first); - } else { - if (m instanceof DBusSignal) { - List l; - synchronized (sigrecips) { - l = new ArrayList<>(sigrecips); - } - List list = l; - for (ConnectionStruct d : list) { - send(d, m); - } - } else { - ConnectionStruct dest = names.get(m.getDestination()); - - if (null == dest) { - send(connectionStruct, new Error("org.freedesktop.DBus", null, - "org.freedesktop.DBus.Error.ServiceUnknown", m.getSerial(), "s", - String.format("The name `%s' does not exist", m.getDestination()))); - } else { - send(dest, m); - } - } - } - } - } - - } catch (DBusException _ex) { - LOGGER.debug("Error processing connection", _ex); - } catch (InterruptedException _ex) { - LOGGER.debug("Interrupted"); - close(); - interrupt(); - } - } - - } - - private static void logMessage(String _logStr, Message _m, String _connUniqueId) { - Object logMsg = _m; - if (_m != null && Introspectable.class.getName().equals(_m.getInterface()) && !LOGGER.isTraceEnabled()) { - logMsg = ""; - } - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace(_logStr, logMsg, _connUniqueId); - } else { - LOGGER.debug(_logStr, _m, _connUniqueId); - } - } - - public synchronized boolean isRunning() { - return run.get(); - } - - @Override - public void close() { - run.set(false); - if (!conns.isEmpty()) { - // disconnect all remaining connection - Set connections = new HashSet<>(conns.keySet()); - for (ConnectionStruct c : connections) { - removeConnection(c); - } - } - sender.terminate(); - if (transport != null && transport.isConnected()) { - LOGGER.debug("Terminating transport {}", transport); - try { - // shutdown listener - transport.close(); - } catch (IOException _ex) { - LOGGER.debug("Error closing transport", _ex); - } - } - } - - private void removeConnection(ConnectionStruct _c) { - - boolean exists = false; - synchronized (conns) { - if (conns.containsKey(_c)) { - DBusDaemonReaderThread r = conns.get(_c); - exists = true; - r.terminate(); - conns.remove(_c); - } - } - if (exists) { - try { - if (null != _c.connection) { - _c.connection.close(); - } - } catch (IOException _exIo) { - LOGGER.trace("Error while closing socketchannel", _exIo); - } - - synchronized (names) { - List toRemove = new ArrayList<>(); - for (String name : names.keySet()) { - if (names.get(name) == _c) { - toRemove.add(name); - try { - send(null, new NameOwnerChanged("/org/freedesktop/DBus", name, _c.unique, "")); - } catch (DBusException _ex) { - LOGGER.debug("", _ex); - } - } - } - for (String name : toRemove) { - names.remove(name); - } - } - } - - } - - void addSock(TransportConnection _s) throws IOException { - LOGGER.debug("New Client"); - - ConnectionStruct c = new ConnectionStruct(_s); - DBusDaemonReaderThread r = new DBusDaemonReaderThread(c); - conns.put(c, r); - r.start(); - } - - public static void syntax() { - System.out.println("Syntax: DBusDaemon [--version] [-v] [--help] [-h] [--listen address] " - + "[-l address] [--print-address] [-r] [--pidfile file] [-p file] [--addressfile file] " - + "[--auth-mode AUTH_ANONYMOUS|AUTH_COOKIE|AUTH_EXTERNAL] [-m AUTH_ANONYMOUS|AUTH_COOKIE|AUTH_EXTERNAL]" - + "[-a file] [--unix] [-u] [--tcp] [-t] "); - System.exit(1); - } - - public static void version() { - System.out.println("D-Bus Java Version: " + System.getProperty("Version")); - System.exit(1); - } - - public static void saveFile(String _data, String _file) throws IOException { - try (PrintWriter w = new PrintWriter(new FileOutputStream(_file))) { - w.println(_data); - } - } - - public static void main(String[] _args) throws Exception { - - String addr = null; - String pidfile = null; - String addrfile = null; - String authModeStr = null; - boolean printaddress = false; - boolean unix = true; - boolean tcp = false; - // parse options - try { - for (int i = 0; i < _args.length; i++) { - if ("--help".equals(_args[i]) || "-h".equals(_args[i])) { - syntax(); - } else if ("--version".equals(_args[i]) || "-v".equals(_args[i])) { - version(); - } else if ("--listen".equals(_args[i]) || "-l".equals(_args[i])) { - addr = _args[++i]; - } else if ("--pidfile".equals(_args[i]) || "-p".equals(_args[i])) { - pidfile = _args[++i]; - } else if ("--addressfile".equals(_args[i]) || "-a".equals(_args[i])) { - addrfile = _args[++i]; - } else if ("--print-address".equals(_args[i]) || "-r".equals(_args[i])) { - printaddress = true; - } else if ("--unix".equals(_args[i]) || "-u".equals(_args[i])) { - unix = true; - tcp = false; - } else if ("--tcp".equals(_args[i]) || "-t".equals(_args[i])) { - tcp = true; - unix = false; - } else if ("--auth-mode".equals(_args[i]) || "-m".equals(_args[i])) { - authModeStr = _args[++i]; - } else { - syntax(); - } - } - } catch (ArrayIndexOutOfBoundsException _ex) { - syntax(); - } - - // generate a random address if none specified - if (null == addr && unix) { - addr = TransportBuilder.createDynamicSession("UNIX", true); - } else if (null == addr && tcp) { - addr = TransportBuilder.createDynamicSession("TCP", true); - } - - BusAddress address = BusAddress.of(addr); - - // print address to stdout - if (printaddress) { - System.out.println(addr); - } - - SaslAuthMode saslAuthMode = null; - if (authModeStr != null) { - String selectedMode = authModeStr; - saslAuthMode = Arrays.stream(SaslAuthMode.values()) - .filter(e -> e.name().toLowerCase().matches(selectedMode.toLowerCase())) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Auth mode '" + selectedMode + "' unsupported")); - } - - // print address to file - if (null != addrfile) { - saveFile(addr, addrfile); - } - - // print PID to file - if (null != pidfile) { - saveFile(System.getProperty("Pid"), pidfile); - } - - // start the daemon - LOGGER.info("Binding to {}", addr); - try (EmbeddedDBusDaemon daemon = new EmbeddedDBusDaemon(address)) { - daemon.setSaslAuthMode(saslAuthMode); - daemon.startInForeground(); - } - - } - - /** - * Create a 'NameAcquired' signal manually. - * This is required because the implementation in DBusNameAquired is for receiving of this signal only. - * - * @param _name name to announce - * - * @return signal - * @throws DBusException if signal creation fails - */ - private DBusSignal generateNameAcquiredSignal(String _name) throws DBusException { - return new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameAcquired", "s", _name); - } - - /** - * Create a 'NameOwnerChanged' signal manually. - * This is required because the implementation in DBusNameAquired is for receiving of this signal only. - * - * @param _name name to announce - * @param _oldOwner previous owner - * @param _newOwner new owner - * - * @return signal - * @throws DBusException if signal creation fails - */ - private DBusSignal generatedNameOwnerChangedSignal(String _name, String _oldOwner, String _newOwner) throws DBusException { - return new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", _name, _oldOwner, _newOwner); - } - - public static class ConnectionStruct { - private final TransportConnection connection; - private String unique; - - ConnectionStruct(TransportConnection _c) throws IOException { - connection = _c; - } - - @Override - public String toString() { - return null == unique ? ":?-?" : unique; - } - } - - public class DBusServer implements DBus, Introspectable, Peer { - - private final String machineId; - private ConnectionStruct connStruct; - - public DBusServer() { - String ascii; - try { - ascii = Hexdump.toAscii(MessageDigest.getInstance("MD5").digest(InetAddress.getLocalHost().getHostName().getBytes())); - } catch (NoSuchAlgorithmException | UnknownHostException _ex) { - ascii = this.hashCode() + ""; - } - - machineId = ascii; - } - - @Override - public boolean isRemote() { - return false; - } - - @Override - public String Hello() { - synchronized (connStruct) { - if (null != connStruct.unique) { - throw new AccessDenied("Connection has already sent a Hello message"); - } - connStruct.unique = ":1." + nextUnique.incrementAndGet(); - } - names.put(connStruct.unique, connStruct); - - LOGGER.info("Client {} registered", connStruct.unique); - - try { - send(connStruct, generateNameAcquiredSignal(connStruct.unique)); - send(null, generatedNameOwnerChangedSignal(connStruct.unique, "", connStruct.unique)); - } catch (DBusException _ex) { - LOGGER.debug("", _ex); - } - - return connStruct.unique; - } - - @Override - public String[] ListNames() { - String[] ns; - Set nss = names.keySet(); - ns = nss.toArray(new String[0]); - return ns; - } - - @Override - public boolean NameHasOwner(String _name) { - return names.containsKey(_name); - } - - @Override - public String GetNameOwner(String _name) { - - ConnectionStruct owner = names.get(_name); - String o; - if (null == owner) { - o = ""; - } else { - o = owner.unique; - } - - return o; - } - - @Override - public UInt32 GetConnectionUnixUser(String _connectionName) { - return new UInt32(0); - } - - @Override - public UInt32 StartServiceByName(String _name, UInt32 _flags) { - return new UInt32(0); - } - - @Override - @SuppressWarnings("checkstyle:innerassignment") - public UInt32 RequestName(String _name, UInt32 _flags) { - boolean exists = false; - synchronized (names) { - if (!(exists = names.containsKey(_name))) { - names.put(_name, connStruct); - } - } - - int rv; - if (exists) { - rv = DBus.DBUS_REQUEST_NAME_REPLY_EXISTS; - } else { - - LOGGER.info("Client {} acquired name {}", connStruct.unique, _name); - - rv = DBus.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER; - try { - send(connStruct, generateNameAcquiredSignal(_name)); - send(null, generatedNameOwnerChangedSignal(_name, "", connStruct.unique)); - } catch (DBusException _ex) { - LOGGER.debug("", _ex); - } - } - return new UInt32(rv); - } - - @Override - public UInt32 ReleaseName(String _name) { - - boolean exists = false; - synchronized (names) { - if (names.containsKey(_name) && names.get(_name).equals(connStruct)) { - exists = names.remove(_name) != null; - } - } - - int rv; - if (!exists) { - rv = DBus.DBUS_RELEASE_NAME_REPLY_NON_EXISTANT; - } else { - LOGGER.info("Client {} acquired name {}", connStruct.unique, _name); - rv = DBus.DBUS_RELEASE_NAME_REPLY_RELEASED; - try { - send(connStruct, new NameLost("/org/freedesktop/DBus", _name)); - send(null, new NameOwnerChanged("/org/freedesktop/DBus", _name, connStruct.unique, "")); - } catch (DBusException _ex) { - LOGGER.debug("", _ex); - } - } - - return new UInt32(rv); - } - - @Override - public void AddMatch(String _matchrule) throws MatchRuleInvalid { - - LOGGER.trace("Adding match rule: {}", _matchrule); - - synchronized (sigrecips) { - if (!sigrecips.contains(connStruct)) { - sigrecips.add(connStruct); - } - } - } - - @Override - public void RemoveMatch(String _matchrule) throws MatchRuleInvalid { - LOGGER.trace("Removing match rule: {}", _matchrule); - } - - @Override - public String[] ListQueuedOwners(String _name) { - return new String[0]; - } - - @Override - public UInt32 GetConnectionUnixProcessID(String _connectionName) { - return new UInt32(0); - } - - @Override - public Byte[] GetConnectionSELinuxSecurityContext(String _args) { - return new Byte[0]; - } - - @SuppressWarnings("unchecked") - private void handleMessage(ConnectionStruct _connStruct, Message _msg) throws DBusException { - LOGGER.trace("Handling message {} from {}", _msg, _connStruct.unique); - - if (!(_msg instanceof MethodCall)) { - return; - } - Object[] args = _msg.getParameters(); - - Class[] cs = new Class[args.length]; - - for (int i = 0; i < cs.length; i++) { - cs[i] = args[i].getClass(); - } - - java.lang.reflect.Method meth = null; - Object rv = null; - - try { - meth = DBusServer.class.getMethod(_msg.getName(), cs); - try { - this.connStruct = _connStruct; - rv = meth.invoke(dbusServer, args); - if (null == rv) { - - send(_connStruct, new MethodReturn("org.freedesktop.DBus", (MethodCall) _msg, null), true); - } else { - String sig = Marshalling.getDBusType(meth.getGenericReturnType())[0]; - send(_connStruct, new MethodReturn("org.freedesktop.DBus", (MethodCall) _msg, sig, rv), true); - } - } catch (InvocationTargetException _exIte) { - LOGGER.debug("", _exIte); - send(_connStruct, new Error("org.freedesktop.DBus", _msg, _exIte.getCause())); - } catch (DBusExecutionException _exDnEe) { - LOGGER.debug("", _exDnEe); - send(_connStruct, new Error("org.freedesktop.DBus", _msg, _exDnEe)); - } catch (Exception _ex) { - LOGGER.debug("", _ex); - send(_connStruct, new Error("org.freedesktop.DBus", _connStruct.unique, - "org.freedesktop.DBus.Error.GeneralError", _msg.getSerial(), "s", "An error occurred while calling " + _msg.getName())); - } - } catch (NoSuchMethodException _exNsm) { - send(_connStruct, new Error("org.freedesktop.DBus", _connStruct.unique, - "org.freedesktop.DBus.Error.UnknownMethod", _msg.getSerial(), "s", "This service does not support " + _msg.getName())); - } - - } - - @Override - public String getObjectPath() { - return null; - } - - @Override - public String Introspect() { - return "\n" - + "\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" + ""; - } - - @Override - public void Ping() { - } - - @Override - public String[] ListActivatableNames() { - return null; - } - - @Override - public Map> GetConnectionCredentials(String _busName) { - return null; - } - - @Override - public Byte[] GetAdtAuditSessionData(String _busName) { - return null; - } - - @Override - public void UpdateActivationEnvironment(Map[] _environment) { - - } - - @Override - public String GetId() { - return null; - } - - @Override - public String GetMachineId() { - return machineId; - } - - } - - public class DBusDaemonSenderThread extends Thread { - private final Logger logger = LoggerFactory.getLogger(getClass()); - private final AtomicBoolean running = new AtomicBoolean(false); // switch running status when thread begins - - public DBusDaemonSenderThread() { - setName(getClass().getSimpleName().replace('$', '-')); - } - - @Override - public void run() { - logger.debug(">>>> Sender thread started <<<<"); - running.set(true); - while (isRunning() && running.get()) { - - logger.trace("Acquiring lock on outqueue and blocking for data"); - - // block on outqueue - try { - Pair> pollFirst = outqueue.take(); - if (pollFirst != null) { - ConnectionStruct connectionStruct = pollFirst.second.get(); - if (connectionStruct != null) { - if (connectionStruct.connection.getChannel().isConnected()) { - logger.debug(" Got message {} for {}", pollFirst.first, connectionStruct.unique); - - try { - connectionStruct.connection.getWriter().writeMessage(pollFirst.first); - } catch (IOException _ex) { - logger.debug("Disconnecting client due to previous exception", _ex); - removeConnection(connectionStruct); - } - } else { - logger.warn("Connection to {} broken", pollFirst.first.getDestination()); - removeConnection(connectionStruct); - } - - } else { - logger.info("Discarding {} connection reaped", pollFirst.first); - } - } - } catch (InterruptedException _ex) { - logger.debug("Got interrupted", _ex); - } - } - logger.debug(">>>> Sender Thread terminated <<<<"); - } - - public synchronized void terminate() { - running.set(false); - interrupt(); - } - } - - public class DBusDaemonReaderThread extends Thread { - private final Logger logger = LoggerFactory.getLogger(getClass()); - private ConnectionStruct conn; - private final WeakReference weakconn; - private final AtomicBoolean running = new AtomicBoolean(false); - - public DBusDaemonReaderThread(ConnectionStruct _conn) { - this.conn = _conn; - weakconn = new WeakReference<>(_conn); - setName(getClass().getSimpleName()); - } - - public void terminate() { - running.set(false); - } - - @Override - public void run() { - logger.debug(">>>> Reader Thread started <<<<"); - running.set(true); - while (isRunning() && running.get()) { - - Message m = null; - try { - m = conn.connection.getReader().readMessage(); - } catch (IOException _ex) { - LOGGER.debug("", _ex); - removeConnection(conn); - } catch (DBusException _ex) { - LOGGER.debug("", _ex); - if (_ex instanceof FatalException) { - removeConnection(conn); - } - } - - if (null != m) { - logMessage("Read {} from {}", m, conn.unique); - - inqueue.add(new Pair<>(m, weakconn)); - } - } - conn = null; - logger.debug(">>>> Reader Thread terminated <<<<"); - } - } - - static class Pair { - private final A first; - private final B second; - - Pair(A _first, B _second) { - first = _first; - second = _second; - } - - @Override - public int hashCode() { - return Objects.hash(first, second); - } - - @Override - public boolean equals(Object _obj) { - if (this == _obj) { - return true; - } - if (!(_obj instanceof Pair)) { - return false; - } - Pair other = (Pair) _obj; - return Objects.equals(first, other.first) && Objects.equals(second, other.second); - } - - } -} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/bin/EmbeddedDBusDaemon.java b/federation/sssd/src/main/java/org/freedesktop/dbus/bin/EmbeddedDBusDaemon.java deleted file mode 100644 index b40792542d61..000000000000 --- a/federation/sssd/src/main/java/org/freedesktop/dbus/bin/EmbeddedDBusDaemon.java +++ /dev/null @@ -1,224 +0,0 @@ -package org.freedesktop.dbus.bin; - -import org.freedesktop.dbus.connections.BusAddress; -import org.freedesktop.dbus.connections.transports.AbstractTransport; -import org.freedesktop.dbus.connections.transports.TransportBuilder; -import org.freedesktop.dbus.connections.transports.TransportBuilder.SaslAuthMode; -import org.freedesktop.dbus.connections.transports.TransportConnection; -import org.freedesktop.dbus.exceptions.AuthenticationException; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.SocketClosedException; -import org.freedesktop.dbus.utils.Util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Closeable; -import java.io.IOException; -import java.nio.file.attribute.PosixFilePermission; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Simple DBusDaemon implementation to use if no DBusDaemon is running on the OS level. - */ -public class EmbeddedDBusDaemon implements Closeable { - - private static final Logger LOGGER = LoggerFactory.getLogger(EmbeddedDBusDaemon.class); - - private final BusAddress address; - - private DBusDaemon daemon; - - private final AtomicBoolean closed = new AtomicBoolean(false); - private final AtomicBoolean connectionReady = new AtomicBoolean(false); - - private SaslAuthMode saslAuthMode; - - private String unixSocketFileOwner; - - private String unixSocketFileGroup; - - private PosixFilePermission[] unixSocketFilePermissions; - - public EmbeddedDBusDaemon(BusAddress _address) { - // create copy of address so manipulation happens later does not interfere with our instance - address = BusAddress.of(Objects.requireNonNull(_address, "Address required")); - } - - public EmbeddedDBusDaemon(String _address) throws DBusException { - this(BusAddress.of(_address)); - } - - /** - * Shutdown the running DBusDaemon instance. - */ - @Override - public synchronized void close() throws IOException { - closed.set(true); - connectionReady.set(false); - if (daemon != null) { - daemon.close(); - daemon = null; - } - } - - /** - * Run the DBusDaemon in foreground. - *

- * This is a blocking operation. - */ - public void startInForeground() { - try { - startListening(); - } catch (IOException | DBusException _ex) { - if (!closed.get()) { - throw new RuntimeException(_ex); - } - } - } - - /** - * Start the DBusDaemon in background and returns immediately. - *

- * This method may return before the background thread is ready. - * To ensure the the background thread is running on return use {@link #startInBackgroundAndWait(long)}. - */ - public void startInBackground() { - Thread thread = new Thread(this::startInForeground); - String threadName = address.toString().replaceAll("^([^,]+),.+", "$1"); - - thread.setName("EmbeddedDBusDaemon-" + threadName); - thread.setDaemon(true); - thread.setUncaughtExceptionHandler((th, ex) -> LOGGER.error("Got uncaught exception", ex)); - thread.start(); - } - - /** - * Starts the DBusDaemon in background. - *

- * Will wait up to the given period of milliseconds for the background thread to get ready. - * If given wait time exceeded, a {@link RuntimeException} is thrown. - * - * @param _maxWaitMillis maximum wait time in milliseconds - */ - public void startInBackgroundAndWait(long _maxWaitMillis) { - startInBackground(); - Util.waitFor("EmbeddedDbusDaemon", this::isRunning, _maxWaitMillis, 100); - } - - /** - * Whether the DBusDaemon is still running. - * - * @return true if running, false otherwise - */ - public synchronized boolean isRunning() { - return connectionReady.get() && daemon != null && daemon.isRunning(); - } - - /** - * The currently configured {@link SaslAuthMode}. - * When null is returned, the {@link SaslAuthMode} of the transport provider is used. - * - * @return {@link SaslAuthMode} or null - */ - public SaslAuthMode getSaslAuthMode() { - return saslAuthMode; - } - - /** - * Use this to override the default authentication mode which would - * be used by the transport based on the {@link BusAddress}. - * - * @param _saslAuthMode auth mode, null to use default - */ - public void setSaslAuthMode(SaslAuthMode _saslAuthMode) { - saslAuthMode = _saslAuthMode; - } - - /** - * The file owner for the created unix socket.
- * Ignored if TCP is used.
- *
- * Will only work if currently running JVM process user - * has suitable permissions to change the owner. - * - * @param _owner owner to set - */ - public void setUnixSocketOwner(String _owner) { - unixSocketFileOwner = _owner; - } - - /** - * The file group for the created unix socket.
- * Ignored if TCP is used.
- *
- * Will only work if currently running JVM process user - * has suitable permissions to change the group. - * - * @param _group group to set - */ - public void setUnixSocketGroup(String _group) { - unixSocketFileGroup = _group; - } - - /** - * The file permissions for the created unix socket.
- * Ignored if TCP is used or if the OS is Windows.
- *
- * Will only work if currently running JVM process user - * has suitable permissions to change the permissions. - * - * @param _permissions permissions to set - */ - public void setUnixSocketPermissions(PosixFilePermission... _permissions) { - unixSocketFilePermissions = _permissions; - } - - private synchronized void setDaemonAndStart(AbstractTransport _transport) { - daemon = new DBusDaemon(_transport); - daemon.start(); - } - - /** - * Start listening for incoming connections. - *

- * Will throw {@link IllegalArgumentException} if a unsupported transport is used. - * - * @throws IOException when connection fails - * @throws DBusException when the provided bus address is wrong - */ - private void startListening() throws IOException, DBusException { - if (!TransportBuilder.getRegisteredBusTypes().contains(address.getBusType())) { - throw new IllegalArgumentException("Unknown or unsupported address type: " + address.getType()); - } - - LOGGER.debug("About to initialize transport on: {}", address); - try (AbstractTransport transport = TransportBuilder.create(address).configure() - .withUnixSocketFileOwner(unixSocketFileOwner) - .withUnixSocketFileGroup(unixSocketFileGroup) - .withUnixSocketFilePermissions(unixSocketFilePermissions) - .withAutoConnect(false) - .configureSasl().withAuthMode(getSaslAuthMode()).back() - .back() - .build()) { - - setDaemonAndStart(transport); - - // use tail-controlled loop so we at least try to get a client connection once - do { - try { - LOGGER.debug("Begin listening to: {}", transport); - connectionReady.set(true); - TransportConnection s = transport.listen(); - daemon.addSock(s); - } catch (AuthenticationException _ex) { - LOGGER.error("Authentication failed", _ex); - } catch (SocketClosedException _ex) { - LOGGER.debug("Connection closed", _ex); - } - - } while (daemon.isRunning()); - - } - } -} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/connections/impl/DirectConnection.java b/federation/sssd/src/main/java/org/freedesktop/dbus/connections/impl/DirectConnection.java deleted file mode 100644 index 60ba444b88f1..000000000000 --- a/federation/sssd/src/main/java/org/freedesktop/dbus/connections/impl/DirectConnection.java +++ /dev/null @@ -1,300 +0,0 @@ -package org.freedesktop.dbus.connections.impl; - -import static org.freedesktop.dbus.utils.CommonRegexPattern.IFACE_PATTERN; -import static org.freedesktop.dbus.utils.CommonRegexPattern.PROXY_SPLIT_PATTERN; - -import org.freedesktop.dbus.DBusMatchRule; -import org.freedesktop.dbus.RemoteInvocationHandler; -import org.freedesktop.dbus.RemoteObject; -import org.freedesktop.dbus.connections.AbstractConnection; -import org.freedesktop.dbus.connections.BusAddress; -import org.freedesktop.dbus.connections.config.ReceivingServiceConfig; -import org.freedesktop.dbus.connections.config.TransportConfig; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.interfaces.DBusInterface; -import org.freedesktop.dbus.interfaces.DBusSigHandler; -import org.freedesktop.dbus.interfaces.Introspectable; -import org.freedesktop.dbus.messages.DBusSignal; -import org.freedesktop.dbus.messages.ExportedObject; -import org.freedesktop.dbus.utils.Hexdump; -import org.freedesktop.dbus.utils.Util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Proxy; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.stream.Collectors; - -/** - * Handles a peer to peer connection between two applications without a bus daemon. - *

- * Signal Handlers and method calls from remote objects are run in their own threads, you MUST handle the concurrency issues. - *

- */ -public class DirectConnection extends AbstractConnection { - private final Logger logger = LoggerFactory.getLogger(getClass()); - private final String machineId; - - /** - * Create a direct connection to another application. - * @param _address The address to connect to. This is a standard D-Bus address, except that the additional parameter 'listen=true' should be added in the application which is creating the socket. - * @throws DBusException on error - * @deprecated use {@link DirectConnectionBuilder} - */ - @Deprecated(since = "4.1.0", forRemoval = true) - public DirectConnection(String _address) throws DBusException { - this(_address, AbstractConnection.TCP_CONNECT_TIMEOUT); - } - - /** - * Create a direct connection to another application. - * @param _address The address to connect to. This is a standard D-Bus address, except that the additional parameter 'listen=true' should be added in the application which is creating the socket. - * @param _timeout the timeout set for the underlying socket. 0 will block forever on the underlying socket. - * @throws DBusException on error - * @deprecated use {@link DirectConnectionBuilder} - */ - @Deprecated(since = "4.1.0", forRemoval = true) - public DirectConnection(String _address, int _timeout) throws DBusException { - this(createTransportConfig(_address, _timeout), null); - } - - DirectConnection(TransportConfig _transportCfg, ReceivingServiceConfig _rsCfg) throws DBusException { - super(_transportCfg, _rsCfg); - machineId = createMachineId(); - if (!getAddress().isServer()) { - super.listen(); - } - } - - @Deprecated(since = "4.2.0", forRemoval = true) - static TransportConfig createTransportConfig(String _address, int _timeout) { - TransportConfig cfg = new TransportConfig(); - cfg.setBusAddress(BusAddress.of(_address)); - cfg.getAdditionalConfig().put("TIMEOUT", _timeout); - return cfg; - } - - /** - * Use this method when running on server side. - * Call will block. - */ - @Override - public void listen() { - if (getAddress().isServer()) { - super.listen(); - } - } - - private String createMachineId() { - String ascii; - - try { - ascii = Hexdump.toAscii(MessageDigest.getInstance("MD5").digest(InetAddress.getLocalHost().getHostName().getBytes())); - return ascii; - } catch (NoSuchAlgorithmException _ex) { - logger.trace("MD5 algorithm not present", _ex); - } catch (UnknownHostException _ex) { - logger.trace("Unable to determine this machines hostname", _ex); - } - - return Util.randomString(32); - } - - @SuppressWarnings("unchecked") - T dynamicProxy(String _path, Class _type) throws DBusException { - try { - Introspectable intro = getRemoteObject(_path, Introspectable.class); - String data = intro.Introspect(); - - String[] tags = PROXY_SPLIT_PATTERN.split(data); - - List ifaces = Arrays.stream(tags).filter(t -> t.startsWith("interface")) - .map(t -> IFACE_PATTERN.matcher(t).replaceAll("$1")) - .collect(Collectors.toList()); - - List> ifcs = findMatchingTypes(_type, ifaces); - - if (ifcs.isEmpty()) { - throw new DBusException("Could not find an interface to cast to"); - } - - RemoteObject ro = new RemoteObject(null, _path, _type, false); - DBusInterface newi = (DBusInterface) Proxy.newProxyInstance(ifcs.get(0).getClassLoader(), ifcs.toArray(new Class[0]), new RemoteInvocationHandler(this, ro)); - getImportedObjects().put(newi, ro); - return (T) newi; - } catch (Exception _ex) { - logger.debug("", _ex); - throw new DBusException(String.format("Failed to create proxy object for %s; reason: %s.", _path, _ex.getMessage())); - } - } - - @SuppressWarnings("unchecked") - T getExportedObject(String _path, Class _type) throws DBusException { - ExportedObject o = null; - synchronized (getExportedObjects()) { - o = getExportedObjects().get(_path); - } - if (null != o && null == o.getObject().get()) { - unExportObject(_path); - o = null; - } - if (null != o) { - return (T) o.getObject().get(); - } - return dynamicProxy(_path, _type); - } - - /** - * Return a reference to a remote object. - * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. - * In particular this means that if a process providing the well known name disappears and is taken over by another process - * proxy objects gained by this method will make calls on the new proccess. - * - * This method will use bus introspection to determine the interfaces on a remote object and so - * may block and may fail. The resulting proxy object will, however, be castable - * to any interface it implements. It will also autostart the process if applicable. Also note - * that the resulting proxy may fail to execute the correct method with overloaded methods - * and that complex types may fail in interesting ways. Basically, if something odd happens, - * try specifying the interface explicitly. - * - * @param _objectPath The path on which the process is exporting the object. - * @return A reference to a remote object. - * @throws ClassCastException If type is not a sub-type of DBusInterface - * @throws DBusException If busname or objectpath are incorrectly formatted. - */ - public DBusInterface getRemoteObject(String _objectPath) throws DBusException { - if (null == _objectPath) { - throw new DBusException("Invalid object path: null"); - } - - if (_objectPath.length() > MAX_NAME_LENGTH || !OBJECT_REGEX_PATTERN.matcher(_objectPath).matches()) { - throw new DBusException("Invalid object path: " + _objectPath); - } - - return dynamicProxy(_objectPath, null); - } - - /** - * Return a reference to a remote object. - * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. - * In particular this means that if a process providing the well known name disappears and is taken over by another process - * proxy objects gained by this method will make calls on the new proccess. - * @param _objectPath The path on which the process is exporting the object. - * @param _type The interface they are exporting it on. This type must have the same full class name and exposed method signatures - * as the interface the remote object is exporting. - * @param class which extends DBusInterface - * @return A reference to a remote object. - * @throws ClassCastException If type is not a sub-type of DBusInterface - * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. - */ - public T getRemoteObject(String _objectPath, Class _type) throws DBusException { - if (null == _objectPath) { - throw new DBusException("Invalid object path: null"); - } - if (null == _type) { - throw new ClassCastException("Not A DBus Interface"); - } - - if (_objectPath.length() > MAX_NAME_LENGTH || !OBJECT_REGEX_PATTERN.matcher(_objectPath).matches()) { - throw new DBusException("Invalid object path: " + _objectPath); - } - - if (!DBusInterface.class.isAssignableFrom(_type)) { - throw new ClassCastException("Not A DBus Interface"); - } - - // don't let people import things which don't have a - // valid D-Bus interface name - if (_type.getName().equals(_type.getSimpleName())) { - throw new DBusException("DBusInterfaces cannot be declared outside a package"); - } - - RemoteObject ro = new RemoteObject(null, _objectPath, _type, false); - - @SuppressWarnings("unchecked") - T i = (T) Proxy.newProxyInstance(_type.getClassLoader(), - new Class[] {_type}, new RemoteInvocationHandler(this, ro)); - - getImportedObjects().put(i, ro); - - return i; - } - - @Override - protected void removeSigHandler(DBusMatchRule _rule, DBusSigHandler _handler) throws DBusException { - Queue> v = getHandledSignals().get(_rule); - if (null != v) { - v.remove(_handler); - if (0 == v.size()) { - getHandledSignals().remove(_rule); - } - } - } - - @Override - protected AutoCloseable addSigHandler(DBusMatchRule _rule, DBusSigHandler _handler) throws DBusException { - Queue> v = - getHandledSignals().computeIfAbsent(_rule, val -> { - Queue> l = new ConcurrentLinkedQueue<>(); - return l; - }); - - v.add(_handler); - return new AutoCloseable() { - @Override - public void close() throws Exception { - removeSigHandler(_rule, _handler); - } - }; - } - - @Override - protected void removeGenericSigHandler(DBusMatchRule _rule, DBusSigHandler _handler) throws DBusException { - Queue> v = getGenericHandledSignals().get(_rule); - if (null != v) { - v.remove(_handler); - if (0 == v.size()) { - getGenericHandledSignals().remove(_rule); - } - } - } - - @Override - protected AutoCloseable addGenericSigHandler(DBusMatchRule _rule, DBusSigHandler _handler) throws DBusException { - Queue> v = - getGenericHandledSignals().computeIfAbsent(_rule, val -> { - Queue> l = new ConcurrentLinkedQueue<>(); - return l; - }); - - v.add(_handler); - return new AutoCloseable() { - @Override - public void close() throws Exception { - removeGenericSigHandler(_rule, _handler); - } - }; - } - - @Override - public T getExportedObject(String _source, String _path, Class _type) throws DBusException { - return getExportedObject(_path, _type); - } - - @Override - public String getMachineId() { - return machineId; - } - - @Override - public DBusInterface getExportedObject(String _source, String _path) throws DBusException { - return getExportedObject(_path, (Class) null); - } -} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/connections/impl/DirectConnectionBuilder.java b/federation/sssd/src/main/java/org/freedesktop/dbus/connections/impl/DirectConnectionBuilder.java deleted file mode 100644 index 1e291656b17d..000000000000 --- a/federation/sssd/src/main/java/org/freedesktop/dbus/connections/impl/DirectConnectionBuilder.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.freedesktop.dbus.connections.impl; - -import org.freedesktop.dbus.connections.BusAddress; -import org.freedesktop.dbus.connections.config.ReceivingServiceConfig; -import org.freedesktop.dbus.connections.config.TransportConfig; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.messages.Message; - -import java.nio.ByteOrder; - -/** - * Builder to create a new DirectConnection. - * - * @author hypfvieh - * @version 4.1.0 - 2022-02-04 - */ -public final class DirectConnectionBuilder extends BaseConnectionBuilder { - - private DirectConnectionBuilder(BusAddress _address) { - super(DirectConnectionBuilder.class, _address); - } - - /** - * Use the given address to create the connection (e.g. used for remote TCP connected DBus daemons). - * - * @param _address address to use - * @return this - */ - public static DirectConnectionBuilder forAddress(String _address) { - DirectConnectionBuilder instance = new DirectConnectionBuilder(BusAddress.of(_address)); - return instance; - } - - /** - * Create the new {@link DBusConnection}. - * - * @return {@link DBusConnection} - * @throws DBusException when DBusConnection could not be opened - */ - @Override - public DirectConnection build() throws DBusException { - ReceivingServiceConfig rsCfg = buildThreadConfig(); - TransportConfig transportCfg = buildTransportConfig(); - - DirectConnection c = new DirectConnection(transportCfg, rsCfg); - c.setDisconnectCallback(getDisconnectCallback()); - c.setWeakReferences(isWeakReference()); - DirectConnection.setEndianness(getEndianess()); - return c; - } - - /** - * Get the default system endianness. - * - * @return LITTLE or BIG - * @deprecated if required, use {@link BaseConnectionBuilder#getSystemEndianness()} - */ - @Deprecated(forRemoval = true, since = "4.2.0") - public static byte getSystemEndianness() { - return ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN) - ? Message.Endian.BIG - : Message.Endian.LITTLE; - } -} From 55751a0830dbb33f2e1f20eb86cacec41636dfb9 Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:20:05 +0800 Subject: [PATCH 14/19] Fix client assertion with invalid ES256, ES384, ES512 signatures Closes #23721 --- .../keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java index 25aaf89f5264..1fb4d684dfe2 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java @@ -34,9 +34,9 @@ import org.keycloak.common.ClientConnection; import org.keycloak.common.util.Time; import org.keycloak.crypto.Algorithm; -import org.keycloak.crypto.AsymmetricSignatureProvider; import org.keycloak.crypto.KeyWrapper; import org.keycloak.crypto.MacSignatureSignerContext; +import org.keycloak.crypto.SignatureProvider; import org.keycloak.crypto.SignatureSignerContext; import org.keycloak.events.Details; import org.keycloak.events.Errors; @@ -447,7 +447,7 @@ protected SignatureSignerContext getSignatureContext() { } } String alg = getConfig().getClientAssertionSigningAlg() != null ? getConfig().getClientAssertionSigningAlg() : Algorithm.RS256; - return new AsymmetricSignatureProvider(session, alg).signer(); + return session.getProvider(SignatureProvider.class, alg).signer(); } protected static class Endpoint { From 7f2f4aae67093685757c420ee992a360341261fc Mon Sep 17 00:00:00 2001 From: vramik Date: Wed, 27 Sep 2023 13:53:58 +0200 Subject: [PATCH 15/19] Upgrade liquibase version to avoid a bug where a changeset is executed twice Closes #23220 --- .../jpa/updater/liquibase/custom/CustomCreateIndexChange.java | 2 +- pom.xml | 2 +- .../org/keycloak/testsuite/zerodowntime/ZeroDowntimeTest.java | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/CustomCreateIndexChange.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/CustomCreateIndexChange.java index 3daaa64790ef..f507b6bf134f 100644 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/CustomCreateIndexChange.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/CustomCreateIndexChange.java @@ -74,7 +74,7 @@ public SqlStatement[] generateStatements(Database database) { } try { // To check that the table already exists or not on which the index will be created. - if (!SnapshotGeneratorFactory.getInstance() + if (getTableName() == null || !SnapshotGeneratorFactory.getInstance() .has(new Table().setName(getTableName()).setSchema(new Schema(getCatalogName(), getSchemaName())), database)) return super.generateStatements(database); diff --git a/pom.xml b/pom.xml index cec73caa3830..1d3bd725a587 100644 --- a/pom.xml +++ b/pom.xml @@ -138,7 +138,7 @@ 2.3.32 ${jetty94.version} - 4.20.0 + 4.23.2 4.2.0 7.1.0 1.0.2.Final diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/zerodowntime/ZeroDowntimeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/zerodowntime/ZeroDowntimeTest.java index 56ed6873ba71..b60e0d6feda5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/zerodowntime/ZeroDowntimeTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/zerodowntime/ZeroDowntimeTest.java @@ -30,6 +30,7 @@ import org.jboss.arquillian.test.api.ArquillianResource; import org.junit.Assume; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.KeycloakBuilder; @@ -75,6 +76,7 @@ * * @author vramik */ +@Ignore public class ZeroDowntimeTest extends AbstractKeycloakTest { @ArquillianResource private ContainerController controller; From 2dfbbff3431ae273c522d26536a8c85a29c9f5bc Mon Sep 17 00:00:00 2001 From: Garth <244253+xgp@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:08:01 +0200 Subject: [PATCH 16/19] added AccountResource SPI, Provider and ProviderFactory. (#22317) Added AccountResource SPI, Provider and ProviderFactory. updated AccountLoader to load provider(s) and check if it is compatible with the chosen theme. --- .../resource/AccountResourceProvider.java | 15 +++++ .../AccountResourceProviderFactory.java | 9 +++ .../services/resource/AccountResourceSpi.java | 34 ++++++++++++ .../services/org.keycloak.provider.Spi | 1 + .../main/java/org/keycloak/theme/Theme.java | 2 + .../resources/account/AccountConsole.java | 12 +++- .../account/AccountConsoleFactory.java | 55 +++++++++++++++++++ .../resources/account/AccountLoader.java | 18 ++++-- ...es.resource.AccountResourceProviderFactory | 1 + .../CustomAccountResourceProviderFactory.java | 47 ++++++++++++++++ ...es.resource.AccountResourceProviderFactory | 1 + .../account/AccountRestServiceTest.java | 22 ++++++++ .../testsuite/admin/ServerInfoTest.java | 2 +- .../CustomAccountResourceProviderTest.java | 27 +++++++++ .../resources/META-INF/keycloak-themes.json | 8 ++- .../account/theme.properties | 1 + 16 files changed, 248 insertions(+), 7 deletions(-) create mode 100644 server-spi-private/src/main/java/org/keycloak/services/resource/AccountResourceProvider.java create mode 100644 server-spi-private/src/main/java/org/keycloak/services/resource/AccountResourceProviderFactory.java create mode 100644 server-spi-private/src/main/java/org/keycloak/services/resource/AccountResourceSpi.java create mode 100644 services/src/main/java/org/keycloak/services/resources/account/AccountConsoleFactory.java create mode 100644 services/src/main/resources/META-INF/services/org.keycloak.services.resource.AccountResourceProviderFactory create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/theme/CustomAccountResourceProviderFactory.java create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.services.resource.AccountResourceProviderFactory create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/theme/CustomAccountResourceProviderTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/theme/custom-account-provider/account/theme.properties diff --git a/server-spi-private/src/main/java/org/keycloak/services/resource/AccountResourceProvider.java b/server-spi-private/src/main/java/org/keycloak/services/resource/AccountResourceProvider.java new file mode 100644 index 000000000000..8f64d40db857 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/resource/AccountResourceProvider.java @@ -0,0 +1,15 @@ +package org.keycloak.services.resource; + +import org.keycloak.provider.Provider; +import org.keycloak.theme.Theme; + +import java.io.IOException; + +/** + *

A {@link AccountResourceProvider} creates JAX-RS resource instances for the Account endpoints, allowing + * an implementor to override the behavior of the entire Account console. + */ +public interface AccountResourceProvider extends Provider { + /** Returns a JAX-RS resource instance. */ + Object getResource(); +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/resource/AccountResourceProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/services/resource/AccountResourceProviderFactory.java new file mode 100644 index 000000000000..8a9c0ce2fde8 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/resource/AccountResourceProviderFactory.java @@ -0,0 +1,9 @@ +package org.keycloak.services.resource; + +import org.keycloak.provider.ProviderFactory; + +/** + *

A factory that creates {@link AccountResourceProvider} instances. + */ +public interface AccountResourceProviderFactory extends ProviderFactory { +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/resource/AccountResourceSpi.java b/server-spi-private/src/main/java/org/keycloak/services/resource/AccountResourceSpi.java new file mode 100644 index 000000000000..43889cb7a010 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/resource/AccountResourceSpi.java @@ -0,0 +1,34 @@ +package org.keycloak.services.resource; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + *

A {@link Spi} to replace Account resources. + * + *

Implementors can use this {@link Spi} to override the behavior of the Account endpoints and resources by + * creating JAX-RS resources that override those served at /account by default. + */ +public class AccountResourceSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "account-resource"; + } + + @Override + public Class getProviderClass() { + return AccountResourceProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return AccountResourceProviderFactory.class; + } +} diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 8e9c90c5beaa..6c8d5d862e5e 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -37,6 +37,7 @@ org.keycloak.exportimport.ImportSpi org.keycloak.timer.TimerSpi org.keycloak.scripting.ScriptingSpi org.keycloak.services.managers.BruteForceProtectorSpi +org.keycloak.services.resource.AccountResourceSpi org.keycloak.services.resource.RealmResourceSPI org.keycloak.sessions.AuthenticationSessionSpi org.keycloak.sessions.StickySessionEncoderSpi diff --git a/server-spi/src/main/java/org/keycloak/theme/Theme.java b/server-spi/src/main/java/org/keycloak/theme/Theme.java index cf2fe18dbf0e..e01bdf3be312 100755 --- a/server-spi/src/main/java/org/keycloak/theme/Theme.java +++ b/server-spi/src/main/java/org/keycloak/theme/Theme.java @@ -30,6 +30,8 @@ */ public interface Theme { + public static final String ACCOUNT_RESOURCE_PROVIDER_KEY = "accountResourceProvider"; + enum Type { LOGIN, ACCOUNT, ADMIN, EMAIL, WELCOME, COMMON }; String getName(); diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java b/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java index 0973df958cc6..7a18e790f3ad 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java @@ -35,6 +35,7 @@ import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.Auth; import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.resource.AccountResourceProvider; import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.util.ResolveRelative; import org.keycloak.services.validation.Validation; @@ -49,7 +50,7 @@ /** * Created by st on 29/03/17. */ -public class AccountConsole { +public class AccountConsole implements AccountResourceProvider { // Used when some other context (ie. IdentityBrokerService) wants to forward error to account management and display it here public static final String ACCOUNT_MGMT_FORWARDED_ERROR_NOTE = "ACCOUNT_MGMT_FORWARDED_ERROR"; @@ -71,6 +72,7 @@ public AccountConsole(KeycloakSession session, ClientModel client, Theme theme) this.client = client; this.theme = theme; this.authManager = new AppAuthManager(); + init(); } public void init() { @@ -80,6 +82,14 @@ public void init() { } } + @Override + public Object getResource() { + return this; + } + + @Override + public void close() {} + @GET @NoCache public Response getMainPage() throws IOException, FreeMarkerException { diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountConsoleFactory.java b/services/src/main/java/org/keycloak/services/resources/account/AccountConsoleFactory.java new file mode 100644 index 000000000000..2b994cf00fb7 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountConsoleFactory.java @@ -0,0 +1,55 @@ +package org.keycloak.services.resources.account; + +import java.io.IOException; +import org.keycloak.Config.Scope; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; +import org.keycloak.services.resource.AccountResourceProvider; +import org.keycloak.services.resource.AccountResourceProviderFactory; +import org.keycloak.theme.Theme; +import jakarta.ws.rs.InternalServerErrorException; +import jakarta.ws.rs.NotFoundException; +import org.keycloak.models.Constants; + +public class AccountConsoleFactory implements AccountResourceProviderFactory { + + @Override + public String getId() { + return "default"; + } + + @Override + public AccountResourceProvider create(KeycloakSession session) { + RealmModel realm = session.getContext().getRealm(); + ClientModel client = getAccountManagementClient(realm); + Theme theme = getTheme(session); + return new AccountConsole(session, client, theme); + } + + @Override + public void init(Scope config) {} + + @Override + public void postInit(KeycloakSessionFactory factory) {} + + @Override + public void close() {} + + static Theme getTheme(KeycloakSession session) { + try { + return session.theme().getTheme(Theme.Type.ACCOUNT); + } catch (IOException e) { + throw new InternalServerErrorException(e); + } + } + + static ClientModel getAccountManagementClient(RealmModel realm) { + ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID); + if (client == null || !client.isEnabled()) { + throw new NotFoundException("account management not enabled"); + } + return client; + } +} diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java b/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java index 9b2557f8474f..d247721abeac 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java @@ -29,6 +29,7 @@ import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.Auth; import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.resource.AccountResourceProvider; import org.keycloak.services.resources.Cors; import org.keycloak.theme.Theme; @@ -78,14 +79,14 @@ public Object getAccountService() { Theme theme = getTheme(session); UriInfo uriInfo = session.getContext().getUri(); + AccountResourceProvider accountResourceProvider = getAccountResourceProvider(theme); + if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) { return new CorsPreflightService(request); } else if ((accepts.contains(MediaType.APPLICATION_JSON_TYPE) || MediaType.APPLICATION_JSON_TYPE.equals(content)) && !uriInfo.getPath().endsWith("keycloak.json")) { return getAccountRestService(client, null); - } else if (Profile.isFeatureEnabled(Profile.Feature.ACCOUNT2) || Profile.isFeatureEnabled(Profile.Feature.ACCOUNT3)) { - AccountConsole console = new AccountConsole(session, client, theme); - console.init(); - return console; + } else if (accountResourceProvider != null) { + return accountResourceProvider.getResource(); } else { throw new NotFoundException(); } @@ -108,6 +109,7 @@ private Theme getTheme(KeycloakSession session) { } } + private AccountRestService getAccountRestService(ClientModel client, String versionStr) { AuthenticationManager.AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(session) .setAudience(client.getClientId()) @@ -147,4 +149,12 @@ private ClientModel getAccountManagementClient(RealmModel realm) { return client; } + private AccountResourceProvider getAccountResourceProvider(Theme theme) { + try { + if (theme.getProperties().containsKey(Theme.ACCOUNT_RESOURCE_PROVIDER_KEY)) { + return session.getProvider(AccountResourceProvider.class, theme.getProperties().getProperty(Theme.ACCOUNT_RESOURCE_PROVIDER_KEY)); + } + } catch (IOException ignore) {} + return session.getProvider(AccountResourceProvider.class); + } } diff --git a/services/src/main/resources/META-INF/services/org.keycloak.services.resource.AccountResourceProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.services.resource.AccountResourceProviderFactory new file mode 100644 index 000000000000..bd642ab38f77 --- /dev/null +++ b/services/src/main/resources/META-INF/services/org.keycloak.services.resource.AccountResourceProviderFactory @@ -0,0 +1 @@ +org.keycloak.services.resources.account.AccountConsoleFactory diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/theme/CustomAccountResourceProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/theme/CustomAccountResourceProviderFactory.java new file mode 100644 index 000000000000..003a2123c159 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/theme/CustomAccountResourceProviderFactory.java @@ -0,0 +1,47 @@ +package org.keycloak.testsuite.theme; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.Config.Scope; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.services.resource.AccountResourceProvider; +import org.keycloak.services.resource.AccountResourceProviderFactory; + +public class CustomAccountResourceProviderFactory implements AccountResourceProviderFactory, AccountResourceProvider { + public static final String ID = "ext-custom-account-console"; + + @Override + public String getId() { + return ID; + } + + @Override + public AccountResourceProvider create(KeycloakSession session) { + return this; + } + + @Override + public Object getResource() { + return this; + } + + @GET + @NoCache + @Produces(MediaType.TEXT_HTML) + public Response getMainPage() { + return Response.ok().entity("Account

Custom Account Console

").build(); + } + + @Override + public void init(Scope config) {} + + @Override + public void postInit(KeycloakSessionFactory factory) {} + + @Override + public void close() {} +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.services.resource.AccountResourceProviderFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.services.resource.AccountResourceProviderFactory new file mode 100644 index 000000000000..c2e7da804405 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.services.resource.AccountResourceProviderFactory @@ -0,0 +1 @@ +org.keycloak.testsuite.theme.CustomAccountResourceProviderFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java index c04f7c3fc11f..a33c047257ca 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java @@ -1614,6 +1614,28 @@ public void testAudience() throws Exception { } @Test + public void testCustomAccountResourceTheme() throws Exception { + String accountTheme = ""; + try { + RealmRepresentation realmRep = adminClient.realm("test").toRepresentation(); + accountTheme = realmRep.getAccountTheme(); + realmRep.setAccountTheme("custom-account-provider"); + adminClient.realm("test").update(realmRep); + + SimpleHttp.Response response = SimpleHttp.doGet(getAccountUrl(null), httpClient) + .header("Accept", "text/html") + .asResponse(); + assertEquals(200, response.getStatus()); + + String html = response.asString(); + assertTrue(html.contains("Custom Account Console")); + } finally { + RealmRepresentation realmRep = testRealm().toRepresentation(); + realmRep.setAccountTheme(accountTheme); + testRealm().update(realmRep); + } + } + @EnableFeature(Profile.Feature.UPDATE_EMAIL) public void testEmailWhenUpdateEmailEnabled() throws Exception { reconnectAdminClient(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ServerInfoTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ServerInfoTest.java index 1b70feb34a28..2224125effcb 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ServerInfoTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ServerInfoTest.java @@ -56,8 +56,8 @@ public void testServerInfo() { assertNotNull(info.getProviders().get("authenticator")); assertNotNull(info.getThemes()); - // Not checking account themes for now as old account console is going to be removed soon, which would remove "keycloak" theme. So that is just to avoid another "test to update" when it is removed :) assertNotNull(info.getThemes().get("account")); + Assert.assertNames(info.getThemes().get("account"), "base", "keycloak.v2", "custom-account-provider"); Assert.assertNames(info.getThemes().get("admin"), "base", "keycloak.v2"); Assert.assertNames(info.getThemes().get("email"), "base", "keycloak"); Assert.assertNames(info.getThemes().get("login"), "address", "base", "environment-agnostic", "keycloak"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/theme/CustomAccountResourceProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/theme/CustomAccountResourceProviderTest.java new file mode 100644 index 000000000000..8b2b7d0db2b5 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/theme/CustomAccountResourceProviderTest.java @@ -0,0 +1,27 @@ +package org.keycloak.testsuite.theme; + +import java.io.IOException; +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.resource.AccountResourceProvider; +import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; +import org.keycloak.testsuite.theme.CustomAccountResourceProviderFactory; +import org.keycloak.theme.Theme; + +public class CustomAccountResourceProviderTest extends AbstractTestRealmKeycloakTest { + + @Override + public void configureTestRealm(RealmRepresentation testRealm) { + + } + + @Test + public void testProviderOverride() { + testingClient.server().run(session -> { + AccountResourceProvider arp = session.getProvider(AccountResourceProvider.class, "ext-custom-account-console"); + Assert.assertTrue(arp instanceof CustomAccountResourceProviderFactory); + }); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-themes.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-themes.json index e5d4937bd742..1961f3c37737 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-themes.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-themes.json @@ -12,6 +12,12 @@ "types": [ "login" ] + }, + { + "name": "custom-account-provider", + "types": [ + "account" + ] } ] -} \ No newline at end of file +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/theme/custom-account-provider/account/theme.properties b/testsuite/integration-arquillian/tests/base/src/test/resources/theme/custom-account-provider/account/theme.properties new file mode 100644 index 000000000000..4da7224f785b --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/theme/custom-account-provider/account/theme.properties @@ -0,0 +1 @@ +accountResourceProvider=ext-custom-account-console From f100aa7e071aa9c2d3967dea4b1ff39f6087be84 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Thu, 5 Oct 2023 13:09:40 -0400 Subject: [PATCH 17/19] fix: remove common-compress (#23745) closes #23331 --- operator/pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/operator/pom.xml b/operator/pom.xml index 6be165360c98..1acf80c5fbdb 100644 --- a/operator/pom.xml +++ b/operator/pom.xml @@ -91,6 +91,10 @@ com.aayushatharva.brotli4j * + + org.apache.commons + commons-compress + From 058d00fea849a7c9ffdfa7d35aed4d324579199d Mon Sep 17 00:00:00 2001 From: Yoshikazu Nojima Date: Mon, 2 Oct 2023 10:05:04 +0900 Subject: [PATCH 18/19] Rewrite mention to add-user-keycloak since it was already removed --- .../server_admin/topics/realms/proc-using-admin-console.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/documentation/server_admin/topics/realms/proc-using-admin-console.adoc b/docs/documentation/server_admin/topics/realms/proc-using-admin-console.adoc index b22a7ca95757..0c4199c52b62 100644 --- a/docs/documentation/server_admin/topics/realms/proc-using-admin-console.adoc +++ b/docs/documentation/server_admin/topics/realms/proc-using-admin-console.adoc @@ -15,7 +15,7 @@ For example, for localhost, use this URL: http://localhost:8080{kc_admins_path}/ .Login page image:images/login-page.png[Login page] -. Enter the username and password you created on the Welcome Page or the `add-user-keycloak` script in the bin directory. +. Enter the username and password you created on the Welcome Page or through environment variables as per https://www.keycloak.org/server/configuration#_creating_the_initial_admin_user[Creating the initial admin user] guide. This action displays the Admin Console. + .Admin Console @@ -30,4 +30,4 @@ image:images/admin-console.png[Admin Console] * Hover over a question mark *?* icon to show a tooltip text that describes that field. The image above shows the tooltip in action. * Click a question mark *?* icon to show a tooltip text that describes that field. The image above shows the tooltip in action. -NOTE: Export files from the Admin Console are not suitable for backups or data transfer between servers. Only boot-time exports are suitable for backups or data transfer between servers. \ No newline at end of file +NOTE: Export files from the Admin Console are not suitable for backups or data transfer between servers. Only boot-time exports are suitable for backups or data transfer between servers. From 0853d484ece8f8c19f7310d90781c42cb8938fc9 Mon Sep 17 00:00:00 2001 From: Martin Kanis Date: Fri, 6 Oct 2023 10:00:04 +0200 Subject: [PATCH 19/19] Remove transaction in InfinispanSingleUseObjectProvider#remove (#23708) Co-authored-by: mposolda --- .../InfinispanSingleUseObjectProvider.java | 8 +- .../concurrency/ConcurrentLoginTest.java | 13 +- .../model/SingleUseProviderTest.java | 115 ++++++++++++++++++ 3 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/SingleUseProviderTest.java diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseObjectProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseObjectProvider.java index 1c033da11827..714ae30d6208 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseObjectProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseObjectProvider.java @@ -82,12 +82,8 @@ public Map get(String key) { public Map remove(String key) { try { BasicCache cache = singleUseObjectCache.get(); - SingleUseObjectValueEntity singleUseObjectValueEntity = tx.get(cache, key); - if (singleUseObjectValueEntity != null) { - tx.remove(cache, key); - return singleUseObjectValueEntity.getNotes(); - } - return null; + SingleUseObjectValueEntity existing = cache.remove(key); + return existing == null ? null : existing.getNotes(); } catch (HotRodClientException re) { // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java index 0a353abbb11a..9226139ba590 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java @@ -53,7 +53,6 @@ import org.keycloak.jose.jws.JWSInput; import org.keycloak.models.UserSessionSpi; import org.keycloak.models.map.common.AbstractMapProviderFactory; -import org.keycloak.models.map.storage.hotRod.HotRodMapStorageProviderFactory; import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; import org.keycloak.models.utils.KeycloakModelUtils; @@ -228,6 +227,10 @@ public void concurrentLoginMultipleUsers() throws Throwable { @Test public void concurrentCodeReuseShouldFail() throws Throwable { + Assume.assumeThat("Test does not work with ConcurrentHashMap storage", + userSessionProvider, + not(equalTo(MapUserSessionProviderFactory.PROVIDER_ID + "-" + ConcurrentHashMapStorageProviderFactory.PROVIDER_ID))); + log.info("*********************************************"); long start = System.currentTimeMillis(); @@ -239,7 +242,6 @@ public void concurrentCodeReuseShouldFail() throws Throwable { OAuthClient.AuthorizationEndpointResponse resp = oauth1.doLogin("test-user@localhost", "password"); String code = resp.getCode(); - String idTokenHint = oauth1.doAccessTokenRequest(code, "password").getIdToken(); Assert.assertNotNull(code); String codeURL = driver.getCurrentUrl(); @@ -265,11 +267,12 @@ public void run(int threadIndex, Keycloak keycloak, RealmResource realm) throws run(DEFAULT_THREADS, DEFAULT_THREADS, codeToTokenTask); - oauth1.idTokenHint(idTokenHint).openLogout(); + // Logout user + ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost").logout(); // Code should be successfully exchanged for the token at max once. In some cases (EG. Cross-DC) it may not be even successfully exchanged - assertThat(codeToTokenSuccessCount.get(), Matchers.lessThanOrEqualTo(0)); - assertThat(codeToTokenErrorsCount.get(), Matchers.greaterThanOrEqualTo(DEFAULT_THREADS)); + assertThat(codeToTokenSuccessCount.get(), Matchers.lessThanOrEqualTo(1)); + assertThat(codeToTokenErrorsCount.get(), Matchers.greaterThanOrEqualTo(DEFAULT_THREADS - 1)); log.infof("Iteration %d passed successfully", i); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/SingleUseProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/SingleUseProviderTest.java new file mode 100644 index 000000000000..bd956a317dd4 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/SingleUseProviderTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed 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 org.keycloak.testsuite.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.SingleUseObjectProvider; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; +import org.keycloak.testsuite.arquillian.annotation.ModelTest; + +/** + * @author Marek Posolda + */ +public class SingleUseProviderTest extends AbstractTestRealmKeycloakTest { + + private static final int ITEMS_COUNT = 100; + private static final int THREADS_COUNT = 20; + @Override + public void configureTestRealm(RealmRepresentation testRealm) { + + } + + @Test + @ModelTest + public void testConcurrentRemoveFromSingleUseCacheShouldFail(KeycloakSession session) throws Exception { + Map tracker = new ConcurrentHashMap<>(); + + // Add some items to singleUse cache + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (session1) -> { + for (int i = 0; i < ITEMS_COUNT; i++) { + Map mapp = Collections.singletonMap("my-key-" + i, "my-value-" + i); + SingleUseObjectProvider singleUseProvider = session1.getProvider(SingleUseObjectProvider.class); + singleUseProvider.put("my-key-" + i, 1000, mapp); + tracker.put(i, new Tracker()); + } + }); + + // Try to remove all items + Runnable runnable = () -> { + + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (session1) -> { + // Each thread trying to remove all items + for (int i = 0; i < ITEMS_COUNT ; i++) { + SingleUseObjectProvider singleUseProvider1 = session1.getProvider(SingleUseObjectProvider.class); + Map data = singleUseProvider1.remove("my-key-" + i); + if (data != null) { + tracker.get(i).countSuccess.incrementAndGet(); + } else { + tracker.get(i).countFailures.incrementAndGet(); + } + } + }); + }; + + // Try to remove all items concurrently + List workers = new ArrayList<>(); + for (int j=0 ; j< THREADS_COUNT ; j++) { + Thread t = new Thread(runnable); + workers.add(t); + t.start(); + } + + for (Thread t : workers) { + t.join(); + } + + // Check countSuccess and countFailures. For each key, only single successful "remove" is allowed. Other threads should fail to remove the item and nothing should be found + for (Map.Entry entry : tracker.entrySet()) { + getLogger().info(entry.getKey() + ": " + entry.getValue()); + } + + for (Map.Entry entry : tracker.entrySet()) { + Assert.assertEquals(1, entry.getValue().countSuccess.get()); + Assert.assertEquals(THREADS_COUNT - 1, entry.getValue().countFailures.get()); + } + } + + private class Tracker { + AtomicInteger countSuccess = new AtomicInteger(0); + AtomicInteger countFailures = new AtomicInteger(0); + + @Override + public String toString() { + return "success: " + countSuccess.get() + ", failures: " + countFailures.get(); + } + + } +}