keycloak-uncached
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java 18(+6 -12)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java 2(+1 -1)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java 8(+1 -7)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java 4(+2 -2)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/KeyResource.java 36(+36 -0)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java 3(+3 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java 50(+0 -50)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java 1(+0 -1)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/ExtractRealmKeysFromRealmTable.java 88(+88 -0)
model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java 4(+3 -1)
model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update2_3_0.java 80(+80 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java 114(+5 -109)
services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java 2(+0 -2)
services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java 2(+1 -1)
services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java 10(+7 -3)
services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java 6(+4 -2)
services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java 7(+4 -3)
services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java 2(+1 -1)
services/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java 2(+1 -1)
services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java 7(+4 -3)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java 2(+1 -1)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java 24(+15 -9)
services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java 1(+1 -0)
services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java 9(+4 -5)
services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java 6(+1 -5)
services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java 1(+1 -0)
services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java 5(+1 -4)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithSignatureTest.java 39(+35 -4)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestImplProviderFactory.java 107(+107 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestProvider.java 44(+44 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestProviderFactory.java 26(+26 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestSpi.java 47(+47 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java 9(+5 -4)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java 42(+23 -19)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.provider.Spi 1(+1 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.testsuite.components.TestProviderFactory 18(+18 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java 6(+5 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java 13(+7 -6)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java 12(+7 -5)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java 19(+17 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java 26(+12 -14)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java 220(+220 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/AbstractGroupTest.java 3(+2 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java 156(+0 -156)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java 106(+41 -65)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java 1(+0 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/KeyRotationTest.java 236(+236 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java 225(+225 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java 18(+1 -17)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java 97(+57 -40)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java 32(+0 -32)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java 2(+1 -1)
Details
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
index 25c899e..9a291c0 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
@@ -214,7 +214,7 @@ public class PreAuthActionsHandler {
try {
JWSInput input = new JWSInput(token);
- PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input, deployment);
+ PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input.getHeader().getKeyId(), deployment);
if (RSAProvider.verify(input, publicKey)) {
return input;
}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java
index 236844e..2faa84a 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java
@@ -38,12 +38,12 @@ public class AdapterRSATokenVerifier {
}
- public static PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) throws VerificationException {
+ public static PublicKey getPublicKey(String kid, KeycloakDeployment deployment) throws VerificationException {
PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
- PublicKey publicKey = pkLocator.getPublicKey(input, deployment);
+ PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
if (publicKey == null) {
- log.errorf("Didn't find publicKey for kid: %s", input.getHeader().getKeyId());
+ log.errorf("Didn't find publicKey for kid: %s", kid);
throw new VerificationException("Didn't find publicKey for specified kid");
}
@@ -51,14 +51,8 @@ public class AdapterRSATokenVerifier {
}
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException {
- JWSInput input;
- try {
- input = new JWSInput(tokenString);
- } catch (Exception e) {
- throw new VerificationException("Couldn't parse token", e);
- }
-
- PublicKey publicKey = getPublicKey(input, deployment);
- return RSATokenVerifier.verifyToken(input, publicKey, deployment.getRealmInfoUrl(), checkActive, checkTokenType);
+ RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(deployment.getRealmInfoUrl()).checkActive(checkActive).checkTokenType(checkTokenType);
+ PublicKey publicKey = getPublicKey(verifier.getHeader().getKeyId(), deployment);
+ return verifier.publicKey(publicKey).verify().getToken();
}
}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java
index 469dd26..2aa51a4 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java
@@ -34,7 +34,7 @@ public class HardcodedPublicKeyLocator implements PublicKeyLocator {
}
@Override
- public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) {
+ public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
return publicKey;
}
}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java
index 502d0c0..9305f32 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java
@@ -46,13 +46,7 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
private volatile int lastRequestTime = 0;
@Override
- public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) {
- String kid = input.getHeader().getKeyId();
- return getPublicKey(kid, deployment);
- }
-
-
- private PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
+ public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
// Check if key is in cache.
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java
index 62cef81..3efd90a 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java
@@ -28,10 +28,10 @@ import java.security.PublicKey;
public interface PublicKeyLocator {
/**
- * @param input
+ * @param kid
* @param deployment
* @return publicKey, which should be used for verify signature on given "input"
*/
- PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment);
+ PublicKey getPublicKey(String kid, KeycloakDeployment deployment);
}
diff --git a/common/src/main/java/org/keycloak/common/util/CertificateUtils.java b/common/src/main/java/org/keycloak/common/util/CertificateUtils.java
index 9d3e76f..7fc440e 100755
--- a/common/src/main/java/org/keycloak/common/util/CertificateUtils.java
+++ b/common/src/main/java/org/keycloak/common/util/CertificateUtils.java
@@ -140,11 +140,13 @@ public class CertificateUtils {
*
* @throws Exception the exception
*/
- public static X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) throws Exception {
+ public static X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) {
+ return generateV1SelfSignedCertificate(caKeyPair, subject, BigInteger.valueOf(System.currentTimeMillis()));
+ }
+ public static X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject, BigInteger serialNumber) {
try {
X500Name subjectDN = new X500Name("CN=" + subject);
- BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
Date validityStartDate = new Date(System.currentTimeMillis() - 100000);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.YEAR, 10);
diff --git a/common/src/main/java/org/keycloak/common/util/KeyUtils.java b/common/src/main/java/org/keycloak/common/util/KeyUtils.java
new file mode 100644
index 0000000..a49b9ab
--- /dev/null
+++ b/common/src/main/java/org/keycloak/common/util/KeyUtils.java
@@ -0,0 +1,76 @@
+/*
+ * 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.common.util;
+
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.spec.RSAPublicKeySpec;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class KeyUtils {
+
+ private static final String DEFAULT_MESSAGE_DIGEST = "SHA-256";
+
+ private KeyUtils() {
+ }
+
+ public static KeyPair generateRsaKeyPair(int keysize) {
+ try {
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+ generator.initialize(keysize);
+ KeyPair keyPair = generator.generateKeyPair();
+ return keyPair;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static PublicKey extractPublicKey(PrivateKey key) {
+ if (key == null) {
+ return null;
+ }
+
+ try {
+ RSAPrivateCrtKey rsaPrivateCrtKey = (RSAPrivateCrtKey) key;
+ RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(rsaPrivateCrtKey.getModulus(), rsaPrivateCrtKey.getPublicExponent());
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ return keyFactory.generatePublic(publicKeySpec);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String createKeyId(Key key) {
+ try {
+ return Base64Url.encode(MessageDigest.getInstance(DEFAULT_MESSAGE_DIGEST).digest(key.getEncoded()));
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+}
diff --git a/common/src/main/java/org/keycloak/common/util/PemUtils.java b/common/src/main/java/org/keycloak/common/util/PemUtils.java
index 4bff8c9..fc4e193 100755
--- a/common/src/main/java/org/keycloak/common/util/PemUtils.java
+++ b/common/src/main/java/org/keycloak/common/util/PemUtils.java
@@ -18,12 +18,15 @@
package org.keycloak.common.util;
+import org.bouncycastle.openssl.PEMWriter;
+
import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
import java.io.IOException;
-import java.io.InputStream;
+import java.io.StringWriter;
+import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
+import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
/**
@@ -33,6 +36,7 @@ import java.security.cert.X509Certificate;
* @version $Revision: 1 $
*/
public final class PemUtils {
+
static {
BouncyIntegration.init();
}
@@ -40,73 +44,112 @@ public final class PemUtils {
private PemUtils() {
}
- public static X509Certificate decodeCertificate(InputStream is) throws Exception {
- byte[] der = pemToDer(is);
- ByteArrayInputStream bis = new ByteArrayInputStream(der);
- return DerUtils.decodeCertificate(bis);
- }
-
- public static X509Certificate decodeCertificate(String cert) throws Exception {
- byte[] der = pemToDer(cert);
- ByteArrayInputStream bis = new ByteArrayInputStream(der);
- return DerUtils.decodeCertificate(bis);
+ /**
+ * Decode a X509 Certificate from a PEM string
+ *
+ * @param cert
+ * @return
+ * @throws Exception
+ */
+ public static X509Certificate decodeCertificate(String cert) {
+ if (cert == null) {
+ return null;
+ }
+
+ try {
+ byte[] der = pemToDer(cert);
+ ByteArrayInputStream bis = new ByteArrayInputStream(der);
+ return DerUtils.decodeCertificate(bis);
+ } catch (Exception e) {
+ throw new PemException(e);
+ }
}
-
/**
- * Extract a public key from a PEM string
+ * Decode a Public Key from a PEM string
*
* @param pem
* @return
* @throws Exception
*/
- public static PublicKey decodePublicKey(String pem) throws Exception {
- byte[] der = pemToDer(pem);
- return DerUtils.decodePublicKey(der);
+ public static PublicKey decodePublicKey(String pem) {
+ if (pem == null) {
+ return null;
+ }
+
+ try {
+ byte[] der = pemToDer(pem);
+ return DerUtils.decodePublicKey(der);
+ } catch (Exception e) {
+ throw new PemException(e);
+ }
}
/**
- * Extract a private key that is a PKCS8 pem string (base64 encoded PKCS8)
+ * Decode a Private Key from a PEM string
*
* @param pem
* @return
* @throws Exception
*/
- public static PrivateKey decodePrivateKey(String pem) throws Exception {
- byte[] der = pemToDer(pem);
- return DerUtils.decodePrivateKey(der);
- }
-
- public static PrivateKey decodePrivateKey(InputStream is) throws Exception {
- String pem = pemFromStream(is);
- return decodePrivateKey(pem);
+ public static PrivateKey decodePrivateKey(String pem) {
+ if (pem == null) {
+ return null;
+ }
+
+ try {
+ byte[] der = pemToDer(pem);
+ return DerUtils.decodePrivateKey(der);
+ } catch (Exception e) {
+ throw new PemException(e);
+ }
}
/**
- * Decode a PEM file to DER format
+ * Encode a Key to a PEM string
*
- * @param is
+ * @param key
* @return
- * @throws java.io.IOException
+ * @throws Exception
*/
- public static byte[] pemToDer(InputStream is) throws IOException {
- String pem = pemFromStream(is);
- return pemToDer(pem);
+ public static String encodeKey(Key key) {
+ return encode(key);
}
/**
- * Decode a PEM string to DER format
+ * Encode a X509 Certificate to a PEM string
*
- * @param pem
+ * @param certificate
* @return
- * @throws java.io.IOException
*/
- public static byte[] pemToDer(String pem) throws IOException {
+ public static String encodeCertificate(Certificate certificate) {
+ return encode(certificate);
+ }
+
+ private static String encode(Object obj) {
+ if (obj == null) {
+ return null;
+ }
+
+ try {
+ StringWriter writer = new StringWriter();
+ PEMWriter pemWriter = new PEMWriter(writer);
+ pemWriter.writeObject(obj);
+ pemWriter.flush();
+ pemWriter.close();
+ String s = writer.toString();
+ return PemUtils.removeBeginEnd(s);
+ } catch (Exception e) {
+ throw new PemException(e);
+ }
+ }
+
+ private static byte[] pemToDer(String pem) throws IOException {
pem = removeBeginEnd(pem);
return Base64.decode(pem);
}
- public static String removeBeginEnd(String pem) {
+ private static String removeBeginEnd(String pem) {
pem = pem.replaceAll("-----BEGIN (.*)-----", "");
pem = pem.replaceAll("-----END (.*)----", "");
pem = pem.replaceAll("\r\n", "");
@@ -114,12 +157,4 @@ public final class PemUtils {
return pem.trim();
}
-
- public static String pemFromStream(InputStream is) throws IOException {
- DataInputStream dis = new DataInputStream(is);
- byte[] keyBytes = new byte[dis.available()];
- dis.readFully(keyBytes);
- dis.close();
- return new String(keyBytes, "utf-8");
- }
}
diff --git a/core/src/main/java/org/keycloak/jose/jwk/JWKBuilder.java b/core/src/main/java/org/keycloak/jose/jwk/JWKBuilder.java
index ae37ebf..2333ba9 100644
--- a/core/src/main/java/org/keycloak/jose/jwk/JWKBuilder.java
+++ b/core/src/main/java/org/keycloak/jose/jwk/JWKBuilder.java
@@ -18,6 +18,7 @@
package org.keycloak.jose.jwk;
import org.keycloak.common.util.Base64Url;
+import org.keycloak.common.util.KeyUtils;
import java.math.BigInteger;
import java.security.Key;
@@ -53,7 +54,7 @@ public class JWKBuilder {
RSAPublicJWK k = new RSAPublicJWK();
- String kid = this.kid != null ? this.kid : createKeyId(key);
+ String kid = this.kid != null ? this.kid : KeyUtils.createKeyId(key);
k.setKeyId(kid);
k.setKeyType(RSAPublicJWK.RSA);
k.setAlgorithm(RSAPublicJWK.RS256);
@@ -64,14 +65,6 @@ public class JWKBuilder {
return k;
}
- public static String createKeyId(Key key) {
- try {
- return Base64Url.encode(MessageDigest.getInstance(DEFAULT_MESSAGE_DIGEST).digest(key.getEncoded()));
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- }
-
/**
* Copied from org.apache.commons.codec.binary.Base64
*/
diff --git a/core/src/main/java/org/keycloak/representations/idm/ComponentRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ComponentRepresentation.java
index 2fac965..ec1f378 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ComponentRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ComponentRepresentation.java
@@ -24,6 +24,8 @@ import org.keycloak.common.util.MultivaluedHashMap;
*/
public class ComponentRepresentation {
+ public static final String SECRET_VALUE = "**********";
+
private String id;
private String name;
private String providerId;
diff --git a/core/src/main/java/org/keycloak/representations/idm/ConfigPropertyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ConfigPropertyRepresentation.java
index 0baa66a..6558c41 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ConfigPropertyRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ConfigPropertyRepresentation.java
@@ -27,6 +27,7 @@ public class ConfigPropertyRepresentation {
protected String helpText;
protected String type;
protected Object defaultValue;
+ protected boolean secret;
public String getName() {
return name;
@@ -67,4 +68,12 @@ public class ConfigPropertyRepresentation {
public void setHelpText(String helpText) {
this.helpText = helpText;
}
+
+ public boolean isSecret() {
+ return secret;
+ }
+
+ public void setSecret(boolean secret) {
+ this.secret = secret;
+ }
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/KeysMetadataRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/KeysMetadataRepresentation.java
new file mode 100644
index 0000000..54d3ba9
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/KeysMetadataRepresentation.java
@@ -0,0 +1,117 @@
+/*
+ * 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.representations.idm;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class KeysMetadataRepresentation {
+
+ private Map<String, String> active;
+
+ private List<KeyMetadataRepresentation> keys;
+
+ public Map<String, String> getActive() {
+ return active;
+ }
+
+ public void setActive(Map<String, String> active) {
+ this.active = active;
+ }
+
+ public List<KeyMetadataRepresentation> getKeys() {
+ return keys;
+ }
+
+ public void setKeys(List<KeyMetadataRepresentation> keys) {
+ this.keys = keys;
+ }
+
+ public static class KeyMetadataRepresentation {
+ private String providerId;
+ private long providerPriority;
+
+ private String kid;
+
+ private String status;
+
+ private String type;
+
+ private String publicKey;
+ private String certificate;
+
+ public String getProviderId() {
+ return providerId;
+ }
+
+ public void setProviderId(String providerId) {
+ this.providerId = providerId;
+ }
+
+ public long getProviderPriority() {
+ return providerPriority;
+ }
+
+ public void setProviderPriority(long providerPriority) {
+ this.providerPriority = providerPriority;
+ }
+
+ public String getKid() {
+ return kid;
+ }
+
+ public void setKid(String kid) {
+ this.kid = kid;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getPublicKey() {
+ return publicKey;
+ }
+
+ public void setPublicKey(String publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ public String getCertificate() {
+ return certificate;
+ }
+
+ public void setCertificate(String certificate) {
+ this.certificate = certificate;
+ }
+ }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/PublishedRealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/PublishedRealmRepresentation.java
index ceb2fe9..eceb759 100755
--- a/core/src/main/java/org/keycloak/representations/idm/PublishedRealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/PublishedRealmRepresentation.java
@@ -19,11 +19,8 @@ package org.keycloak.representations.idm;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
-import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.common.util.PemUtils;
-import java.io.IOException;
-import java.io.StringWriter;
import java.security.PublicKey;
/**
@@ -85,17 +82,7 @@ public class PublishedRealmRepresentation {
@JsonIgnore
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
- StringWriter writer = new StringWriter();
- PEMWriter pemWriter = new PEMWriter(writer);
- try {
- pemWriter.writeObject(publicKey);
- pemWriter.flush();
- pemWriter.close();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- String s = writer.toString();
- this.publicKeyPem = PemUtils.removeBeginEnd(s);
+ this.publicKeyPem = PemUtils.encodeKey(publicKey);
}
public String getTokenServiceUrl() {
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 776aa0e..bcbb02d 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -72,9 +72,13 @@ public class RealmRepresentation {
protected Integer failureFactor;
//--- end brute force settings
+ @Deprecated
protected String privateKey;
+ @Deprecated
protected String publicKey;
+ @Deprecated
protected String certificate;
+ @Deprecated
protected String codeSecret;
protected RolesRepresentation roles;
protected List<GroupRepresentation> groups;
diff --git a/core/src/main/java/org/keycloak/RSATokenVerifier.java b/core/src/main/java/org/keycloak/RSATokenVerifier.java
index 562261d..1cbc2e2 100755
--- a/core/src/main/java/org/keycloak/RSATokenVerifier.java
+++ b/core/src/main/java/org/keycloak/RSATokenVerifier.java
@@ -18,6 +18,7 @@
package org.keycloak;
import org.keycloak.common.VerificationException;
+import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
@@ -31,84 +32,123 @@ import java.security.PublicKey;
* @version $Revision: 1 $
*/
public class RSATokenVerifier {
- public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl) throws VerificationException {
- return verifyToken(tokenString, realmKey, realmUrl, true, true);
+
+ private final String tokenString;
+ private PublicKey publicKey;
+ private String realmUrl;
+ private boolean checkTokenType = true;
+ private boolean checkActive = true;
+ private boolean checkRealmUrl = true;
+
+ private JWSInput jws;
+ private AccessToken token;
+
+ private RSATokenVerifier(String tokenString) {
+ this.tokenString = tokenString;
}
- public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
- AccessToken token = toAccessToken(tokenString, realmKey);
+ public static RSATokenVerifier create(String tokenString) {
+ return new RSATokenVerifier(tokenString);
+ }
- tokenVerifications(token, realmUrl, checkActive, checkTokenType);
+ public static AccessToken verifyToken(String tokenString, PublicKey publicKey, String realmUrl) throws VerificationException {
+ return RSATokenVerifier.create(tokenString).publicKey(publicKey).realmUrl(realmUrl).verify().getToken();
+ }
- return token;
+ public static AccessToken verifyToken(String tokenString, PublicKey publicKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
+ return RSATokenVerifier.create(tokenString).publicKey(publicKey).realmUrl(realmUrl).checkActive(checkActive).checkTokenType(checkTokenType).verify().getToken();
}
- private static void tokenVerifications(AccessToken token, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
- String user = token.getSubject();
- if (user == null) {
- throw new VerificationException("Token user was null.");
- }
- if (realmUrl == null) {
- throw new VerificationException("Realm URL is null. Make sure to add auth-server-url to the configuration of your adapter!");
- }
- if (!realmUrl.equals(token.getIssuer())) {
- throw new VerificationException("Token audience doesn't match domain. Token issuer is " + token.getIssuer() + ", but URL from configuration is " + realmUrl);
+ public RSATokenVerifier publicKey(PublicKey publicKey) {
+ this.publicKey = publicKey;
+ return this;
+ }
- }
+ public RSATokenVerifier realmUrl(String realmUrl) {
+ this.realmUrl = realmUrl;
+ return this;
+ }
- if (checkTokenType) {
- String type = token.getType();
- if (type == null || !type.equalsIgnoreCase(TokenUtil.TOKEN_TYPE_BEARER)) {
- throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + type + "'");
- }
- }
- if (checkActive && !token.isActive()) {
- throw new VerificationException("Token is not active.");
- }
+ public RSATokenVerifier checkTokenType(boolean checkTokenType) {
+ this.checkTokenType = checkTokenType;
+ return this;
+ }
+ public RSATokenVerifier checkActive(boolean checkActive) {
+ this.checkActive = checkActive;
+ return this;
}
+ public RSATokenVerifier checkRealmUrl(boolean checkRealmUrl) {
+ this.checkRealmUrl = checkRealmUrl;
+ return this;
+ }
- public static AccessToken toAccessToken(String tokenString, PublicKey realmKey) throws VerificationException {
- JWSInput input;
- try {
- input = new JWSInput(tokenString);
- } catch (JWSInputException e) {
- throw new VerificationException("Couldn't parse token", e);
- }
- if (!isPublicKeyValid(input, realmKey)) throw new VerificationException("Invalid token signature.");
+ public RSATokenVerifier parse() throws VerificationException {
+ if (jws == null) {
+ if (tokenString == null) {
+ throw new VerificationException("Token not set");
+ }
- AccessToken token;
- try {
- token = input.readJsonContent(AccessToken.class);
- } catch (JWSInputException e) {
- throw new VerificationException("Couldn't parse token signature", e);
+ try {
+ jws = new JWSInput(tokenString);
+ } catch (JWSInputException e) {
+ throw new VerificationException("Failed to parse JWT", e);
+ }
+
+
+ try {
+ token = jws.readJsonContent(AccessToken.class);
+ } catch (JWSInputException e) {
+ throw new VerificationException("Failed to read access token from JWT", e);
+ }
}
+ return this;
+ }
+
+ public AccessToken getToken() throws VerificationException {
+ parse();
return token;
}
+ public JWSHeader getHeader() throws VerificationException {
+ parse();
+ return jws.getHeader();
+ }
- public static AccessToken verifyToken(JWSInput input, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
- if (!isPublicKeyValid(input, realmKey)) throw new VerificationException("Invalid token signature.");
+ public RSATokenVerifier verify() throws VerificationException {
+ parse();
- AccessToken token;
- try {
- token = input.readJsonContent(AccessToken.class);
- } catch (JWSInputException e) {
- throw new VerificationException("Couldn't parse token signature", e);
+ if (publicKey == null) {
+ throw new VerificationException("Public key not set");
}
- tokenVerifications(token, realmUrl, checkActive, checkTokenType);
+ if (checkRealmUrl && realmUrl == null) {
+ throw new VerificationException("Realm URL not set");
+ }
- return token;
- }
+ if (!RSAProvider.verify(jws, publicKey)) {
+ throw new VerificationException("Invalid token signature");
+ }
+ String user = token.getSubject();
+ if (user == null) {
+ throw new VerificationException("Subject missing in token");
+ }
+
+ if (checkRealmUrl && !realmUrl.equals(token.getIssuer())) {
+ throw new VerificationException("Invalid token issuer. Expected '" + realmUrl + "', but was '" + token.getIssuer() + "'");
+ }
- private static boolean isPublicKeyValid(JWSInput input, PublicKey realmKey) throws VerificationException {
- try {
- return RSAProvider.verify(input, realmKey);
- } catch (Exception e) {
- throw new VerificationException("Token signature not validated.", e);
+ if (checkTokenType && !TokenUtil.TOKEN_TYPE_BEARER.equalsIgnoreCase(token.getType())) {
+ throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + token.getType() + "'");
}
+
+ if (checkActive && !token.isActive()) {
+ throw new VerificationException("Token is not active");
+ }
+
+ return this;
}
+
}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/KeyResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/KeyResource.java
new file mode 100644
index 0000000..aeb1fdb
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/KeyResource.java
@@ -0,0 +1,36 @@
+/*
+ * 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.admin.client.resource;
+
+import org.keycloak.representations.idm.KeysMetadataRepresentation;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface KeyResource {
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ KeysMetadataRepresentation getKeyMetadata();
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
index 252157a..4ce3a41 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -207,4 +207,7 @@ public interface RealmResource {
@Path("components")
ComponentsResource components();
+ @Path("keys")
+ KeyResource keys();
+
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
index 8ca5a6d..99b718c 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
@@ -89,15 +89,6 @@ public class CachedRealm extends AbstractRevisioned {
protected PasswordPolicy passwordPolicy;
protected OTPPolicy otpPolicy;
- protected transient String keyId;
- protected transient PublicKey publicKey;
- protected String publicKeyPem;
- protected transient PrivateKey privateKey;
- protected String privateKeyPem;
- protected transient X509Certificate certificate;
- protected String certificatePem;
- protected String codeSecret;
-
protected String loginTheme;
protected String accountTheme;
protected String adminTheme;
@@ -191,15 +182,6 @@ public class CachedRealm extends AbstractRevisioned {
passwordPolicy = model.getPasswordPolicy();
otpPolicy = model.getOTPPolicy();
- keyId = model.getKeyId();
- publicKeyPem = model.getPublicKeyPem();
- publicKey = model.getPublicKey();
- privateKeyPem = model.getPrivateKeyPem();
- privateKey = model.getPrivateKey();
- certificatePem = model.getCertificatePem();
- certificate = model.getCertificate();
- codeSecret = model.getCodeSecret();
-
loginTheme = model.getLoginTheme();
accountTheme = model.getAccountTheme();
adminTheme = model.getAdminTheme();
@@ -415,22 +397,6 @@ public class CachedRealm extends AbstractRevisioned {
return accessCodeLifespanLogin;
}
- public String getKeyId() {
- return keyId;
- }
-
- public String getPublicKeyPem() {
- return publicKeyPem;
- }
-
- public String getPrivateKeyPem() {
- return privateKeyPem;
- }
-
- public String getCodeSecret() {
- return codeSecret;
- }
-
public List<RequiredCredentialModel> getRequiredCredentials() {
return requiredCredentials;
}
@@ -507,10 +473,6 @@ public class CachedRealm extends AbstractRevisioned {
return userFederationMappers;
}
- public String getCertificatePem() {
- return certificatePem;
- }
-
public List<IdentityProviderModel> getIdentityProviders() {
return identityProviders;
}
@@ -591,18 +553,6 @@ public class CachedRealm extends AbstractRevisioned {
return clientTemplates;
}
- public PublicKey getPublicKey() {
- return publicKey;
- }
-
- public PrivateKey getPrivateKey() {
- return privateKey;
- }
-
- public X509Certificate getCertificate() {
- return certificate;
- }
-
public Set<UserFederationMapperModel> getUserFederationMapperSet() {
return userFederationMapperSet;
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index af3c7ad..4802a6b 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -60,10 +60,6 @@ public class RealmAdapter implements RealmModel {
protected RealmCacheSession cacheSession;
protected RealmModel updated;
protected RealmCache cache;
- protected volatile transient PublicKey publicKey;
- protected volatile transient PrivateKey privateKey;
- protected volatile transient Key codeSecretKey;
- protected volatile transient X509Certificate certificate;
public RealmAdapter(CachedRealm cached, RealmCacheSession cacheSession) {
this.cached = cached;
@@ -424,123 +420,6 @@ public class RealmAdapter implements RealmModel {
}
@Override
- public String getKeyId() {
- if (isUpdated()) return updated.getKeyId();
- return cached.getKeyId();
- }
-
- @Override
- public String getPublicKeyPem() {
- if (isUpdated()) return updated.getPublicKeyPem();
- return cached.getPublicKeyPem();
- }
-
- @Override
- public void setPublicKeyPem(String publicKeyPem) {
- getDelegateForUpdate();
- updated.setPublicKeyPem(publicKeyPem);
- }
-
- @Override
- public String getPrivateKeyPem() {
- if (isUpdated()) return updated.getPrivateKeyPem();
- return cached.getPrivateKeyPem();
- }
-
- @Override
- public void setPrivateKeyPem(String privateKeyPem) {
- getDelegateForUpdate();
- updated.setPrivateKeyPem(privateKeyPem);
- }
-
- @Override
- public PublicKey getPublicKey() {
- if (isUpdated()) return updated.getPublicKey();
- if (publicKey != null) return publicKey;
- publicKey = cached.getPublicKey();
- if (publicKey != null) return publicKey;
- publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem());
- return publicKey;
- }
-
- @Override
- public void setPublicKey(PublicKey publicKey) {
- this.publicKey = publicKey;
- String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
- setPublicKeyPem(publicKeyPem);
- }
-
- @Override
- public X509Certificate getCertificate() {
- if (isUpdated()) return updated.getCertificate();
- if (certificate != null) return certificate;
- certificate = cached.getCertificate();
- if (certificate != null) return certificate;
- certificate = KeycloakModelUtils.getCertificate(getCertificatePem());
- return certificate;
- }
-
- @Override
- public void setCertificate(X509Certificate certificate) {
- this.certificate = certificate;
- String certPem = KeycloakModelUtils.getPemFromCertificate(certificate);
- setCertificatePem(certPem);
- }
-
- @Override
- public String getCertificatePem() {
- if (isUpdated()) return updated.getCertificatePem();
- return cached.getCertificatePem();
- }
-
- @Override
- public void setCertificatePem(String certificate) {
- getDelegateForUpdate();
- updated.setCertificatePem(certificate);
-
- }
-
- @Override
- public PrivateKey getPrivateKey() {
- if (isUpdated()) return updated.getPrivateKey();
- if (privateKey != null) {
- return privateKey;
- }
- privateKey = cached.getPrivateKey();
- if (privateKey != null) {
- return privateKey;
- }
- privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem());
- return privateKey;
- }
-
- @Override
- public void setPrivateKey(PrivateKey privateKey) {
- this.privateKey = privateKey;
- String privateKeyPem = KeycloakModelUtils.getPemFromKey(privateKey);
- setPrivateKeyPem(privateKeyPem);
- }
-
- @Override
- public String getCodeSecret() {
- return isUpdated() ? updated.getCodeSecret() : cached.getCodeSecret();
- }
-
- @Override
- public Key getCodeSecretKey() {
- if (codeSecretKey == null) {
- codeSecretKey = KeycloakModelUtils.getSecretKey(getCodeSecret());
- }
- return codeSecretKey;
- }
-
- @Override
- public void setCodeSecret(String codeSecret) {
- getDelegateForUpdate();
- updated.setCodeSecret(codeSecret);
- }
-
- @Override
public List<RequiredCredentialModel> getRequiredCredentials() {
if (isUpdated()) return updated.getRequiredCredentials();
return cached.getRequiredCredentials();
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 73027b8..f6d1640 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -106,7 +106,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
tx.put(sessionCache, id, entity);
ClientSessionAdapter wrap = wrap(realm, entity, false);
- wrap.setNote(ClientSessionModel.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
return wrap;
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/ExtractRealmKeysFromRealmTable.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/ExtractRealmKeysFromRealmTable.java
new file mode 100644
index 0000000..d3def65
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/ExtractRealmKeysFromRealmTable.java
@@ -0,0 +1,88 @@
+/*
+ * 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.connections.jpa.updater.liquibase.custom;
+
+import liquibase.exception.CustomChangeException;
+import liquibase.statement.core.InsertStatement;
+import liquibase.structure.core.Table;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ExtractRealmKeysFromRealmTable extends CustomKeycloakTask {
+
+ @Override
+ protected void generateStatementsImpl() throws CustomChangeException {
+ try {
+ PreparedStatement statement = jdbcConnection.prepareStatement("select ID, PRIVATE_KEY, CERTIFICATE from " + getTableName("REALM"));
+
+ try {
+ ResultSet resultSet = statement.executeQuery();
+ try {
+ while (resultSet.next()) {
+ String realmId = resultSet.getString(1);
+ String privateKeyPem = resultSet.getString(2);
+ String certificatePem = resultSet.getString(3);
+
+ String componentId = KeycloakModelUtils.generateId();
+
+ InsertStatement insertComponent = new InsertStatement(null, null, database.correctObjectName("COMPONENT", Table.class))
+ .addColumnValue("ID", componentId)
+ .addColumnValue("REALM_ID", realmId)
+ .addColumnValue("PARENT_ID", realmId)
+ .addColumnValue("NAME", "rsa")
+ .addColumnValue("PROVIDER_ID", "rsa")
+ .addColumnValue("PROVIDER_TYPE", KeyProvider.class.getName());
+
+ statements.add(insertComponent);
+
+ statements.add(componentConfigStatement(componentId, "priority", "100"));
+ statements.add(componentConfigStatement(componentId, "privateKey", privateKeyPem));
+ statements.add(componentConfigStatement(componentId, "certificate", certificatePem));
+ }
+ } finally {
+ resultSet.close();
+ }
+ } finally {
+ statement.close();
+ }
+
+ confirmationMessage.append("Updated " + statements.size() + " records in USER_FEDERATION_PROVIDER table");
+ } catch (Exception e) {
+ throw new CustomChangeException(getTaskId() + ": Exception when updating data from previous version", e);
+ }
+ }
+
+ private InsertStatement componentConfigStatement(String componentId, String name, String value) {
+ return new InsertStatement(null, null, database.correctObjectName("COMPONENT_CONFIG", Table.class))
+ .addColumnValue("ID", KeycloakModelUtils.generateId())
+ .addColumnValue("COMPONENT_ID", componentId)
+ .addColumnValue("NAME", name)
+ .addColumnValue("VALUE", value);
+ }
+
+ @Override
+ protected String getTaskId() {
+ return "Update 2.3.0.Final";
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index e45d819..eef1d91 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -117,15 +117,6 @@ public class RealmEntity {
@Column(name="NOT_BEFORE")
protected int notBefore;
- @Column(name="PUBLIC_KEY", length = 4000)
- protected String publicKeyPem;
- @Column(name="PRIVATE_KEY", length = 4000)
- protected String privateKeyPem;
- @Column(name="CERTIFICATE", length = 4000)
- protected String certificatePem;
- @Column(name="CODE_SECRET", length = 255)
- protected String codeSecret;
-
@Column(name="LOGIN_THEME")
protected String loginTheme;
@Column(name="ACCOUNT_THEME")
@@ -384,30 +375,6 @@ public class RealmEntity {
this.accessCodeLifespanLogin = accessCodeLifespanLogin;
}
- public String getPublicKeyPem() {
- return publicKeyPem;
- }
-
- public void setPublicKeyPem(String publicKeyPem) {
- this.publicKeyPem = publicKeyPem;
- }
-
- public String getPrivateKeyPem() {
- return privateKeyPem;
- }
-
- public void setPrivateKeyPem(String privateKeyPem) {
- this.privateKeyPem = privateKeyPem;
- }
-
- public String getCodeSecret() {
- return codeSecret;
- }
-
- public void setCodeSecret(String codeSecret) {
- this.codeSecret = codeSecret;
- }
-
public Collection<RequiredCredentialEntity> getRequiredCredentials() {
return requiredCredentials;
}
@@ -567,14 +534,6 @@ public class RealmEntity {
this.attributes = attributes;
}
- public String getCertificatePem() {
- return certificatePem;
- }
-
- public void setCertificatePem(String certificatePem) {
- this.certificatePem = certificatePem;
- }
-
public List<IdentityProviderEntity> getIdentityProviders() {
return this.identityProviders;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 21aa8d0..2a9bd45 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -20,6 +20,7 @@ package org.keycloak.models.jpa;
import org.jboss.logging.Logger;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.models.AuthenticationExecutionModel;
@@ -60,6 +61,7 @@ import org.keycloak.models.jpa.entities.RequiredCredentialEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.jpa.entities.UserFederationMapperEntity;
import org.keycloak.models.jpa.entities.UserFederationProviderEntity;
+import org.keycloak.models.utils.ComponentUtil;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
@@ -88,10 +90,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
protected static final Logger logger = Logger.getLogger(RealmAdapter.class);
protected RealmEntity realm;
protected EntityManager em;
- protected volatile transient PublicKey publicKey;
- protected volatile transient PrivateKey privateKey;
- protected volatile transient X509Certificate certificate;
- protected volatile transient Key codeSecretKey;
protected KeycloakSession session;
private PasswordPolicy passwordPolicy;
private OTPPolicy otpPolicy;
@@ -488,106 +486,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
em.flush();
}
- @Override
- public String getKeyId() {
- PublicKey publicKey = getPublicKey();
- return publicKey != null ? JWKBuilder.create().rs256(publicKey).getKeyId() : null;
- }
-
- @Override
- public String getPublicKeyPem() {
- return realm.getPublicKeyPem();
- }
-
- @Override
- public void setPublicKeyPem(String publicKeyPem) {
- realm.setPublicKeyPem(publicKeyPem);
- em.flush();
- }
-
- @Override
- public X509Certificate getCertificate() {
- if (certificate != null) return certificate;
- certificate = KeycloakModelUtils.getCertificate(getCertificatePem());
- return certificate;
- }
-
- @Override
- public void setCertificate(X509Certificate certificate) {
- this.certificate = certificate;
- String certificatePem = KeycloakModelUtils.getPemFromCertificate(certificate);
- setCertificatePem(certificatePem);
-
- }
-
- @Override
- public String getCertificatePem() {
- return realm.getCertificatePem();
- }
-
- @Override
- public void setCertificatePem(String certificate) {
- realm.setCertificatePem(certificate);
-
- }
-
- @Override
- public String getPrivateKeyPem() {
- return realm.getPrivateKeyPem();
- }
-
- @Override
- public void setPrivateKeyPem(String privateKeyPem) {
- realm.setPrivateKeyPem(privateKeyPem);
- em.flush();
- }
-
- @Override
- public PublicKey getPublicKey() {
- if (publicKey != null) return publicKey;
- publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem());
- return publicKey;
- }
-
- @Override
- public void setPublicKey(PublicKey publicKey) {
- this.publicKey = publicKey;
- String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
- setPublicKeyPem(publicKeyPem);
- }
-
- @Override
- public PrivateKey getPrivateKey() {
- if (privateKey != null) return privateKey;
- privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem());
- return privateKey;
- }
-
- @Override
- public void setPrivateKey(PrivateKey privateKey) {
- this.privateKey = privateKey;
- String privateKeyPem = KeycloakModelUtils.getPemFromKey(privateKey);
- setPrivateKeyPem(privateKeyPem);
- }
-
- @Override
- public String getCodeSecret() {
- return realm.getCodeSecret();
- }
-
- @Override
- public Key getCodeSecretKey() {
- if (codeSecretKey == null) {
- codeSecretKey = KeycloakModelUtils.getSecretKey(getCodeSecret());
- }
- return codeSecretKey;
- }
-
- @Override
- public void setCodeSecret(String codeSecret) {
- realm.setCodeSecret(codeSecret);
- }
-
protected RequiredCredentialModel initRequiredCredentialModel(String type) {
RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
if (model == null) {
@@ -2138,6 +2036,13 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
@Override
public ComponentModel addComponentModel(ComponentModel model) {
+ ComponentFactory componentFactory = ComponentUtil.getComponentFactory(session, model);
+ if (componentFactory == null) {
+ throw new IllegalArgumentException("Invalid component type");
+ }
+
+ componentFactory.validateConfiguration(session, model);
+
ComponentEntity c = new ComponentEntity();
if (model.getId() == null) {
c.setId(KeycloakModelUtils.generateId());
@@ -2171,6 +2076,8 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
@Override
public void updateComponent(ComponentModel component) {
+ ComponentUtil.getComponentFactory(session, component).validateConfiguration(session, component);
+
ComponentEntity c = em.find(ComponentEntity.class, component.getId());
if (c == null) return;
c.setName(component.getName());
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml
index a73ee6b..b1f9887 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml
@@ -36,6 +36,13 @@
<addColumn tableName="IDENTITY_PROVIDER">
<column name="PROVIDER_DISPLAY_NAME" type="VARCHAR(255)"></column>
</addColumn>
+
+ <customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.ExtractRealmKeysFromRealmTable"/>
+ <dropColumn tableName="REALM" columnName="CODE_SECRET" />
+ <dropColumn tableName="REALM" columnName="PRIVATE_KEY" />
+ <dropColumn tableName="REALM" columnName="PUBLIC_KEY" />
+ <dropColumn tableName="REALM" columnName="CERTIFICATE" />
+
</changeSet>
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java
index 7b18863..3e429e2 100755
--- a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java
@@ -33,6 +33,7 @@ import org.keycloak.connections.mongo.updater.impl.updates.Update1_4_0;
import org.keycloak.connections.mongo.updater.impl.updates.Update1_7_0;
import org.keycloak.connections.mongo.updater.impl.updates.Update1_8_0;
import org.keycloak.connections.mongo.updater.impl.updates.Update1_9_2;
+import org.keycloak.connections.mongo.updater.impl.updates.Update2_3_0;
import org.keycloak.models.KeycloakSession;
import java.util.Date;
@@ -57,7 +58,8 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
Update1_4_0.class,
Update1_7_0.class,
Update1_8_0.class,
- Update1_9_2.class
+ Update1_9_2.class,
+ Update2_3_0.class
};
@Override
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update2_3_0.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update2_3_0.java
new file mode 100644
index 0000000..0636761
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update2_3_0.java
@@ -0,0 +1,80 @@
+/*
+ * 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.connections.mongo.updater.impl.updates;
+
+import com.mongodb.BasicDBList;
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.mongo.keycloak.entities.ComponentEntity;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.Collections;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class Update2_3_0 extends Update {
+
+ @Override
+ public String getId() {
+ return "2.3.0";
+ }
+
+ @Override
+ public void update(KeycloakSession session) {
+
+ DBCollection realms = db.getCollection("realms");
+ DBCursor cursor = realms.find();
+ while (cursor.hasNext()) {
+ BasicDBObject realm = (BasicDBObject) cursor.next();
+
+ String realmId = realm.getString("_id");
+
+ String privateKeyPem = realm.getString("privateKeyPem");
+ String certificatePem = realm.getString("certificatePem");
+
+ BasicDBList entities = (BasicDBList) realm.get("componentEntities");
+
+ BasicDBObject component = new BasicDBObject();
+ component.put("id", KeycloakModelUtils.generateId());
+ component.put("name", "rsa");
+ component.put("providerType", KeyProvider.class.getName());
+ component.put("providerId", "rsa");
+ component.put("parentId", realmId);
+
+ BasicDBObject config = new BasicDBObject();
+ config.put("priority", Collections.singletonList("100"));
+ config.put("privateKey", Collections.singletonList(privateKeyPem));
+ config.put("certificate", Collections.singletonList(certificatePem));
+
+ component.put("config", config);
+
+ entities.add(component);
+
+ realm.remove("privateKeyPem");
+ realm.remove("certificatePem");
+ realm.remove("publicKeyPem");
+ realm.remove("codeSecret");
+
+ realms.update(new BasicDBObject().append("_id", realmId), realm);
+ }
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 4ebfca8..10e45c0 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -59,6 +59,7 @@ import org.keycloak.models.mongo.keycloak.entities.RequiredActionProviderEntity;
import org.keycloak.models.mongo.keycloak.entities.RequiredCredentialEntity;
import org.keycloak.models.mongo.keycloak.entities.UserFederationMapperEntity;
import org.keycloak.models.mongo.keycloak.entities.UserFederationProviderEntity;
+import org.keycloak.models.utils.ComponentUtil;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.security.Key;
@@ -85,11 +86,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
private final MongoRealmEntity realm;
private final RealmProvider model;
- protected volatile transient PublicKey publicKey;
- protected volatile transient PrivateKey privateKey;
- protected volatile transient X509Certificate certificate;
- protected volatile transient Key codeSecretKey;
-
private volatile transient OTPPolicy otpPolicy;
private volatile transient PasswordPolicy passwordPolicy;
private volatile transient KeycloakSession session;
@@ -456,110 +452,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
@Override
- public String getKeyId() {
- PublicKey publicKey = getPublicKey();
- return publicKey != null ? JWKBuilder.create().rs256(publicKey).getKeyId() : null;
- }
-
- @Override
- public String getPublicKeyPem() {
- return realm.getPublicKeyPem();
- }
-
- @Override
- public void setPublicKeyPem(String publicKeyPem) {
- realm.setPublicKeyPem(publicKeyPem);
- this.publicKey = null;
- updateRealm();
- }
-
- @Override
- public X509Certificate getCertificate() {
- if (certificate != null) return certificate;
- certificate = KeycloakModelUtils.getCertificate(getCertificatePem());
- return certificate;
- }
-
- @Override
- public void setCertificate(X509Certificate certificate) {
- this.certificate = certificate;
- String certificatePem = KeycloakModelUtils.getPemFromCertificate(certificate);
- setCertificatePem(certificatePem);
-
- }
-
- @Override
- public String getCertificatePem() {
- return realm.getCertificatePem();
- }
-
- @Override
- public void setCertificatePem(String certificate) {
- realm.setCertificatePem(certificate);
-
- }
-
-
- @Override
- public String getPrivateKeyPem() {
- return realm.getPrivateKeyPem();
- }
-
- @Override
- public void setPrivateKeyPem(String privateKeyPem) {
- realm.setPrivateKeyPem(privateKeyPem);
- this.privateKey = null;
- updateRealm();
- }
-
- @Override
- public PublicKey getPublicKey() {
- if (publicKey != null) return publicKey;
- publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem());
- return publicKey;
- }
-
- @Override
- public void setPublicKey(PublicKey publicKey) {
- this.publicKey = publicKey;
- String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
- setPublicKeyPem(publicKeyPem);
- }
-
- @Override
- public PrivateKey getPrivateKey() {
- if (privateKey != null) return privateKey;
- privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem());
- return privateKey;
- }
-
- @Override
- public void setPrivateKey(PrivateKey privateKey) {
- this.privateKey = privateKey;
- String privateKeyPem = KeycloakModelUtils.getPemFromKey(privateKey);
- setPrivateKeyPem(privateKeyPem);
- }
-
- @Override
- public String getCodeSecret() {
- return realm.getCodeSecret();
- }
-
- @Override
- public Key getCodeSecretKey() {
- if (codeSecretKey == null) {
- codeSecretKey = KeycloakModelUtils.getSecretKey(getCodeSecret());
- }
- return codeSecretKey;
- }
-
- @Override
- public void setCodeSecret(String codeSecret) {
- realm.setCodeSecret(codeSecret);
- updateRealm();
- }
-
- @Override
public String getLoginTheme() {
return realm.getLoginTheme();
}
@@ -2062,6 +1954,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public ComponentModel addComponentModel(ComponentModel model) {
+ ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, model);
+
ComponentEntity entity = new ComponentEntity();
if (model.getId() == null) {
entity.setId(KeycloakModelUtils.generateId());
@@ -2082,6 +1976,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public void updateComponent(ComponentModel model) {
+ ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, model);
+
for (ComponentEntity entity : realm.getComponentEntities()) {
if (entity.getId().equals(model.getId())) {
entity.setConfig(model.getConfig());
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java
index d1f16f7..7151216 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java
@@ -70,11 +70,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private int accessCodeLifespanLogin;
private int notBefore;
- private String publicKeyPem;
- private String privateKeyPem;
- private String certificatePem;
- private String codeSecret;
-
private String loginTheme;
private String accountTheme;
private String adminTheme;
@@ -351,30 +346,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.notBefore = notBefore;
}
- public String getPublicKeyPem() {
- return publicKeyPem;
- }
-
- public void setPublicKeyPem(String publicKeyPem) {
- this.publicKeyPem = publicKeyPem;
- }
-
- public String getPrivateKeyPem() {
- return privateKeyPem;
- }
-
- public void setPrivateKeyPem(String privateKeyPem) {
- this.privateKeyPem = privateKeyPem;
- }
-
- public String getCodeSecret() {
- return codeSecret;
- }
-
- public void setCodeSecret(String codeSecret) {
- this.codeSecret = codeSecret;
- }
-
public String getLoginTheme() {
return loginTheme;
}
@@ -527,14 +498,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.identityProviders = identityProviders;
}
- public String getCertificatePem() {
- return certificatePem;
- }
-
- public void setCertificatePem(String certificatePem) {
- this.certificatePem = certificatePem;
- }
-
public boolean isInternationalizationEnabled() {
return internationalizationEnabled;
}
diff --git a/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java
index 284d29d..a0603db 100644
--- a/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java
+++ b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java
@@ -33,6 +33,6 @@ public interface ComponentFactory<CreatedType, ProviderType extends Provider> ex
return null;
}
- void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException;
+ void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException;
}
diff --git a/server-spi/src/main/java/org/keycloak/component/ComponentModel.java b/server-spi/src/main/java/org/keycloak/component/ComponentModel.java
index c80df69..b46f5b6 100755
--- a/server-spi/src/main/java/org/keycloak/component/ComponentModel.java
+++ b/server-spi/src/main/java/org/keycloak/component/ComponentModel.java
@@ -20,6 +20,7 @@ package org.keycloak.component;
import org.keycloak.common.util.MultivaluedHashMap;
import java.io.Serializable;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Stored configuration of a User Storage provider instance.
@@ -35,6 +36,7 @@ public class ComponentModel implements Serializable {
private String providerType;
private String parentId;
private MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+ private transient ConcurrentHashMap<String, Object> notes = new ConcurrentHashMap<>();
public ComponentModel() {}
@@ -71,6 +73,57 @@ public class ComponentModel implements Serializable {
this.config = config;
}
+ public boolean contains(String key) {
+ return config.containsKey(key);
+ }
+
+ public String get(String key) {
+ return config.getFirst(key);
+ }
+
+ public int get(String key, int defaultValue) {
+ String s = config.getFirst(key);
+ return s != null ? Integer.parseInt(s) : defaultValue;
+ }
+
+ public long get(String key, long defaultValue) {
+ String s = config.getFirst(key);
+ return s != null ? Long.parseLong(s) : defaultValue;
+ }
+
+ public boolean get(String key, boolean defaultValue) {
+ String s = config.getFirst(key);
+ return s != null ? Boolean.parseBoolean(s) : defaultValue;
+ }
+
+ public void put(String key, String value) {
+ config.putSingle(key, value);
+ }
+
+ public void put(String key, int value) {
+ config.putSingle(key, Integer.toString(value));
+ }
+
+ public void put(String key, long value) {
+ config.putSingle(key, Long.toString(value));
+ }
+
+ public void put(String key, boolean value) {
+ config.putSingle(key, Boolean.toString(value));
+ }
+
+ public boolean hasNote(String key) {
+ return notes.containsKey(key);
+ }
+
+ public <T> T getNote(String key) {
+ return (T) notes.get(key);
+ }
+
+ public void setNote(String key, Object object) {
+ notes.put(key, object);
+ }
+
public String getProviderId() {
return providerId;
}
diff --git a/server-spi/src/main/java/org/keycloak/keys/KeyMetadata.java b/server-spi/src/main/java/org/keycloak/keys/KeyMetadata.java
new file mode 100644
index 0000000..b2d0a58
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/keys/KeyMetadata.java
@@ -0,0 +1,104 @@
+/*
+ * 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.keys;
+
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class KeyMetadata {
+
+ public enum Status {
+ ACTIVE, PASSIVE, DISABLED
+ }
+
+ public enum Type {
+ RSA
+ }
+
+ private String providerId;
+ private long providerPriority;
+
+ private String kid;
+
+ private Status status;
+
+ private Type type;
+
+ private PublicKey publicKey;
+ private Certificate certificate;
+
+ public String getProviderId() {
+ return providerId;
+ }
+
+ public void setProviderId(String providerId) {
+ this.providerId = providerId;
+ }
+
+ public long getProviderPriority() {
+ return providerPriority;
+ }
+
+ public void setProviderPriority(long providerPriority) {
+ this.providerPriority = providerPriority;
+ }
+
+ public String getKid() {
+ return kid;
+ }
+
+ public void setKid(String kid) {
+ this.kid = kid;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ public void setStatus(Status status) {
+ this.status = status;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+ public PublicKey getPublicKey() {
+ return publicKey;
+ }
+
+ public void setPublicKey(PublicKey publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ public Certificate getCertificate() {
+ return certificate;
+ }
+
+ public void setCertificate(Certificate certificate) {
+ this.certificate = certificate;
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/keys/KeyProvider.java b/server-spi/src/main/java/org/keycloak/keys/KeyProvider.java
new file mode 100644
index 0000000..31ca541
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/keys/KeyProvider.java
@@ -0,0 +1,69 @@
+/*
+ * 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.keys;
+
+import org.keycloak.provider.Provider;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface KeyProvider extends Provider {
+
+ /**
+ * Return the KID for the active keypair, or <code>null</code> if no active key is available.
+ *
+ * @return
+ */
+ String getKid();
+
+ /**
+ * Return the private key for the active keypair, or <code>null</code> if no active key is available.
+ *
+ * @return
+ */
+ PrivateKey getPrivateKey();
+
+ /**
+ * Return the public key for the specified kid, or <code>null</code> if the kid is unknown.
+ *
+ * @param kid
+ * @return
+ */
+ PublicKey getPublicKey(String kid);
+
+ /**
+ * Return the certificate for the specified kid, or <code>null</code> if the kid is unknown.
+ *
+ * @param kid
+ * @return
+ */
+ X509Certificate getCertificate(String kid);
+
+ /**
+ * Return metadata about all keypairs held by the provider
+ * @return
+ */
+ List<KeyMetadata> getKeyMetadata();
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/keys/KeyProviderFactory.java b/server-spi/src/main/java/org/keycloak/keys/KeyProviderFactory.java
new file mode 100644
index 0000000..be005b8
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/keys/KeyProviderFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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.keys;
+
+import org.keycloak.component.ComponentFactory;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface KeyProviderFactory<T extends KeyProvider> extends ComponentFactory<T, KeyProvider> {
+
+ T create(KeycloakSession session, ComponentModel model);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/keys/KeySpi.java b/server-spi/src/main/java/org/keycloak/keys/KeySpi.java
new file mode 100644
index 0000000..22adc0d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/keys/KeySpi.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.keys;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class KeySpi implements Spi {
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "keys";
+ }
+
+ @Override
+ public Class<? extends Provider> getProviderClass() {
+ return KeyProvider.class;
+ }
+
+ @Override
+ public Class<? extends ProviderFactory> getProviderFactoryClass() {
+ return KeyProviderFactory.class;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/ClientSessionModel.java b/server-spi/src/main/java/org/keycloak/models/ClientSessionModel.java
index 6c1cb3e..6b7e9d5 100755
--- a/server-spi/src/main/java/org/keycloak/models/ClientSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/ClientSessionModel.java
@@ -24,7 +24,7 @@ import java.util.Set;
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface ClientSessionModel {
- public static final String ACTION_KEY = "action_key";
+ public static final String ACTION_SIGNATURE = "action_signature";
public String getId();
public RealmModel getRealm();
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
index 60893cc..f0481ba 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
@@ -145,6 +145,12 @@ public interface KeycloakSession {
*/
UserFederatedStorageProvider userFederatedStorage();
+ /**
+ * Key manager
+ *
+ * @return
+ */
+ KeyManager keys();
/**
* Keycloak scripting support.
diff --git a/server-spi/src/main/java/org/keycloak/models/KeyManager.java b/server-spi/src/main/java/org/keycloak/models/KeyManager.java
new file mode 100644
index 0000000..757e9a9
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/KeyManager.java
@@ -0,0 +1,71 @@
+/*
+ * 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.models;
+
+import org.keycloak.keys.KeyMetadata;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface KeyManager {
+
+ ActiveKey getActiveKey(RealmModel realm);
+
+ PublicKey getPublicKey(RealmModel realm, String kid);
+
+ Certificate getCertificate(RealmModel realm, String kid);
+
+ List<KeyMetadata> getKeys(RealmModel realm, boolean includeDisabled);
+
+ class ActiveKey {
+ private final String kid;
+ private final PrivateKey privateKey;
+ private final PublicKey publicKey;
+ private final X509Certificate certificate;
+
+ public ActiveKey(String kid, PrivateKey privateKey, PublicKey publicKey, X509Certificate certificate) {
+ this.kid = kid;
+ this.privateKey = privateKey;
+ this.publicKey = publicKey;
+ this.certificate = certificate;
+ }
+
+ public String getKid() {
+ return kid;
+ }
+
+ public PrivateKey getPrivateKey() {
+ return privateKey;
+ }
+
+ public PublicKey getPublicKey() {
+ return publicKey;
+ }
+
+ public X509Certificate getCertificate() {
+ return certificate;
+ }
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
index 7ff7813..aa8b440 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
@@ -178,35 +178,6 @@ public interface RealmModel extends RoleContainerModel {
void setAccessCodeLifespanLogin(int seconds);
- String getKeyId();
-
- String getPublicKeyPem();
-
- void setPublicKeyPem(String publicKeyPem);
-
- String getPrivateKeyPem();
-
- void setPrivateKeyPem(String privateKeyPem);
-
- PublicKey getPublicKey();
-
- void setPublicKey(PublicKey publicKey);
-
- String getCodeSecret();
-
- Key getCodeSecretKey();
-
- void setCodeSecret(String codeSecret);
-
- X509Certificate getCertificate();
- void setCertificate(X509Certificate certificate);
- String getCertificatePem();
- void setCertificatePem(String certificate);
-
- PrivateKey getPrivateKey();
-
- void setPrivateKey(PrivateKey privateKey);
-
List<RequiredCredentialModel> getRequiredCredentials();
void addRequiredCredential(String cred);
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ComponentUtil.java b/server-spi/src/main/java/org/keycloak/models/utils/ComponentUtil.java
new file mode 100644
index 0000000..a0d915e
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/utils/ComponentUtil.java
@@ -0,0 +1,66 @@
+/*
+ * 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.models.utils;
+
+import org.keycloak.component.ComponentFactory;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.provider.ProviderFactory;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ComponentUtil {
+
+ public static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, ComponentModel component) {
+ try {
+ List<ProviderConfigProperty> l = getComponentFactory(session, component).getConfigProperties();
+ Map<String, ProviderConfigProperty> properties = new HashMap<>();
+ for (ProviderConfigProperty p : l) {
+ properties.put(p.getName(), p);
+ }
+ return properties;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static ComponentFactory getComponentFactory(KeycloakSession session, ComponentModel component) {
+ Class<? extends Provider> provider;
+ try {
+ provider = (Class<? extends Provider>) Class.forName(component.getProviderType());
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Invalid provider type '" + component.getProviderType() + "'");
+ }
+
+ ProviderFactory<? extends Provider> f = session.getKeycloakSessionFactory().getProviderFactory(provider, component.getProviderId());
+ if (f == null) {
+ throw new RuntimeException("No such provider '" + component.getProviderId() + "'");
+ }
+
+ ComponentFactory cf = (ComponentFactory) f;
+ return cf;
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index e2e31e6..598e3e9 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -17,15 +17,9 @@
package org.keycloak.models.utils;
-import org.keycloak.common.util.Time;
-import org.keycloak.jose.jws.JWSInput;
-import org.keycloak.jose.jws.JWSInputException;
-import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.representations.PasswordToken;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -33,29 +27,6 @@ import org.keycloak.representations.PasswordToken;
*/
public class CredentialValidation {
-
- public static boolean validPasswordToken(RealmModel realm, UserModel user, String encodedPasswordToken) {
- try {
- JWSInput jws = new JWSInput(encodedPasswordToken);
- if (!RSAProvider.verify(jws, realm.getPublicKey())) {
- return false;
- }
- PasswordToken passwordToken = jws.readJsonContent(PasswordToken.class);
- if (!passwordToken.getRealm().equals(realm.getName())) {
- return false;
- }
- if (!passwordToken.getUser().equals(user.getId())) {
- return false;
- }
- if (Time.currentTime() - passwordToken.getTimestamp() > realm.getAccessCodeLifespanUserAction()) {
- return false;
- }
- return true;
- } catch (JWSInputException e) {
- return false;
- }
- }
-
public static boolean validOTP(RealmModel realm, String token, String secret) {
OTPPolicy policy = realm.getOTPPolicy();
if (policy.getType().equals(UserCredentialModel.TOTP)) {
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java b/server-spi/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java
new file mode 100644
index 0000000..30ff7d7
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java
@@ -0,0 +1,62 @@
+/*
+ * 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.models.utils;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultKeyProviders {
+
+ public static void createProviders(RealmModel realm) {
+ ComponentModel generated = new ComponentModel();
+ generated.setName("rsa-generated");
+ generated.setParentId(realm.getId());
+ generated.setProviderId("rsa-generated");
+ generated.setProviderType(KeyProvider.class.getName());
+
+ MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+ config.putSingle("priority", "100");
+ generated.setConfig(config);
+
+ realm.addComponentModel(generated);
+ }
+
+ public static void createProviders(RealmModel realm, String privateKeyPem, String certificatePem) {
+ ComponentModel rsa = new ComponentModel();
+ rsa.setName("rsa");
+ rsa.setParentId(realm.getId());
+ rsa.setProviderId("rsa");
+ rsa.setProviderType(KeyProvider.class.getName());
+
+ MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+ config.putSingle("priority", "100");
+ config.putSingle("privateKey", privateKeyPem);
+ if (certificatePem != null) {
+ config.putSingle("certificate", certificatePem);
+ }
+ rsa.setConfig(config);
+
+ realm.addComponentModel(rsa);
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index c291c42..1eae859 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -17,12 +17,15 @@
package org.keycloak.models.utils;
-import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.broker.social.SocialIdentityProviderFactory;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.CertificateUtils;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.PemUtils;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.keys.KeyProvider;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
@@ -52,8 +55,6 @@ import javax.crypto.spec.SecretKeySpec;
import javax.transaction.InvalidTransactionException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
-import java.io.IOException;
-import java.io.StringWriter;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -135,82 +136,19 @@ public final class KeycloakModelUtils {
}
public static String getPemFromKey(Key key) {
- StringWriter writer = new StringWriter();
- PEMWriter pemWriter = new PEMWriter(writer);
- try {
- pemWriter.writeObject(key);
- pemWriter.flush();
- pemWriter.close();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- String s = writer.toString();
- return PemUtils.removeBeginEnd(s);
+ return PemUtils.encodeKey(key);
}
public static String getPemFromCertificate(X509Certificate certificate) {
- StringWriter writer = new StringWriter();
- PEMWriter pemWriter = new PEMWriter(writer);
- try {
- pemWriter.writeObject(certificate);
- pemWriter.flush();
- pemWriter.close();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- String s = writer.toString();
- return PemUtils.removeBeginEnd(s);
- }
-
- public static void generateRealmKeys(RealmModel realm) {
- KeyPair keyPair = null;
- try {
- KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
- generator.initialize(2048);
- keyPair = generator.generateKeyPair();
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- realm.setPrivateKey(keyPair.getPrivate());
- realm.setPublicKey(keyPair.getPublic());
- X509Certificate certificate = null;
- try {
- certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- realm.setCertificate(certificate);
-
- realm.setCodeSecret(generateCodeSecret());
- }
-
- public static void generateRealmCertificate(RealmModel realm) {
- X509Certificate certificate = null;
- try {
- certificate = CertificateUtils.generateV1SelfSignedCertificate(new KeyPair(realm.getPublicKey(), realm.getPrivateKey()), realm.getName());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- realm.setCertificate(certificate);
+ return PemUtils.encodeCertificate(certificate);
}
public static CertificateRepresentation generateKeyPairCertificate(String subject) {
- KeyPair keyPair = null;
- try {
- KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
- generator.initialize(2048);
- keyPair = generator.generateKeyPair();
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- X509Certificate certificate = null;
- try {
- certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, subject);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- String privateKeyPem = KeycloakModelUtils.getPemFromKey(keyPair.getPrivate());
- String certPem = KeycloakModelUtils.getPemFromCertificate(certificate);
+ KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
+ X509Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, subject);
+
+ String privateKeyPem = PemUtils.encodeKey(keyPair.getPrivate());
+ String certPem = PemUtils.encodeCertificate(certificate);
CertificateRepresentation rep = new CertificateRepresentation();
rep.setPrivateKey(privateKeyPem);
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 193bbc2..551d62e 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -25,6 +25,7 @@ import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
@@ -265,16 +266,6 @@ public class ModelToRepresentation {
rep.setEnabled(realm.isEnabled());
rep.setNotBefore(realm.getNotBefore());
rep.setSslRequired(realm.getSslRequired().name().toLowerCase());
- rep.setPublicKey(realm.getPublicKeyPem());
- if (internal) {
- rep.setPrivateKey(realm.getPrivateKeyPem());
- String privateKeyPem = realm.getPrivateKeyPem();
- if (realm.getCertificatePem() == null && privateKeyPem != null) {
- KeycloakModelUtils.generateRealmCertificate(realm);
- }
- rep.setCodeSecret(realm.getCodeSecret());
- }
- rep.setCertificate(realm.getCertificatePem());
rep.setRegistrationAllowed(realm.isRegistrationAllowed());
rep.setRegistrationEmailAsUsername(realm.isRegistrationEmailAsUsername());
rep.setRememberMe(realm.isRememberMe());
@@ -783,19 +774,38 @@ public class ModelToRepresentation {
propRep.setType(prop.getType());
propRep.setDefaultValue(prop.getDefaultValue());
propRep.setHelpText(prop.getHelpText());
+ propRep.setSecret(prop.isSecret());
propertiesRep.add(propRep);
}
return propertiesRep;
}
- public static ComponentRepresentation toRepresentation(ComponentModel component) {
+ public static ComponentRepresentation toRepresentation(KeycloakSession session, ComponentModel component, boolean internal) {
ComponentRepresentation rep = new ComponentRepresentation();
rep.setId(component.getId());
rep.setName(component.getName());
rep.setProviderId(component.getProviderId());
rep.setProviderType(component.getProviderType());
rep.setParentId(component.getParentId());
- rep.setConfig(component.getConfig());
+ if (internal) {
+ rep.setConfig(component.getConfig());
+ } else {
+ Map<String, ProviderConfigProperty> configProperties = ComponentUtil.getComponentConfigProperties(session, component);
+ MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+
+ for (Map.Entry<String, List<String>> e : component.getConfig().entrySet()) {
+ ProviderConfigProperty configProperty = configProperties.get(e.getKey());
+ if (configProperty != null) {
+ if (configProperty.isSecret()) {
+ config.putSingle(e.getKey(), ComponentRepresentation.SECRET_VALUE);
+ } else {
+ config.put(e.getKey(), e.getValue());
+ }
+ }
+ }
+
+ rep.setConfig(config);
+ }
return rep;
}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 8d4ac2d..fc8b52f 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -31,10 +31,14 @@ import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.Base64;
+import org.keycloak.common.util.CertificateUtils;
+import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.common.util.PemUtils;
import org.keycloak.common.util.UriUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
+import org.keycloak.keys.KeyProvider;
import org.keycloak.migration.MigrationProvider;
import org.keycloak.migration.migrators.MigrationUtils;
import org.keycloak.models.AuthenticationExecutionModel;
@@ -63,6 +67,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
+import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
@@ -99,12 +104,16 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
+import java.security.KeyPair;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
@@ -183,23 +192,6 @@ public class RepresentationToModel {
if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail());
if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed());
if (rep.isEditUsernameAllowed() != null) newRealm.setEditUsernameAllowed(rep.isEditUsernameAllowed());
- if (rep.getPrivateKey() == null || rep.getPublicKey() == null) {
- KeycloakModelUtils.generateRealmKeys(newRealm);
- } else {
- newRealm.setPrivateKeyPem(rep.getPrivateKey());
- newRealm.setPublicKeyPem(rep.getPublicKey());
- }
- if (rep.getCertificate() == null) {
- KeycloakModelUtils.generateRealmCertificate(newRealm);
- } else {
- newRealm.setCertificatePem(rep.getCertificate());
- }
- if (rep.getCodeSecret() == null) {
- newRealm.setCodeSecret(KeycloakModelUtils.generateCodeSecret());
- } else {
- newRealm.setCodeSecret(rep.getCodeSecret());
- }
-
if (rep.getLoginTheme() != null) newRealm.setLoginTheme(rep.getLoginTheme());
if (rep.getAccountTheme() != null) newRealm.setAccountTheme(rep.getAccountTheme());
if (rep.getAdminTheme() != null) newRealm.setAdminTheme(rep.getAdminTheme());
@@ -381,6 +373,13 @@ public class RepresentationToModel {
}
}
+ if (newRealm.getComponents(newRealm.getId(), KeyProvider.class.getName()).isEmpty()) {
+ if (rep.getPrivateKey() != null) {
+ DefaultKeyProviders.createProviders(newRealm, rep.getPrivateKey(), rep.getCertificate());
+ } else {
+ DefaultKeyProviders.createProviders(newRealm);
+ }
+ }
}
protected static void importComponents(RealmModel newRealm, MultivaluedHashMap<String, ComponentExportRepresentation> components, String parentId) {
@@ -819,20 +818,6 @@ public class RepresentationToModel {
realm.setUserFederationProviders(providerModels);
}
- if (Constants.GENERATE.equals(rep.getPublicKey())) {
- KeycloakModelUtils.generateRealmKeys(realm);
- } else {
- if (rep.getPrivateKey() != null && rep.getPublicKey() != null) {
- realm.setPrivateKeyPem(rep.getPrivateKey());
- realm.setPublicKeyPem(rep.getPublicKey());
- realm.setCodeSecret(KeycloakModelUtils.generateCodeSecret());
- }
-
- if (rep.getCertificate() != null) {
- realm.setCertificatePem(rep.getCertificate());
- }
- }
-
if(rep.isInternationalizationEnabled() != null){
realm.setInternationalizationEnabled(rep.isInternationalizationEnabled());
}
@@ -1692,17 +1677,82 @@ public class RepresentationToModel {
return model;
}
-
- public static ComponentModel toModel(ComponentRepresentation rep) {
+ public static ComponentModel toModel(KeycloakSession session, ComponentRepresentation rep) {
ComponentModel model = new ComponentModel();
model.setParentId(rep.getParentId());
model.setProviderType(rep.getProviderType());
model.setProviderId(rep.getProviderId());
- model.setConfig(rep.getConfig());
+ model.setConfig(new MultivaluedHashMap<>());
model.setName(rep.getName());
+
+ if (rep.getConfig() != null) {
+ Set<String> keys = new HashSet<>(rep.getConfig().keySet());
+ for (String k : keys) {
+ List<String> values = rep.getConfig().get(k);
+ if (values != null) {
+ ListIterator<String> itr = values.listIterator();
+ while (itr.hasNext()) {
+ String v = itr.next();
+ if (v == null || v.trim().isEmpty()) {
+ itr.remove();
+ }
+ }
+
+ if (!values.isEmpty()) {
+ model.getConfig().put(k, values);
+ }
+ }
+ }
+ }
+
return model;
}
+ public static void updateComponent(KeycloakSession session, ComponentRepresentation rep, ComponentModel component, boolean internal) {
+ if (rep.getParentId() != null) {
+ component.setParentId(rep.getParentId());
+ }
+
+ if (rep.getProviderType() != null) {
+ component.setProviderType(rep.getProviderType());
+ }
+
+ if (rep.getProviderId() != null) {
+ component.setProviderId(rep.getProviderId());
+ }
+
+ Map<String, ProviderConfigProperty> providerConfiguration = null;
+ if (!internal) {
+ providerConfiguration = ComponentUtil.getComponentConfigProperties(session, component);
+ }
+
+ if (rep.getConfig() != null) {
+ Set<String> keys = new HashSet<>(rep.getConfig().keySet());
+ for (String k : keys) {
+ if (!internal && !providerConfiguration.containsKey(k)) {
+ break;
+ }
+
+ List<String> values = rep.getConfig().get(k);
+ if (values == null || values.isEmpty() || values.get(0) == null || values.get(0).trim().isEmpty()) {
+ component.getConfig().remove(k);
+ } else {
+ ListIterator<String> itr = values.listIterator();
+ while (itr.hasNext()) {
+ String v = itr.next();
+ if (v == null || v.trim().isEmpty() || v.equals(ComponentRepresentation.SECRET_VALUE)) {
+ itr.remove();
+ }
+ }
+
+ if (!values.isEmpty()) {
+ component.getConfig().put(k, values);
+ }
+ }
+ }
+ }
+ }
+
public static void importAuthorizationSettings(ClientRepresentation clientRepresentation, ClientModel client, KeycloakSession session) {
if (Boolean.TRUE.equals(clientRepresentation.getAuthorizationServicesEnabled())) {
AuthorizationProviderFactory authorizationFactory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class);
diff --git a/server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java b/server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java
new file mode 100644
index 0000000..dd9561f
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java
@@ -0,0 +1,121 @@
+/*
+ * 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.provider;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ConfigurationValidationHelper {
+
+ private ComponentModel model;
+
+ private ConfigurationValidationHelper(ComponentModel model) {
+ this.model = model;
+ }
+
+ public static ConfigurationValidationHelper check(ComponentModel model) {
+ return new ConfigurationValidationHelper(model);
+ }
+
+ public ConfigurationValidationHelper checkInt(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
+ return checkInt(property.getName(), property.getLabel(), required);
+ }
+
+ public ConfigurationValidationHelper checkInt(String key, String label, boolean required) throws ComponentValidationException {
+ checkSingle(key, label, required);
+
+ String val = model.getConfig().getFirst(key);
+ if (val != null) {
+ try {
+ Integer.parseInt(val);
+ } catch (NumberFormatException e) {
+ throw new ComponentValidationException(label + " should be a number");
+ }
+ }
+
+ return this;
+ }
+
+ public ConfigurationValidationHelper checkLong(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
+ return checkLong(property.getName(), property.getLabel(), required);
+ }
+
+ public ConfigurationValidationHelper checkLong(String key, String label, boolean required) throws ComponentValidationException {
+ checkSingle(key, label, required);
+
+ String val = model.getConfig().getFirst(key);
+ if (val != null) {
+ try {
+ Long.parseLong(val);
+ } catch (NumberFormatException e) {
+ throw new ComponentValidationException(label + " should be a number");
+ }
+ }
+
+ return this;
+ }
+
+ public ConfigurationValidationHelper checkSingle(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
+ return checkSingle(property.getName(), property.getLabel(), required);
+ }
+
+ public ConfigurationValidationHelper checkSingle(String key, String label, boolean required) throws ComponentValidationException {
+ if (model.getConfig().containsKey(key) && model.getConfig().get(key).size() > 1) {
+ throw new ComponentValidationException(label + " should be a single entry");
+ }
+
+ if (required) {
+ checkRequired(key, label);
+ }
+
+ return this;
+ }
+
+ public ConfigurationValidationHelper checkRequired(ProviderConfigProperty property) throws ComponentValidationException {
+ return checkRequired(property.getName(), property.getLabel());
+ }
+
+ public ConfigurationValidationHelper checkRequired(String key, String label) throws ComponentValidationException {
+ List<String> values = model.getConfig().get(key);
+ if (values == null) {
+ throw new ComponentValidationException(label + " is required");
+ }
+
+ return this;
+ }
+
+ public ConfigurationValidationHelper checkBoolean(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
+ return checkBoolean(property.getName(), property.getLabel(), required);
+ }
+
+ public ConfigurationValidationHelper checkBoolean(String key, String label, boolean required) {
+ checkSingle(key, label, required);
+
+ String val = model.getConfig().getFirst(key);
+ if (val != null && !(val.equals("true") || val.equals("false"))) {
+ throw new ComponentValidationException(label + " should be 'true' or 'false'");
+ }
+
+ return this;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/provider/ProviderConfigProperty.java b/server-spi/src/main/java/org/keycloak/provider/ProviderConfigProperty.java
index 5ddfb4c..dee0b4e 100755
--- a/server-spi/src/main/java/org/keycloak/provider/ProviderConfigProperty.java
+++ b/server-spi/src/main/java/org/keycloak/provider/ProviderConfigProperty.java
@@ -25,6 +25,7 @@ public class ProviderConfigProperty {
public static final String BOOLEAN_TYPE="boolean";
public static final String STRING_TYPE="String";
public static final String SCRIPT_TYPE="Script";
+ public static final String FILE_TYPE="File";
public static final String ROLE_TYPE="Role";
public static final String LIST_TYPE="List";
public static final String CLIENT_LIST_TYPE="ClientList";
@@ -35,6 +36,7 @@ public class ProviderConfigProperty {
protected String helpText;
protected String type;
protected Object defaultValue;
+ protected boolean secret;
public ProviderConfigProperty() {
}
@@ -47,6 +49,11 @@ public class ProviderConfigProperty {
this.defaultValue = defaultValue;
}
+ public ProviderConfigProperty(String name, String label, String helpText, String type, Object defaultValue, boolean secret) {
+ this(name, label, helpText, type, defaultValue);
+ this.secret = secret;
+ }
+
public String getName() {
return name;
}
@@ -86,4 +93,13 @@ public class ProviderConfigProperty {
public void setHelpText(String helpText) {
this.helpText = helpText;
}
+
+ public boolean isSecret() {
+ return secret;
+ }
+
+ public void setSecret(boolean secret) {
+ this.secret = secret;
+ }
+
}
diff --git a/server-spi/src/main/java/org/keycloak/provider/ProviderConfigurationBuilder.java b/server-spi/src/main/java/org/keycloak/provider/ProviderConfigurationBuilder.java
new file mode 100644
index 0000000..5382355
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/provider/ProviderConfigurationBuilder.java
@@ -0,0 +1,114 @@
+/*
+ * 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.provider;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ProviderConfigurationBuilder {
+
+ private List<ProviderConfigProperty> properties = new LinkedList<>();
+
+ private ProviderConfigurationBuilder() {
+ }
+
+ public static ProviderConfigurationBuilder create() {
+ return new ProviderConfigurationBuilder();
+ }
+
+ public ProviderConfigPropertyBuilder property() {
+ return new ProviderConfigPropertyBuilder();
+ }
+
+ public ProviderConfigurationBuilder property(ProviderConfigProperty property) {
+ properties.add(property);
+ return this;
+ }
+
+ public ProviderConfigurationBuilder property(String name, String label, String helpText, String type, Object defaultValue, boolean secret) {
+ ProviderConfigProperty property = new ProviderConfigProperty(name, label, helpText, type, defaultValue);
+ property.setSecret(secret);
+ properties.add(property);
+ return this;
+ }
+ public ProviderConfigurationBuilder property(String name, String label, String helpText, String type, Object defaultValue) {
+ properties.add(new ProviderConfigProperty(name, label, helpText, type, defaultValue));
+ return this;
+ }
+
+ public List<ProviderConfigProperty> build() {
+ return properties;
+ }
+
+ public class ProviderConfigPropertyBuilder {
+
+ private String name;
+ private String label;
+ private String helpText;
+ private String type;
+ private Object defaultValue;
+ private boolean secret;
+
+ public ProviderConfigPropertyBuilder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public ProviderConfigPropertyBuilder label(String label) {
+ this.label = label;
+ return this;
+ }
+
+ public ProviderConfigPropertyBuilder helpText(String helpText) {
+ this.helpText = helpText;
+ return this;
+ }
+
+ public ProviderConfigPropertyBuilder type(String type) {
+ this.type = type;
+ return this;
+ }
+
+ public ProviderConfigPropertyBuilder defaultValue(Object defaultValue) {
+ this.defaultValue = defaultValue;
+ return this;
+ }
+
+ public ProviderConfigPropertyBuilder secret(boolean secret) {
+ this.secret = secret;
+ return this;
+ }
+
+ public ProviderConfigurationBuilder add() {
+ ProviderConfigProperty property = new ProviderConfigProperty();
+ property.setName(name);
+ property.setLabel(label);
+ property.setHelpText(helpText);
+ property.setType(type);
+ property.setDefaultValue(defaultValue);
+ property.setSecret(secret);
+ ProviderConfigurationBuilder.this.properties.add(property);
+ return ProviderConfigurationBuilder.this;
+ }
+
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/server-spi/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
index 5cf883d..800ed9c 100755
--- a/server-spi/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/server-spi/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -17,18 +17,23 @@
package org.keycloak.services.managers;
+import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.Time;
+import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
-import javax.crypto.Mac;
-import java.security.Key;
+import java.security.PublicKey;
+import java.security.Signature;
import java.util.HashSet;
import java.util.Set;
@@ -38,8 +43,11 @@ import java.util.Set;
*/
public class ClientSessionCode {
- private static final byte[] HASH_SEPERATOR = "//".getBytes();
+ private static final Logger logger = Logger.getLogger(ClientSessionCode.class);
+ private static final String NEXT_CODE = ClientSessionCode.class.getName() + ".nextCode";
+
+ private KeycloakSession session;
private final RealmModel realm;
private final ClientSessionModel clientSession;
@@ -49,32 +57,12 @@ public class ClientSessionCode {
USER
}
- public ClientSessionCode(RealmModel realm, ClientSessionModel clientSession) {
+ public ClientSessionCode(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
+ this.session = session;
this.realm = realm;
this.clientSession = clientSession;
}
- public static ClientSessionCode parse(String code, KeycloakSession session) {
- try {
- String[] parts = code.split("\\.");
- String id = parts[1];
-
- ClientSessionModel clientSession = session.sessions().getClientSession(id);
- if (clientSession == null) {
- return null;
- }
-
- String hash = createHash(clientSession.getRealm(), clientSession);
- if (!hash.equals(parts[0])) {
- return null;
- }
-
- return new ClientSessionCode(clientSession.getRealm(), clientSession);
- } catch (RuntimeException e) {
- return null;
- }
- }
-
public static class ParseResult {
ClientSessionCode code;
boolean clientSessionNotFound;
@@ -114,13 +102,12 @@ public class ClientSessionCode {
return result;
}
- String hash = createHash(realm, result.clientSession);
- if (!hash.equals(parts[0])) {
+ if (!verifyCode(code, session, realm, result.clientSession)) {
result.illegalHash = true;
return result;
}
- result.code = new ClientSessionCode(realm, result.clientSession);
+ result.code = new ClientSessionCode(session, realm, result.clientSession);
return result;
} catch (RuntimeException e) {
result.illegalHash = true;
@@ -128,8 +115,6 @@ public class ClientSessionCode {
}
}
-
-
public static ClientSessionCode parse(String code, KeycloakSession session, RealmModel realm) {
try {
String[] parts = code.split("\\.");
@@ -140,12 +125,11 @@ public class ClientSessionCode {
return null;
}
- String hash = createHash(realm, clientSession);
- if (!hash.equals(parts[0])) {
+ if (!verifyCode(code, session, realm, clientSession)) {
return null;
}
- return new ClientSessionCode(realm, clientSession);
+ return new ClientSessionCode(session, realm, clientSession);
} catch (RuntimeException e) {
return null;
}
@@ -194,7 +178,7 @@ public class ClientSessionCode {
public Set<RoleModel> getRequestedRoles() {
- Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
+ Set<RoleModel> requestedRoles = new HashSet<>();
for (String roleId : clientSession.getRoles()) {
RoleModel role = realm.getRoleById(roleId);
if (role != null) {
@@ -205,7 +189,7 @@ public class ClientSessionCode {
}
public Set<ProtocolMapperModel> getRequestedProtocolMappers() {
- Set<ProtocolMapperModel> requestedProtocolMappers = new HashSet<ProtocolMapperModel>();
+ Set<ProtocolMapperModel> requestedProtocolMappers = new HashSet<>();
Set<String> protocolMappers = clientSession.getProtocolMappers();
ClientModel client = clientSession.getClient();
ClientTemplateModel template = client.getClientTemplate();
@@ -229,32 +213,67 @@ public class ClientSessionCode {
}
public String getCode() {
- return generateCode(realm, clientSession);
+ String nextCode = (String) session.getAttribute(NEXT_CODE + "." + clientSession.getId());
+ if (nextCode == null) {
+ nextCode = generateCode(session, realm, clientSession);
+ session.setAttribute(NEXT_CODE + "." + clientSession.getId(), nextCode);
+ } else {
+ logger.debug("Code already generated for session, using code from session attributes");
+ }
+ return nextCode;
}
- private static String generateCode(RealmModel realm, ClientSessionModel clientSession) {
- String hash = createHash(realm, clientSession);
+ private static String generateCode(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
+ try {
+ KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+
+ String secret = KeycloakModelUtils.generateSecret();
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(secret);
+ sb.append('.');
+ sb.append(clientSession.getId());
+
+ String code = sb.toString();
- StringBuilder sb = new StringBuilder();
- sb.append(hash);
- sb.append(".");
- sb.append(clientSession.getId());
+ Signature signature = RSAProvider.getSignature(Algorithm.RS256);
+ signature.initSign(keys.getPrivateKey());
+ signature.update(code.getBytes("utf-8"));
- return sb.toString();
+ sb = new StringBuilder();
+
+ sb.append(Base64Url.encode(signature.sign()));
+ sb.append('.');
+ sb.append(keys.getKid());
+
+ clientSession.setNote(ClientSessionModel.ACTION_SIGNATURE, sb.toString());
+
+ return code;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
- private static String createHash(RealmModel realm, ClientSessionModel clientSession) {
+ private static boolean verifyCode(String code, KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
try {
- Key codeSecretKey = realm.getCodeSecretKey();
- Mac mac = Mac.getInstance(codeSecretKey.getAlgorithm());
- mac.init(codeSecretKey);
- mac.update(clientSession.getId().getBytes());
- mac.update(HASH_SEPERATOR);
- mac.update(clientSession.getNote(ClientSessionModel.ACTION_KEY).getBytes());
- return Base64Url.encode(mac.doFinal());
+ String note = clientSession.getNote(ClientSessionModel.ACTION_SIGNATURE);
+ if (note == null) {
+ logger.debug("Action signature not found in client session");
+ return false;
+ }
+
+ clientSession.removeNote(ClientSessionModel.ACTION_SIGNATURE);
+
+ String[] signed = note.split("\\.");
+
+ PublicKey publicKey = session.keys().getPublicKey(realm, signed[1]);
+
+ Signature verifier = RSAProvider.getSignature(Algorithm.RS256);
+ verifier.initVerify(publicKey);
+ verifier.update(code.getBytes("utf-8"));
+ return verifier.verify(Base64Url.decode(signed[0]));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
-
}
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 48357e9..bbd588e 100755
--- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -65,3 +65,4 @@ org.keycloak.transaction.TransactionManagerLookupSpi
org.keycloak.credential.hash.PasswordHashSpi
org.keycloak.credential.CredentialSpi
org.keycloak.keys.PublicKeyStorageSpi
+org.keycloak.keys.KeySpi
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 691acf8..7469511 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -212,7 +212,7 @@ public class AuthenticationProcessor {
}
public String generateCode() {
- ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
+ ClientSessionCode accessCode = new ClientSessionCode(session, getRealm(), getClientSession());
clientSession.setTimestamp(Time.currentTime());
return accessCode.getCode();
}
@@ -690,10 +690,10 @@ public class AuthenticationProcessor {
}
- public static Response redirectToRequiredActions(RealmModel realm, ClientSessionModel clientSession, UriInfo uriInfo) {
+ public static Response redirectToRequiredActions(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession, UriInfo uriInfo) {
// redirect to non-action url so browser refresh button works without reposting past data
- ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
+ ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
accessCode.setAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name());
clientSession.setTimestamp(Time.currentTime());
@@ -764,7 +764,7 @@ public class AuthenticationProcessor {
}
public void checkClientSession() {
- ClientSessionCode code = new ClientSessionCode(realm, clientSession);
+ ClientSessionCode code = new ClientSessionCode(session, realm, clientSession);
String action = ClientSessionModel.Action.AUTHENTICATE.name();
if (!code.isValidAction(action)) {
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_SESSION);
@@ -862,7 +862,7 @@ public class AuthenticationProcessor {
protected Response authenticationComplete() {
attachSession();
if (isActionRequired()) {
- return redirectToRequiredActions(realm, clientSession, uriInfo);
+ return redirectToRequiredActions(session, realm, clientSession, uriInfo);
} else {
event.detail(Details.CODE_ID, clientSession.getId()); // todo This should be set elsewhere. find out why tests fail. Don't know where this is supposed to be set
return AuthenticationManager.finishedRequiredActions(session, userSession, clientSession, connection, request, uriInfo, event);
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java
index d1c749b..1e40462 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java
@@ -256,7 +256,6 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
ctx.setLastName(getLastName());
ctx.setBrokerSessionId(getBrokerSessionId());
ctx.setBrokerUserId(getBrokerUserId());
- ctx.setCode(getCode());
ctx.setToken(getToken());
RealmModel realm = clientSession.getRealm();
@@ -297,7 +296,6 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
ctx.setLastName(context.getLastName());
ctx.setBrokerSessionId(context.getBrokerSessionId());
ctx.setBrokerUserId(context.getBrokerUserId());
- ctx.setCode(context.getCode());
ctx.setToken(context.getToken());
ctx.setIdentityProviderId(context.getIdpConfig().getAlias());
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
index 0a764d8..f8408a4 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
@@ -63,7 +63,7 @@ public class IdentityProviderAuthenticator implements Authenticator {
List<IdentityProviderModel> identityProviders = context.getRealm().getIdentityProviders();
for (IdentityProviderModel identityProvider : identityProviders) {
if (identityProvider.isEnabled() && providerId.equals(identityProvider.getAlias())) {
- String accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode();
+ String accessCode = new ClientSessionCode(context.getSession(), context.getRealm(), context.getClientSession()).getCode();
Response response = Response.seeOther(
Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode))
.build();
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index 684075e..8f830d1 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -148,7 +148,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
@Override
public String generateCode() {
- ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
+ ClientSessionCode accessCode = new ClientSessionCode(session, getRealm(), getClientSession());
clientSession.setTimestamp(Time.currentTime());
return accessCode.getCode();
}
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
index cdb7cd0..77a35e9 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -38,6 +38,7 @@ import org.keycloak.authorization.util.Permissions;
import org.keycloak.authorization.util.Tokens;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
@@ -77,6 +78,9 @@ public class AuthorizationTokenService {
@Context
private HttpRequest httpRequest;
+ @Context
+ private KeycloakSession session;
+
public AuthorizationTokenService(AuthorizationProvider authorization) {
this.authorization = authorization;
}
@@ -180,7 +184,7 @@ public class AuthorizationTokenService {
String rpt = request.getRpt();
if (rpt != null && !"".equals(rpt)) {
- if (!Tokens.verifySignature(rpt, getRealm().getPublicKey())) {
+ if (!Tokens.verifySignature(session, getRealm(), rpt)) {
throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
}
@@ -252,13 +256,13 @@ public class AuthorizationTokenService {
authorization.setPermissions(permissions);
accessToken.setAuthorization(authorization);
- return new TokenManager().encodeToken(getRealm(), accessToken);
+ return new TokenManager().encodeToken(session, getRealm(), accessToken);
}
private PermissionTicket verifyPermissionTicket(AuthorizationRequest request) {
String ticketString = request.getTicket();
- if (ticketString == null || !Tokens.verifySignature(ticketString, getRealm().getPublicKey())) {
+ if (ticketString == null || !Tokens.verifySignature(session, getRealm(), ticketString)) {
throw new ErrorResponseException("invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
}
diff --git a/services/src/main/java/org/keycloak/authorization/config/UmaWellKnownProvider.java b/services/src/main/java/org/keycloak/authorization/config/UmaWellKnownProvider.java
index 07e5908..884f028 100644
--- a/services/src/main/java/org/keycloak/authorization/config/UmaWellKnownProvider.java
+++ b/services/src/main/java/org/keycloak/authorization/config/UmaWellKnownProvider.java
@@ -17,6 +17,7 @@
*/
package org.keycloak.authorization.config;
+import org.keycloak.common.util.PemUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@@ -46,7 +47,7 @@ public class UmaWellKnownProvider implements WellKnownProvider {
return Configuration.fromDefault(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString(), realm.getName(),
URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()),
URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "token").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()),
- realm.getPublicKeyPem());
+ PemUtils.encodeKey(session.keys().getActiveKey(realm).getPublicKey()));
}
@Override
diff --git a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
index e281063..9f89e4a 100644
--- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
+++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
@@ -40,6 +40,7 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
@@ -81,6 +82,9 @@ public class EntitlementService {
@Context
private HttpRequest request;
+ @Context
+ private KeycloakSession session;
+
public EntitlementService(AuthorizationProvider authorization) {
this.authorization = authorization;
}
@@ -200,7 +204,7 @@ public class EntitlementService {
authorization.setPermissions(permissions);
accessToken.setAuthorization(authorization);
- return new TokenManager().encodeToken(realm, accessToken);
+ return new TokenManager().encodeToken(this.authorization.getKeycloakSession(), realm, accessToken);
}
private List<ResourcePermission> createPermissions(EntitlementRequest entitlementRequest, ResourceServer resourceServer, AuthorizationProvider authorization) {
@@ -252,7 +256,7 @@ public class EntitlementService {
if (rpt != null && !"".equals(rpt)) {
KeycloakContext context = authorization.getKeycloakSession().getContext();
- if (!Tokens.verifySignature(rpt, context.getRealm().getPublicKey())) {
+ if (!Tokens.verifySignature(session, context.getRealm(), rpt)) {
throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
}
diff --git a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java
index 09bf694..eee504a 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java
@@ -25,6 +25,7 @@ import org.keycloak.authorization.protection.permission.representation.Permissio
import org.keycloak.authorization.protection.permission.representation.PermissionResponse;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.models.KeyManager;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.ErrorResponseException;
@@ -130,7 +131,8 @@ public class AbstractPermissionService {
}
private String createPermissionTicket(List<ResourceRepresentation> resources) {
- return new JWSBuilder().jsonContent(new PermissionTicket(resources, this.resourceServer.getId(), this.identity.getAccessToken()))
- .rsa256(this.authorization.getKeycloakSession().getContext().getRealm().getPrivateKey());
+ KeyManager.ActiveKey keys = this.authorization.getKeycloakSession().keys().getActiveKey(this.authorization.getRealm());
+ return new JWSBuilder().kid(keys.getKid()).jsonContent(new PermissionTicket(resources, this.resourceServer.getId(), this.identity.getAccessToken()))
+ .rsa256(keys.getPrivateKey());
}
}
diff --git a/services/src/main/java/org/keycloak/authorization/util/Tokens.java b/services/src/main/java/org/keycloak/authorization/util/Tokens.java
index 0deeef5..056f9fb 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Tokens.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Tokens.java
@@ -22,6 +22,7 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AppAuthManager;
@@ -53,10 +54,10 @@ public class Tokens {
return authManager.extractAuthorizationHeaderToken(keycloakSession.getContext().getRequestHeaders());
}
- public static boolean verifySignature(String token, PublicKey publicKey) {
+ public static boolean verifySignature(KeycloakSession keycloakSession, RealmModel realm, String token) {
try {
JWSInput jws = new JWSInput(token);
-
+ PublicKey publicKey = keycloakSession.keys().getPublicKey(realm, jws.getHeader().getKeyId());
return RSAProvider.verify(jws, publicKey);
} catch (Exception e) {
throw new ErrorResponseException("invalid_signature", "Unexpected error while validating signature.", Status.INTERNAL_SERVER_ERROR);
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 3b218cc..170925b 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -233,9 +233,9 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
federatedIdentity.setToken(response);
}
- federatedIdentity.setCode(state);
federatedIdentity.setIdpConfig(getConfig());
federatedIdentity.setIdp(AbstractOAuth2IdentityProvider.this);
+ federatedIdentity.setCode(state);
return callback.authenticated(federatedIdentity);
}
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index f55aa02..997bc93 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -38,6 +38,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
+import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
@@ -265,7 +266,8 @@ public class SAMLEndpoint {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
.relayState(relayState);
if (config.isWantAuthnRequestsSigned()) {
- binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
+ KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+ binding.signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
.signatureAlgorithm(provider.getSignatureAlgorithm())
.signDocument();
}
@@ -291,13 +293,13 @@ public class SAMLEndpoint {
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
try {
- AssertionType assertion = AssertionUtil.getAssertion(responseType, realm.getPrivateKey());
+ KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+ AssertionType assertion = AssertionUtil.getAssertion(responseType, keys.getPrivateKey());
SubjectType subject = assertion.getSubject();
SubjectType.STSubType subType = subject.getSubType();
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
//Map<String, String> notes = new HashMap<>();
BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue());
- identity.setCode(relayState);
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
identity.getContextData().put(SAML_ASSERTION, assertion);
@@ -340,6 +342,7 @@ public class SAMLEndpoint {
if (authn != null && authn.getSessionIndex() != null) {
identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
}
+ identity.setCode(relayState);
return callback.authenticated(identity);
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index f7db32b..104b8f8 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -23,6 +23,7 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
import org.keycloak.broker.provider.util.SimpleHttp;
+import org.keycloak.common.util.PemUtils;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
@@ -31,6 +32,7 @@ import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
@@ -97,18 +99,9 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
.relayState(request.getState());
if (getConfig().isWantAuthnRequestsSigned()) {
- PrivateKey privateKey = realm.getPrivateKey();
- PublicKey publicKey = realm.getPublicKey();
+ KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
- if (privateKey == null) {
- throw new IdentityBrokerException("Identity Provider [" + getConfig().getAlias() + "] wants a signed authentication request. But the Realm [" + realm.getName() + "] does not have a private key.");
- }
-
- if (publicKey == null) {
- throw new IdentityBrokerException("Identity Provider [" + getConfig().getAlias() + "] wants a signed authentication request. But the Realm [" + realm.getName() + "] does not have a public key.");
- }
-
- KeyPair keypair = new KeyPair(publicKey, privateKey);
+ KeyPair keypair = new KeyPair(keys.getPublicKey(), keys.getPrivateKey());
binding.signWith(keypair);
binding.signatureAlgorithm(getSignatureAlgorithm());
@@ -155,7 +148,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
- JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm);
+ JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm);
try {
int status = SimpleHttp.doPost(singleLogoutServiceUrl)
.param(GeneralConstants.SAML_REQUEST_KEY, binding.postBinding(logoutBuilder.buildDocument()).encoded())
@@ -181,7 +174,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
} else {
try {
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
- JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm);
+ JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm);
return binding.postBinding(logoutBuilder.buildDocument()).request(singleLogoutServiceUrl);
} catch (Exception e) {
throw new RuntimeException(e);
@@ -200,11 +193,12 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
return logoutBuilder;
}
- private JaxrsSAML2BindingBuilder buildLogoutBinding(UserSessionModel userSession, RealmModel realm) {
+ private JaxrsSAML2BindingBuilder buildLogoutBinding(KeycloakSession session, UserSessionModel userSession, RealmModel realm) {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
.relayState(userSession.getId());
if (getConfig().isWantAuthnRequestsSigned()) {
- binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
+ KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+ binding.signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
.signatureAlgorithm(getSignatureAlgorithm())
.signDocument();
}
@@ -231,7 +225,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
String entityId = getEntityId(uriInfo, realm);
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
- String certificatePem = realm.getCertificatePem();
+ String certificatePem = PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate());
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, certificatePem);
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
}
diff --git a/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java
new file mode 100644
index 0000000..c5f09f1
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java
@@ -0,0 +1,141 @@
+/*
+ * 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.keys;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.RealmModel;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class AbstractRsaKeyProvider implements KeyProvider {
+
+ private final boolean enabled;
+
+ private final boolean active;
+
+ private final ComponentModel model;
+
+ private final Keys keys;
+
+ public AbstractRsaKeyProvider(RealmModel realm, ComponentModel model) {
+ this.model = model;
+
+ this.enabled = model.get(Attributes.ENABLED_KEY, true);
+ this.active = model.get(Attributes.ACTIVE_KEY, true);
+
+ if (model.hasNote(Keys.class.getName())) {
+ keys = model.getNote(Keys.class.getName());
+ } else {
+ keys = loadKeys(realm, model);
+ model.setNote(Keys.class.getName(), keys);
+ }
+ }
+
+ protected abstract Keys loadKeys(RealmModel realm, ComponentModel model);
+
+ @Override
+ public final String getKid() {
+ return isActive() ? keys.getKid() : null;
+ }
+
+ @Override
+ public final PrivateKey getPrivateKey() {
+ return isActive() ? keys.getKeyPair().getPrivate() : null;
+ }
+
+ @Override
+ public final PublicKey getPublicKey(String kid) {
+ return isEnabled() && kid.equals(keys.getKid()) ? keys.getKeyPair().getPublic() : null;
+ }
+
+ @Override
+ public X509Certificate getCertificate(String kid) {
+ return isEnabled() && kid.equals(keys.getKid()) ? keys.getCertificate() : null;
+ }
+
+ @Override
+ public final List<KeyMetadata> getKeyMetadata() {
+ String kid = keys.getKid();
+ PublicKey publicKey = keys.getKeyPair().getPublic();
+ if (kid != null && publicKey != null) {
+ KeyMetadata k = new KeyMetadata();
+ k.setProviderId(model.getId());
+ k.setProviderPriority(model.get(Attributes.PRIORITY_KEY, 0l));
+ k.setKid(kid);
+ if (isActive()) {
+ k.setStatus(KeyMetadata.Status.ACTIVE);
+ } else if (isEnabled()) {
+ k.setStatus(KeyMetadata.Status.PASSIVE);
+ } else {
+ k.setStatus(KeyMetadata.Status.DISABLED);
+ }
+ k.setType(KeyMetadata.Type.RSA);
+ k.setPublicKey(publicKey);
+ k.setCertificate(keys.getCertificate());
+ return Collections.singletonList(k);
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ @Override
+ public void close() {
+ }
+
+ private boolean isEnabled() {
+ return keys != null && enabled;
+ }
+
+ private boolean isActive() {
+ return isEnabled() && active;
+ }
+
+ public static class Keys {
+ private String kid;
+ private KeyPair keyPair;
+ private X509Certificate certificate;
+
+ public Keys(String kid, KeyPair keyPair, X509Certificate certificate) {
+ this.kid = kid;
+ this.keyPair = keyPair;
+ this.certificate = certificate;
+ }
+
+ public String getKid() {
+ return kid;
+ }
+
+ public KeyPair getKeyPair() {
+ return keyPair;
+ }
+
+ public X509Certificate getCertificate() {
+ return certificate;
+ }
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProviderFactory.java
new file mode 100644
index 0000000..7b2d526
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProviderFactory.java
@@ -0,0 +1,45 @@
+/*
+ * 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.keys;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.provider.ConfigurationValidationHelper;
+import org.keycloak.provider.ProviderConfigurationBuilder;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class AbstractRsaKeyProviderFactory implements KeyProviderFactory {
+
+ public final static ProviderConfigurationBuilder configurationBuilder() {
+ return ProviderConfigurationBuilder.create()
+ .property(Attributes.PRIORITY_PROPERTY)
+ .property(Attributes.ENABLED_PROPERTY)
+ .property(Attributes.ACTIVE_PROPERTY);
+ }
+
+ @Override
+ public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
+ ConfigurationValidationHelper.check(model)
+ .checkLong(Attributes.PRIORITY_PROPERTY, false)
+ .checkBoolean(Attributes.ENABLED_PROPERTY, false)
+ .checkBoolean(Attributes.ACTIVE_PROPERTY, false);
+ }
+}
diff --git a/services/src/main/java/org/keycloak/keys/Attributes.java b/services/src/main/java/org/keycloak/keys/Attributes.java
new file mode 100644
index 0000000..758ec95
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/Attributes.java
@@ -0,0 +1,49 @@
+/*
+ * 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.keys;
+
+import org.keycloak.provider.ProviderConfigProperty;
+
+import static org.keycloak.provider.ProviderConfigProperty.BOOLEAN_TYPE;
+import static org.keycloak.provider.ProviderConfigProperty.FILE_TYPE;
+import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Attributes {
+
+ String PRIORITY_KEY = "priority";
+ ProviderConfigProperty PRIORITY_PROPERTY = new ProviderConfigProperty(PRIORITY_KEY, "Priority", "Priority for the provider", STRING_TYPE, "0");
+
+ String ENABLED_KEY = "enabled";
+ ProviderConfigProperty ENABLED_PROPERTY = new ProviderConfigProperty(ENABLED_KEY, "Enabled", "Set if the keys are enabled", BOOLEAN_TYPE, "true");
+
+ String ACTIVE_KEY = "active";
+ ProviderConfigProperty ACTIVE_PROPERTY = new ProviderConfigProperty(ACTIVE_KEY, "Active", "Set if the keys can be used for signing", BOOLEAN_TYPE, "true");
+
+ String PRIVATE_KEY_KEY = "privateKey";
+ ProviderConfigProperty PRIVATE_KEY_PROPERTY = new ProviderConfigProperty(PRIVATE_KEY_KEY, "Private RSA Key", "Private RSA Key encoded in PEM format", FILE_TYPE, null, true);
+
+ String CERTIFICATE_KEY = "certificate";
+ ProviderConfigProperty CERTIFICATE_PROPERTY = new ProviderConfigProperty(CERTIFICATE_KEY, "X509 Certificate", "X509 Certificate encoded in PEM format", FILE_TYPE, null);
+
+ String KEY_SIZE_KEY = "keySize";
+ ProviderConfigProperty KEY_SIZE_PROPERTY = new ProviderConfigProperty(KEY_SIZE_KEY, "Keysize", "Size for the generated keys (1024, 2048 or 4096)", STRING_TYPE, null);
+
+}
diff --git a/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java b/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java
new file mode 100644
index 0000000..df18005
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java
@@ -0,0 +1,162 @@
+/*
+ * 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.keys;
+
+import org.jboss.logging.Logger;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeyManager;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.ProviderFactory;
+
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultKeyManager implements KeyManager {
+
+ private static final Logger logger = Logger.getLogger(DefaultKeyManager.class);
+
+ private final KeycloakSession session;
+ private final Map<String, List<KeyProvider>> providersMap = new HashMap<>();
+
+ public DefaultKeyManager(KeycloakSession session) {
+ this.session = session;
+ }
+
+ @Override
+ public ActiveKey getActiveKey(RealmModel realm) {
+ for (KeyProvider p : getProviders(realm)) {
+ if (p.getKid() != null && p.getPrivateKey() != null) {
+ if (logger.isTraceEnabled()) {
+ logger.tracev("Active key realm={0} kid={1}", realm.getName(), p.getKid());
+ }
+ String kid = p.getKid();
+ return new ActiveKey(kid, p.getPrivateKey(), p.getPublicKey(kid), p.getCertificate(kid));
+ }
+ }
+ throw new RuntimeException("Failed to get keys");
+ }
+
+ @Override
+ public PublicKey getPublicKey(RealmModel realm, String kid) {
+ if (kid == null) {
+ logger.warnv("KID is null, can't find public key", realm.getName(), kid);
+ return null;
+ }
+
+ for (KeyProvider p : getProviders(realm)) {
+ PublicKey publicKey = p.getPublicKey(kid);
+ if (publicKey != null) {
+ if (logger.isTraceEnabled()) {
+ logger.tracev("Found public key realm={0} kid={1}", realm.getName(), kid);
+ }
+ return publicKey;
+ }
+ }
+ if (logger.isTraceEnabled()) {
+ logger.tracev("Failed to find public key realm={0} kid={1}", realm.getName(), kid);
+ }
+ return null;
+ }
+
+ @Override
+ public Certificate getCertificate(RealmModel realm, String kid) {
+ if (kid == null) {
+ logger.warnv("KID is null, can't find public key", realm.getName(), kid);
+ return null;
+ }
+
+ for (KeyProvider p : getProviders(realm)) {
+ Certificate certificate = p.getCertificate(kid);
+ if (certificate != null) {
+ if (logger.isTraceEnabled()) {
+ logger.tracev("Found certificate realm={0} kid={1}", realm.getName(), kid);
+ }
+ return certificate;
+ }
+ }
+ if (logger.isTraceEnabled()) {
+ logger.tracev("Failed to find certificate realm={0} kid={1}", realm.getName(), kid);
+ }
+ return null;
+ }
+
+ @Override
+ public List<KeyMetadata> getKeys(RealmModel realm, boolean includeDisabled) {
+ List<KeyMetadata> keys = new LinkedList<>();
+ for (KeyProvider p : getProviders(realm)) {
+ if (includeDisabled) {
+ keys.addAll(p.getKeyMetadata());
+ } else {
+ p.getKeyMetadata().stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
+ }
+ }
+ return keys;
+ }
+
+ private List<KeyProvider> getProviders(RealmModel realm) {
+ boolean active = false;
+ List<KeyProvider> providers = providersMap.get(realm.getId());
+ if (providers == null) {
+ providers = new LinkedList<>();
+
+ List<ComponentModel> components = new LinkedList<>(realm.getComponents(realm.getId(), KeyProvider.class.getName()));
+ components.sort(new ProviderComparator());
+
+ for (ComponentModel c : components) {
+ try {
+ ProviderFactory<KeyProvider> f = session.getKeycloakSessionFactory().getProviderFactory(KeyProvider.class, c.getProviderId());
+ KeyProviderFactory factory = (KeyProviderFactory) f;
+ KeyProvider provider = factory.create(session, c);
+ session.enlistForClose(provider);
+ providers.add(provider);
+ if (!active && provider.getKid() != null && provider.getPrivateKey() != null) {
+ active = true;
+ }
+ } catch (Throwable t) {
+ logger.errorv(t, "Failed to load provider {0}", c.getId());
+ }
+ }
+
+ if (!active) {
+ providers.add(new FailsafeRsaKeyProvider());
+ }
+
+ providersMap.put(realm.getId(), providers);
+ }
+ return providers;
+ }
+
+ private class ProviderComparator implements Comparator<ComponentModel> {
+
+ @Override
+ public int compare(ComponentModel o1, ComponentModel o2) {
+ int i = Long.compare(o2.get("priority", 0l), o1.get("priority", 0l));
+ return i != 0 ? i : o1.getId().compareTo(o2.getId());
+ }
+
+ }
+}
diff --git a/services/src/main/java/org/keycloak/keys/FailsafeRsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/FailsafeRsaKeyProvider.java
new file mode 100644
index 0000000..00586a8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/FailsafeRsaKeyProvider.java
@@ -0,0 +1,96 @@
+/*
+ * 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.keys;
+
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.Time;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FailsafeRsaKeyProvider implements KeyProvider {
+
+ private static final Logger logger = Logger.getLogger(FailsafeRsaKeyProvider.class);
+
+ private static String KID;
+
+ private static KeyPair KEY_PAIR;
+
+ private static long EXPIRES;
+
+ private KeyPair keyPair;
+
+ private String kid;
+
+ public FailsafeRsaKeyProvider() {
+ logger.errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
+
+ synchronized (FailsafeRsaKeyProvider.class) {
+ if (EXPIRES < Time.currentTime()) {
+ KEY_PAIR = KeyUtils.generateRsaKeyPair(2048);
+ KID = KeyUtils.createKeyId(KEY_PAIR.getPublic());
+ EXPIRES = Time.currentTime() + 60 * 10;
+
+ if (EXPIRES > 0) {
+ logger.warnv("Keys expired, re-generated kid={0}", KID);
+ }
+ }
+
+ kid = KID;
+ keyPair = KEY_PAIR;
+ }
+ }
+
+ @Override
+ public String getKid() {
+ return kid;
+ }
+
+ @Override
+ public PrivateKey getPrivateKey() {
+ return keyPair.getPrivate();
+ }
+
+ @Override
+ public PublicKey getPublicKey(String kid) {
+ return kid.equals(this.kid) ? keyPair.getPublic() : null;
+ }
+
+ @Override
+ public X509Certificate getCertificate(String kid) {
+ return null;
+ }
+
+ @Override
+ public List<KeyMetadata> getKeyMetadata() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void close() {
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/keys/GeneratedRsaKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/GeneratedRsaKeyProviderFactory.java
new file mode 100644
index 0000000..9042b48
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/GeneratedRsaKeyProviderFactory.java
@@ -0,0 +1,142 @@
+/*
+ * 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.keys;
+
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.common.util.CertificateUtils;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.ConfigurationValidationHelper;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactory {
+
+ private static final Logger logger = Logger.getLogger(GeneratedRsaKeyProviderFactory.class);
+
+ public static final String ID = "rsa-generated";
+
+ private static final String HELP_TEXT = "Generates RSA keys and creates a self-signed certificate";
+
+ private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder()
+ .property(Attributes.KEY_SIZE_PROPERTY)
+ .build();
+
+ @Override
+ public KeyProvider create(KeycloakSession session, ComponentModel model) {
+ return new RsaKeyProvider(session.getContext().getRealm(), model);
+ }
+
+ @Override
+ public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
+ super.validateConfiguration(session, model);
+
+ ConfigurationValidationHelper.check(model)
+ .checkInt(Attributes.KEY_SIZE_PROPERTY, false);
+
+ int size;
+ if (!model.contains(Attributes.KEY_SIZE_KEY)) {
+ size = 2048;
+ model.put(Attributes.KEY_SIZE_KEY, size);
+ } else {
+ size = model.get(Attributes.KEY_SIZE_KEY, 2048);
+ if (size != 1024 && size != 2048 && size != 4096) {
+ throw new ComponentValidationException("Keysize should be 1024, 2048 or 4096");
+ }
+ }
+
+ if (!(model.contains(Attributes.PRIVATE_KEY_KEY) && model.contains(Attributes.CERTIFICATE_KEY))) {
+ RealmModel realm = session.realms().getRealm(model.getParentId());
+ generateKeys(realm, model, size);
+
+ logger.debugv("Generated keys for {0}", realm.getName());
+ } else {
+ PrivateKey privateKey = PemUtils.decodePrivateKey(model.get(Attributes.PRIVATE_KEY_KEY));
+ int currentSize = ((RSAPrivateKey) privateKey).getModulus().bitLength();
+ if (currentSize != size) {
+ RealmModel realm = session.realms().getRealm(model.getParentId());
+ generateKeys(realm, model, size);
+
+ logger.debugv("Key size changed, generating new keys for {0}", realm.getName());
+ }
+ }
+ }
+
+ private void generateKeys(RealmModel realm, ComponentModel model, int size) {
+ KeyPair keyPair;
+ try {
+ keyPair = KeyUtils.generateRsaKeyPair(size);
+ model.put(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
+ } catch (Throwable t) {
+ throw new ComponentValidationException("Failed to generate keys", t);
+ }
+
+ generateCertificate(realm, model, keyPair);
+ }
+
+ private void generateCertificate(RealmModel realm, ComponentModel model, KeyPair keyPair) {
+ try {
+ Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
+ model.put(Attributes.CERTIFICATE_KEY, PemUtils.encodeCertificate(certificate));
+ } catch (Throwable t) {
+ throw new ComponentValidationException("Failed to generate certificate", t);
+ }
+ }
+
+ @Override
+ public String getHelpText() {
+ return HELP_TEXT;
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return CONFIG_PROPERTIES;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java
new file mode 100644
index 0000000..07e91a4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java
@@ -0,0 +1,71 @@
+/*
+ * 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.keys;
+
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.CertificateUtils;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.RealmModel;
+
+import java.io.FileInputStream;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
+
+ private static final Logger logger = Logger.getLogger(JavaKeystoreKeyProvider.class);
+
+ public JavaKeystoreKeyProvider(RealmModel realm, ComponentModel model) {
+ super(realm, model);
+ }
+
+ @Override
+ protected Keys loadKeys(RealmModel realm, ComponentModel model) {
+ try {
+ KeyStore keyStore = KeyStore.getInstance("JKS");
+ keyStore.load(new FileInputStream(model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_KEY)), model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_PASSWORD_KEY).toCharArray());
+
+ PrivateKey privateKey = (PrivateKey) keyStore.getKey(model.get(JavaKeystoreKeyProviderFactory.KEY_ALIAS_KEY), model.get(JavaKeystoreKeyProviderFactory.KEY_PASSWORD_KEY).toCharArray());
+ PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
+
+ KeyPair keyPair = new KeyPair(publicKey, privateKey);
+
+ X509Certificate certificate;
+ if (model.contains(JavaKeystoreKeyProviderFactory.CERTIFICATE_ALIAS_KEY)) {
+ certificate = (X509Certificate) keyStore.getCertificate(model.get(JavaKeystoreKeyProviderFactory.CERTIFICATE_ALIAS_KEY));
+ } else {
+ certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
+ }
+
+ String kid = KeyUtils.createKeyId(keyPair.getPublic());
+
+ return new Keys(kid, keyPair, certificate);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load keys", e);
+ }
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java
new file mode 100644
index 0000000..6b6e530
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java
@@ -0,0 +1,115 @@
+/*
+ * 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.keys;
+
+import org.keycloak.Config;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ConfigurationValidationHelper;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.List;
+
+import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactory {
+
+ public static final String ID = "java-keystore";
+
+ public static String KEYSTORE_KEY = "keystore";
+ public static ProviderConfigProperty KEYSTORE_PROPERTY = new ProviderConfigProperty(KEYSTORE_KEY, "Keystore", "Path to keys file", STRING_TYPE, null);
+
+ public static String KEYSTORE_PASSWORD_KEY = "keystorePassword";
+ public static ProviderConfigProperty KEYSTORE_PASSWORD_PROPERTY = new ProviderConfigProperty(KEYSTORE_PASSWORD_KEY, "Keystore Password", "Password for the keys", STRING_TYPE, null, true);
+
+ public static String KEY_ALIAS_KEY = "keyAlias";
+ public static ProviderConfigProperty KEY_ALIAS_PROPERTY = new ProviderConfigProperty(KEY_ALIAS_KEY, "Private Key Alias", "Alias for the private key", STRING_TYPE, null);
+
+ public static String KEY_PASSWORD_KEY = "keyPassword";
+ public static ProviderConfigProperty KEY_PASSWORD_PROPERTY = new ProviderConfigProperty(KEY_PASSWORD_KEY, "Private Key password", "Password for the private key", STRING_TYPE, null, true);
+
+ public static String CERTIFICATE_ALIAS_KEY = "certificateAlias";
+ public static ProviderConfigProperty CERTIFICATE_ALIAS_PROPERTY = new ProviderConfigProperty(CERTIFICATE_ALIAS_KEY, "Certificate Alias", "Alias for the certificate", STRING_TYPE, null);
+
+ private static final String HELP_TEXT = "Loads keys from a Java keys file";
+
+ private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder()
+ .property(KEYSTORE_PROPERTY)
+ .property(KEYSTORE_PASSWORD_PROPERTY)
+ .property(KEY_ALIAS_PROPERTY)
+ .property(KEY_PASSWORD_PROPERTY)
+ .property(CERTIFICATE_ALIAS_PROPERTY)
+ .build();
+
+ @Override
+ public KeyProvider create(KeycloakSession session, ComponentModel model) {
+ return new JavaKeystoreKeyProvider(session.getContext().getRealm(), model);
+ }
+
+ @Override
+ public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
+ super.validateConfiguration(session, model);
+
+ ConfigurationValidationHelper.check(model)
+ .checkSingle(KEYSTORE_PROPERTY, true)
+ .checkSingle(KEYSTORE_PASSWORD_PROPERTY, true)
+ .checkSingle(KEY_ALIAS_PROPERTY, true)
+ .checkSingle(KEY_PASSWORD_PROPERTY, true)
+ .checkSingle(CERTIFICATE_ALIAS_PROPERTY, false);
+
+ try {
+ new JavaKeystoreKeyProvider(session.getContext().getRealm(), model)
+ .loadKeys(session.getContext().getRealm(), model);
+ } catch (Throwable t) {
+ throw new ComponentValidationException("Failed to load keys", t);
+ }
+ }
+
+ @Override
+ public String getHelpText() {
+ return HELP_TEXT;
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return CONFIG_PROPERTIES;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java b/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java
index 8168832..23eb726 100644
--- a/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java
+++ b/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java
@@ -23,9 +23,9 @@ import java.util.Collections;
import java.util.Map;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
+import org.keycloak.common.util.KeyUtils;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
-import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.keys.PublicKeyLoader;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
@@ -69,7 +69,7 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
PublicKey publicKey = getSignatureValidationKey(certInfo);
// Check if we have kid in DB, generate otherwise
- String kid = certInfo.getKid() != null ? certInfo.getKid() : JWKBuilder.createKeyId(publicKey);
+ String kid = certInfo.getKid() != null ? certInfo.getKid() : KeyUtils.createKeyId(publicKey);
return Collections.singletonMap(kid, publicKey);
} catch (ModelException me) {
logger.warnf(me, "Unable to retrieve publicKey for verify signature of client '%s' . Error details: %s", client.getClientId(), me.getMessage());
diff --git a/services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java b/services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java
index 439f51d..f41ded8 100644
--- a/services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java
+++ b/services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java
@@ -22,10 +22,10 @@ import java.util.Collections;
import java.util.Map;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
+import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
-import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.keys.PublicKeyLoader;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.utils.JWKSHttpUtils;
@@ -60,7 +60,7 @@ public class OIDCIdentityProviderPublicKeyLoader implements PublicKeyLoader {
return Collections.emptyMap();
}
- String kid = JWKBuilder.createKeyId(publicKey);
+ String kid = KeyUtils.createKeyId(publicKey);
return Collections.singletonMap(kid, publicKey);
} catch (Exception e) {
logger.warnf(e, "Unable to retrieve publicKey for verify signature of identityProvider '%s' . Error details: %s", config.getAlias(), e.getMessage());
diff --git a/services/src/main/java/org/keycloak/keys/RsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/RsaKeyProvider.java
new file mode 100644
index 0000000..27f3077
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/RsaKeyProvider.java
@@ -0,0 +1,56 @@
+/*
+ * 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.keys;
+
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.RealmModel;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RsaKeyProvider extends AbstractRsaKeyProvider {
+
+ public RsaKeyProvider(RealmModel realm, ComponentModel model) {
+ super(realm, model);
+ }
+
+ @Override
+ public Keys loadKeys(RealmModel realm, ComponentModel model) {
+ String privateRsaKeyPem = model.getConfig().getFirst(Attributes.PRIVATE_KEY_KEY);
+ String certificatePem = model.getConfig().getFirst(Attributes.CERTIFICATE_KEY);
+
+ PrivateKey privateKey = PemUtils.decodePrivateKey(privateRsaKeyPem);
+ PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
+
+ KeyPair keyPair = new KeyPair(publicKey, privateKey);
+ X509Certificate certificate = PemUtils.decodeCertificate(certificatePem);
+
+ String kid = KeyUtils.createKeyId(keyPair.getPublic());
+
+ return new Keys(kid, keyPair, certificate);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/keys/RsaKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/RsaKeyProviderFactory.java
new file mode 100644
index 0000000..f6814da
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/RsaKeyProviderFactory.java
@@ -0,0 +1,128 @@
+/*
+ * 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.keys;
+
+import org.keycloak.Config;
+import org.keycloak.common.util.CertificateUtils;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.ConfigurationValidationHelper;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RsaKeyProviderFactory extends AbstractRsaKeyProviderFactory {
+
+ public static final String ID = "rsa";
+
+ private static final String HELP_TEXT = "RSA key provider that can optionally generated a self-signed certificate";
+
+ private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder()
+ .property(Attributes.PRIVATE_KEY_PROPERTY)
+ .property(Attributes.CERTIFICATE_PROPERTY)
+ .build();
+
+ @Override
+ public KeyProvider create(KeycloakSession session, ComponentModel model) {
+ return new RsaKeyProvider(session.getContext().getRealm(), model);
+ }
+
+ @Override
+ public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
+ super.validateConfiguration(session, model);
+
+ ConfigurationValidationHelper.check(model)
+ .checkSingle(Attributes.PRIVATE_KEY_PROPERTY, true)
+ .checkSingle(Attributes.CERTIFICATE_PROPERTY, false);
+
+ KeyPair keyPair;
+ try {
+ PrivateKey privateKey = PemUtils.decodePrivateKey(model.get(Attributes.PRIVATE_KEY_KEY));
+ PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
+ keyPair = new KeyPair(publicKey, privateKey);
+ } catch (Throwable t) {
+ throw new ComponentValidationException("Failed to decode private key", t);
+ }
+
+ if (model.contains(Attributes.CERTIFICATE_KEY)) {
+ Certificate certificate = null;
+ try {
+ certificate = PemUtils.decodeCertificate(model.get(Attributes.CERTIFICATE_KEY));
+ } catch (Throwable t) {
+ throw new ComponentValidationException("Failed to decode certificate", t);
+ }
+
+ if (certificate == null) {
+ throw new ComponentValidationException("Failed to decode certificate");
+ }
+
+ if (!certificate.getPublicKey().equals(keyPair.getPublic())) {
+ throw new ComponentValidationException("Certificate does not match private key");
+ }
+ } else {
+ try {
+ RealmModel realm = session.realms().getRealm(model.getParentId());
+ Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
+ model.put(Attributes.CERTIFICATE_KEY, PemUtils.encodeCertificate(certificate));
+ } catch (Throwable t) {
+ throw new ComponentValidationException("Failed to generate self-signed certificate");
+ }
+ }
+ }
+
+ @Override
+ public String getHelpText() {
+ return HELP_TEXT;
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return CONFIG_PROPERTIES;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
index 8eed142..c1a101c 100755
--- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
+++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
@@ -118,7 +118,7 @@ public abstract class AuthorizationEndpointBase {
return processor.finishAuthentication(protocol);
} else {
try {
- RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
+ RestartLoginCookie.setRestartCookie(session, realm, clientConnection, uriInfo, clientSession);
if (redirectToAuthentication) {
return processor.redirectToFlow();
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java
index 0378765..f1132af 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java
@@ -24,10 +24,12 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.Urls;
import org.keycloak.util.JsonSerialization;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import java.security.PublicKey;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -46,13 +48,33 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
public Response introspect(String token) {
try {
- AccessToken toIntrospect = toAccessToken(token);
+ boolean valid = true;
+
+ RSATokenVerifier verifier = RSATokenVerifier.create(token)
+ .realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
+
+ PublicKey publicKey = session.keys().getPublicKey(realm, verifier.getHeader().getKeyId());
+ if (publicKey == null) {
+ valid = false;
+ } else {
+ try {
+ verifier.publicKey(publicKey);
+ verifier.verify();
+ } catch (VerificationException e) {
+ valid = false;
+ }
+ }
+
RealmModel realm = this.session.getContext().getRealm();
ObjectNode tokenMetadata;
- boolean active = tokenManager.isTokenValid(session, realm, toIntrospect);
+ AccessToken toIntrospect = verifier.getToken();
- if (active) {
+ if (valid) {
+ valid = tokenManager.isTokenValid(session, realm, toIntrospect);
+ }
+
+ if (valid) {
tokenMetadata = JsonSerialization.createObjectNode(toIntrospect);
tokenMetadata.put("client_id", toIntrospect.getIssuedFor());
tokenMetadata.put("username", toIntrospect.getPreferredUsername());
@@ -60,7 +82,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
tokenMetadata = JsonSerialization.createObjectNode();
}
- tokenMetadata.put("active", active);
+ tokenMetadata.put("active", valid);
return Response.ok(JsonSerialization.writeValueAsBytes(tokenMetadata)).type(MediaType.APPLICATION_JSON_TYPE).build();
} catch (Exception e) {
@@ -70,7 +92,13 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
protected AccessToken toAccessToken(String token) {
try {
- return RSATokenVerifier.toAccessToken(token, realm.getPublicKey());
+ RSATokenVerifier verifier = RSATokenVerifier.create(token)
+ .realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
+
+ PublicKey publicKey = session.keys().getPublicKey(realm, verifier.getHeader().getKeyId());
+ verifier.publicKey(publicKey);
+
+ return verifier.verify().getToken();
} catch (VerificationException e) {
throw new ErrorResponseException("invalid_request", "Invalid token.", Response.Status.UNAUTHORIZED);
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
index 1a47301..44822c6 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -116,7 +116,7 @@ public class LogoutEndpoint {
boolean error = false;
if (encodedIdToken != null) {
try {
- IDToken idToken = tokenManager.verifyIDToken(realm, encodedIdToken);
+ IDToken idToken = tokenManager.verifyIDToken(session, realm, encodedIdToken);
userSession = session.sessions().getUserSession(realm, idToken.getSessionState());
if (userSession == null) {
error = true;
@@ -187,7 +187,7 @@ public class LogoutEndpoint {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST);
}
try {
- RefreshToken token = tokenManager.verifyRefreshToken(realm, refreshToken, false);
+ RefreshToken token = tokenManager.verifyRefreshToken(session, realm, refreshToken, false);
UserSessionModel userSessionModel = session.sessions().getUserSession(realm, token.getSessionState());
if (userSessionModel != null) {
logout(userSessionModel);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 6d9df6c..9a7fe3e 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -68,9 +68,6 @@ import java.util.Map;
*/
public class TokenEndpoint {
- // Flag if code was already exchanged for token
- private static final String CODE_EXCHANGED = "CODE_EXCHANGED";
-
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
private MultivaluedMap<String, String> formParams;
private ClientModel client;
@@ -203,34 +200,29 @@ public class TokenEndpoint {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
}
- ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
- if (accessCode == null) {
+ ClientSessionCode.ParseResult parseResult = ClientSessionCode.parseResult(code, session, realm);
+ if (parseResult.isClientSessionNotFound() || parseResult.isIllegalHash()) {
String[] parts = code.split("\\.");
if (parts.length == 2) {
event.detail(Details.CODE_ID, parts[1]);
}
event.error(Errors.INVALID_CODE);
- throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code not found", Response.Status.BAD_REQUEST);
+ if (parseResult.getClientSession() != null) {
+ session.sessions().removeClientSession(realm, parseResult.getClientSession());
+ }
+ throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code not valid", Response.Status.BAD_REQUEST);
}
- ClientSessionModel clientSession = accessCode.getClientSession();
+ ClientSessionModel clientSession = parseResult.getClientSession();
event.detail(Details.CODE_ID, clientSession.getId());
- String codeExchanged = clientSession.getNote(CODE_EXCHANGED);
- if (codeExchanged != null && Boolean.parseBoolean(codeExchanged)) {
- session.sessions().removeClientSession(realm, clientSession);
-
- event.error(Errors.INVALID_CODE);
- throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code used already", Response.Status.BAD_REQUEST);
- }
-
- if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) {
+ if (!parseResult.getCode().isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) {
event.error(Errors.INVALID_CODE);
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST);
}
- accessCode.setAction(null);
- clientSession.setNote(CODE_EXCHANGED, "true");
+ parseResult.getCode().setAction(null);
+
UserSessionModel userSession = clientSession.getUserSession();
if (userSession == null) {
@@ -275,7 +267,7 @@ public class TokenEndpoint {
updateClientSession(clientSession);
updateUserSessionFromClientAuth(userSession);
- AccessToken token = tokenManager.createClientAccessToken(session, accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession);
+ AccessToken token = tokenManager.createClientAccessToken(session, parseResult.getCode().getRequestedRoles(), realm, client, user, userSession, clientSession);
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
.accessToken(token)
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java
index 739fd96..89094da 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java
@@ -129,7 +129,11 @@ public class UserInfoEndpoint {
AccessToken token = null;
try {
- token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), true, true);
+ RSATokenVerifier verifier = RSATokenVerifier.create(tokenString)
+ .realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
+ String kid = verifier.getHeader().getKeyId();
+ verifier.publicKey(session.keys().getPublicKey(realm, kid));
+ token = verifier.verify().getToken();
} catch (VerificationException e) {
event.error(Errors.INVALID_TOKEN);
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Token invalid: " + e.getMessage(), Response.Status.UNAUTHORIZED);
@@ -190,7 +194,7 @@ public class UserInfoEndpoint {
claims.put("aud", audience);
Algorithm signatureAlg = cfg.getUserInfoSignedResponseAlg();
- PrivateKey privateKey = realm.getPrivateKey();
+ PrivateKey privateKey = session.keys().getActiveKey(realm).getPrivateKey();
String signedUserInfo = new JWSBuilder()
.jsonContent(claims)
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
index 9e214df..a07ea70 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
@@ -20,10 +20,10 @@ package org.keycloak.protocol.oidc;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.events.EventBuilder;
-import org.keycloak.forms.login.LoginFormsProvider;
-import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder;
+import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.keys.KeyMetadata;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint;
@@ -31,6 +31,7 @@ import org.keycloak.protocol.oidc.endpoints.LoginStatusIframeEndpoint;
import org.keycloak.protocol.oidc.endpoints.LogoutEndpoint;
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
import org.keycloak.protocol.oidc.endpoints.UserInfoEndpoint;
+import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.resources.RealmsResource;
@@ -44,6 +45,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
+import java.util.List;
/**
* Resource class for the oauth/openid connect token service
@@ -174,8 +176,16 @@ public class OIDCLoginProtocolService {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public JSONWebKeySet certs() {
+ List<KeyMetadata> publicKeys = session.keys().getKeys(realm, false);
+ JWK[] keys = new JWK[publicKeys.size()];
+
+ int i = 0;
+ for (KeyMetadata k : publicKeys) {
+ keys[i++] = JWKBuilder.create().kid(k.getKid()).rs256(k.getPublicKey());
+ }
+
JSONWebKeySet keySet = new JSONWebKeySet();
- keySet.setKeys(new JWK[]{JWKBuilder.create().rs256(realm.getPublicKey())});
+ keySet.setKeys(keys);
return keySet;
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index e8b4e11..308c771 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -17,11 +17,11 @@
package org.keycloak.protocol.oidc;
-import org.keycloak.OAuth2Constants;
-import org.keycloak.OAuthErrorException;
+import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.ClientConnection;
-import org.keycloak.common.util.Time;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.OAuthErrorException;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
@@ -35,6 +35,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ProtocolMapperModel;
@@ -55,11 +56,11 @@ import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.services.ErrorResponseException;
-import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.util.TokenUtil;
+import org.keycloak.common.util.Time;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
@@ -80,7 +81,8 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public class TokenManager {
- protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+ private static final Logger logger = Logger.getLogger(TokenManager.class);
+ private static final String JWT = "JWT";
// Harcoded for now
Algorithm jwsAlgorithm = Algorithm.RS256;
@@ -213,7 +215,7 @@ public class TokenManager {
}
public RefreshResult refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event, HttpHeaders headers) throws OAuthErrorException {
- RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken);
+ RefreshToken refreshToken = verifyRefreshToken(session, realm, encodedRefreshToken);
event.user(refreshToken.getSubject()).session(refreshToken.getSessionState())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
@@ -248,13 +250,13 @@ public class TokenManager {
return new RefreshResult(res, TokenUtil.TOKEN_TYPE_OFFLINE.equals(refreshToken.getType()));
}
- public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken) throws OAuthErrorException {
- return verifyRefreshToken(realm, encodedRefreshToken, true);
+ public RefreshToken verifyRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken) throws OAuthErrorException {
+ return verifyRefreshToken(session, realm, encodedRefreshToken, true);
}
- public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken, boolean checkExpiration) throws OAuthErrorException {
+ public RefreshToken verifyRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken, boolean checkExpiration) throws OAuthErrorException {
try {
- RefreshToken refreshToken = toRefreshToken(realm, encodedRefreshToken);
+ RefreshToken refreshToken = toRefreshToken(session, realm, encodedRefreshToken);
if (checkExpiration) {
if (refreshToken.getExpiration() != 0 && refreshToken.isExpired()) {
@@ -272,21 +274,21 @@ public class TokenManager {
}
}
- public RefreshToken toRefreshToken(RealmModel realm, String encodedRefreshToken) throws JWSInputException, OAuthErrorException {
+ public RefreshToken toRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken) throws JWSInputException, OAuthErrorException {
JWSInput jws = new JWSInput(encodedRefreshToken);
- if (!RSAProvider.verify(jws, realm.getPublicKey())) {
+ if (!RSAProvider.verify(jws, session.keys().getPublicKey(realm, jws.getHeader().getKeyId()))) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
}
return jws.readJsonContent(RefreshToken.class);
}
- public IDToken verifyIDToken(RealmModel realm, String encodedIDToken) throws OAuthErrorException {
+ public IDToken verifyIDToken(KeycloakSession session, RealmModel realm, String encodedIDToken) throws OAuthErrorException {
try {
JWSInput jws = new JWSInput(encodedIDToken);
IDToken idToken;
- if (!RSAProvider.verify(jws, realm.getPublicKey())) {
+ if (!RSAProvider.verify(jws, session.keys().getPublicKey(realm, jws.getHeader().getKeyId()))) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken");
}
idToken = jws.readJsonContent(IDToken.class);
@@ -499,7 +501,7 @@ public class TokenManager {
public AccessToken transformAccessToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user,
UserSessionModel userSession, ClientSessionModel clientSession) {
- Set<ProtocolMapperModel> mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers();
+ Set<ProtocolMapperModel> mappings = new ClientSessionCode(session, realm, clientSession).getRequestedProtocolMappers();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
for (ProtocolMapperModel mapping : mappings) {
@@ -513,7 +515,7 @@ public class TokenManager {
public AccessToken transformUserInfoAccessToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user,
UserSessionModel userSession, ClientSessionModel clientSession) {
- Set<ProtocolMapperModel> mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers();
+ Set<ProtocolMapperModel> mappings = new ClientSessionCode(session, realm, clientSession).getRequestedProtocolMappers();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
for (ProtocolMapperModel mapping : mappings) {
@@ -533,7 +535,7 @@ public class TokenManager {
public void transformIDToken(KeycloakSession session, IDToken token, RealmModel realm, ClientModel client, UserModel user,
UserSessionModel userSession, ClientSessionModel clientSession) {
- Set<ProtocolMapperModel> mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers();
+ Set<ProtocolMapperModel> mappings = new ClientSessionCode(session, realm, clientSession).getRequestedProtocolMappers();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
for (ProtocolMapperModel mapping : mappings) {
@@ -618,13 +620,9 @@ public class TokenManager {
}
- public String encodeToken(RealmModel realm, Object token) {
- String encodedToken = new JWSBuilder()
- .type(OAuth2Constants.JWT)
- .kid(realm.getKeyId())
- .jsonContent(token)
- .sign(jwsAlgorithm, realm.getPrivateKey());
- return encodedToken;
+ public String encodeToken(KeycloakSession session, RealmModel realm, Object token) {
+ KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
+ return new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(token).sign(jwsAlgorithm, activeKey.getPrivateKey());
}
public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client, EventBuilder event, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
@@ -731,6 +729,8 @@ public class TokenManager {
public AccessTokenResponse build() {
+ KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
+
if (accessToken != null) {
event.detail(Details.TOKEN_ID, accessToken.getId());
}
@@ -746,7 +746,7 @@ public class TokenManager {
AccessTokenResponse res = new AccessTokenResponse();
if (accessToken != null) {
- String encodedToken = new JWSBuilder().type(OAuth2Constants.JWT).kid(realm.getKeyId()).jsonContent(accessToken).sign(jwsAlgorithm, realm.getPrivateKey());
+ String encodedToken = new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(accessToken).sign(jwsAlgorithm, activeKey.getPrivateKey());
res.setToken(encodedToken);
res.setTokenType("bearer");
res.setSessionState(accessToken.getSessionState());
@@ -764,11 +764,11 @@ public class TokenManager {
}
if (idToken != null) {
- String encodedToken = new JWSBuilder().type(OAuth2Constants.JWT).kid(realm.getKeyId()).jsonContent(idToken).sign(jwsAlgorithm, realm.getPrivateKey());
+ String encodedToken = new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(idToken).sign(jwsAlgorithm, activeKey.getPrivateKey());
res.setIdToken(encodedToken);
}
if (refreshToken != null) {
- String encodedToken = new JWSBuilder().type(OAuth2Constants.JWT).kid(realm.getKeyId()).jsonContent(refreshToken).sign(jwsAlgorithm, realm.getPrivateKey());
+ String encodedToken = new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(refreshToken).sign(jwsAlgorithm, activeKey.getPrivateKey());
res.setRefreshToken(encodedToken);
if (refreshToken.getExpiration() != 0) {
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());
diff --git a/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
index fd32f2b..cc7c324 100644
--- a/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
+++ b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
@@ -22,8 +22,10 @@ import org.keycloak.common.ClientConnection;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.HMACProvider;
+import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.ServicesLogger;
@@ -33,6 +35,7 @@ import org.keycloak.services.util.CookieHelper;
import javax.crypto.SecretKey;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.UriInfo;
+import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
@@ -112,11 +115,12 @@ public class RestartLoginCookie {
this.action = action;
}
- public String encode(RealmModel realm) {
+ public String encode(KeycloakSession session, RealmModel realm) {
+ KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+
JWSBuilder builder = new JWSBuilder();
- return builder.jsonContent(this)
- .hmac256((SecretKey)realm.getCodeSecretKey());
- //.rsa256(realm.getPrivateKey());
+ return builder.kid(keys.getKid()).jsonContent(this)
+ .rsa256(keys.getPrivateKey());
}
@@ -133,11 +137,9 @@ public class RestartLoginCookie {
}
}
- public static void setRestartCookie(RealmModel realm, ClientConnection connection, UriInfo uriInfo, ClientSessionModel clientSession) {
+ public static void setRestartCookie(KeycloakSession session, RealmModel realm, ClientConnection connection, UriInfo uriInfo, ClientSessionModel clientSession) {
RestartLoginCookie restart = new RestartLoginCookie(clientSession);
- String encoded = restart.encode(realm);
- int keySize = realm.getCodeSecret().length();
- int size = encoded.length();
+ String encoded = restart.encode(session, realm);
String path = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
boolean secureOnly = realm.getSslRequired().isRequired(connection);
CookieHelper.addCookie(KC_RESTART, encoded, path, null, null, -1, secureOnly, true);
@@ -157,13 +159,8 @@ public class RestartLoginCookie {
}
String encodedCookie = cook.getValue();
JWSInput input = new JWSInput(encodedCookie);
- /*
- if (!RSAProvider.verify(input, realm.getPublicKey())) {
- logger.debug("Failed to verify encoded RestartLoginCookie");
- return null;
- }
- */
- if (!HMACProvider.verify(input, (SecretKey)realm.getCodeSecretKey())) {
+ PublicKey publicKey = session.keys().getPublicKey(realm, input.getHeader().getKeyId());
+ if (!RSAProvider.verify(input, publicKey)) {
logger.debug("Failed to verify encoded RestartLoginCookie");
return null;
}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
index 4a91db9..2175b32 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
@@ -18,6 +18,7 @@
package org.keycloak.protocol.saml.installation;
import org.keycloak.Config;
+import org.keycloak.common.util.PemUtils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -43,12 +44,12 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
SamlClient samlClient = new SamlClient(client);
StringBuffer buffer = new StringBuffer();
buffer.append("<keycloak-saml-adapter>\n");
- baseXml(realm, client, baseUri, samlClient, buffer);
+ baseXml(session, realm, client, baseUri, samlClient, buffer);
buffer.append("</keycloak-saml-adapter>\n");
return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
}
- public static void baseXml(RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuffer buffer) {
+ public static void baseXml(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuffer buffer) {
buffer.append(" <SP entityID=\"").append(client.getClientId()).append("\"\n");
buffer.append(" sslPolicy=\"").append(realm.getSslRequired().name()).append("\"\n");
buffer.append(" logoutPage=\"SPECIFY YOUR LOGOUT PAGE!\">\n");
@@ -116,7 +117,7 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
buffer.append(" <Keys>\n");
buffer.append(" <Key signing=\"true\">\n");
buffer.append(" <CertificatePem>\n");
- buffer.append(" ").append(realm.getCertificatePem()).append("\n");
+ buffer.append(" ").append(PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate())).append("\n");
buffer.append(" </CertificatePem>\n");
buffer.append(" </Key>\n");
buffer.append(" </Keys>\n");
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
index 1310ea4..ea77d47 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
@@ -41,7 +41,7 @@ public class KeycloakSamlSubsystemInstallation implements ClientInstallationProv
SamlClient samlClient = new SamlClient(client);
StringBuffer buffer = new StringBuffer();
buffer.append("<secure-deployment name=\"YOUR-WAR.war\">\n");
- KeycloakSamlClientInstallation.baseXml(realm, client, baseUri, samlClient, buffer);
+ KeycloakSamlClientInstallation.baseXml(session, realm, client, baseUri, samlClient, buffer);
buffer.append("</secure-deployment>\n");
return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java
index 1d8ca2f..39bfda0 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java
@@ -43,7 +43,7 @@ public class ModAuthMellonClientInstallation implements ClientInstallationProvid
SamlClient samlClient = new SamlClient(client);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(baos);
- String idpDescriptor = SamlIDPDescriptorClientInstallation.getIDPDescriptorForClient(realm, client, serverBaseUri);
+ String idpDescriptor = SamlIDPDescriptorClientInstallation.getIDPDescriptorForClient(session, realm, client, serverBaseUri);
String spDescriptor = SamlSPDescriptorClientInstallation.getSPDescriptorForClient(client);
String clientDirName = client.getClientId()
.replace('/', '_')
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
index 5d155d2..4b84363 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
@@ -18,6 +18,7 @@
package org.keycloak.protocol.saml.installation;
import org.keycloak.Config;
+import org.keycloak.common.util.PemUtils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -37,7 +38,7 @@ import java.net.URI;
* @version $Revision: 1 $
*/
public class SamlIDPDescriptorClientInstallation implements ClientInstallationProvider {
- public static String getIDPDescriptorForClient(RealmModel realm, ClientModel client, URI serverBaseUri) {
+ public static String getIDPDescriptorForClient(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
SamlClient samlClient = new SamlClient(client);
String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString();
String idp = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
@@ -75,7 +76,7 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
" <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
" <dsig:X509Data>\n" +
" <dsig:X509Certificate>\n" +
- " " + realm.getCertificatePem() + "\n" +
+ " " + PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()) + "\n" +
" </dsig:X509Certificate>\n" +
" </dsig:X509Data>\n" +
" </dsig:KeyInfo>\n" +
@@ -87,7 +88,7 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
@Override
public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
- String descriptor = getIDPDescriptorForClient(realm, client, serverBaseUri);
+ String descriptor = getIDPDescriptorForClient(session, realm, client, serverBaseUri);
return Response.ok(descriptor, MediaType.TEXT_PLAIN_TYPE).build();
}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java
index 0f9fdeb..82d26d3 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java
@@ -115,7 +115,7 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
List<SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper>> roleNameMappers = new LinkedList<>();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
AttributeType singleAttributeType = null;
- Set<ProtocolMapperModel> requestedProtocolMappers = new ClientSessionCode(clientSession.getRealm(), clientSession).getRequestedProtocolMappers();
+ Set<ProtocolMapperModel> requestedProtocolMappers = new ClientSessionCode(session, clientSession.getRealm(), clientSession).getRequestedProtocolMappers();
for (ProtocolMapperModel mapping : requestedProtocolMappers) {
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 9751c6b..14726d3 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -32,6 +32,7 @@ import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
@@ -393,19 +394,22 @@ public class SamlProtocol implements LoginProtocol {
JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder();
bindingBuilder.relayState(relayState);
+ KeyManager keyManager = session.keys();
+ KeyManager.ActiveKey keys = keyManager.getActiveKey(realm);
+
if (samlClient.requiresRealmSignature()) {
String canonicalization = samlClient.getCanonicalizationMethod();
if (canonicalization != null) {
bindingBuilder.canonicalizationMethod(canonicalization);
}
- bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
+ bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
}
if (samlClient.requiresAssertionSignature()) {
String canonicalization = samlClient.getCanonicalizationMethod();
if (canonicalization != null) {
bindingBuilder.canonicalizationMethod(canonicalization);
}
- bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signAssertions();
+ bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signAssertions();
}
if (samlClient.requiresEncryption()) {
PublicKey publicKey = null;
@@ -536,7 +540,8 @@ public class SamlProtocol implements LoginProtocol {
if (canonicalization != null) {
binding.canonicalizationMethod(canonicalization);
}
- binding.signatureAlgorithm(algorithm).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
+ KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+ binding.signatureAlgorithm(algorithm).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
}
try {
@@ -633,7 +638,8 @@ public class SamlProtocol implements LoginProtocol {
private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
if (samlClient.requiresRealmSignature()) {
- binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
+ KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+ binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
}
return binding;
}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
index cff68ae..29e809d 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.common.VerificationException;
+import org.keycloak.common.util.PemUtils;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
@@ -34,6 +35,8 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeyManager;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.AuthorizationEndpointBase;
@@ -59,6 +62,7 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@@ -77,6 +81,9 @@ public class SamlService extends AuthorizationEndpointBase {
protected static final Logger logger = Logger.getLogger(SamlService.class);
+ @Context
+ protected KeycloakSession session;
+
public SamlService(RealmModel realm, EventBuilder event) {
super(realm, event);
}
@@ -374,7 +381,8 @@ public class SamlService extends AuthorizationEndpointBase {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState);
if (samlClient.requiresRealmSignature()) {
SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm();
- binding.signatureAlgorithm(algorithm).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
+ KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+ binding.signatureAlgorithm(algorithm).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
}
try {
@@ -508,18 +516,18 @@ public class SamlService extends AuthorizationEndpointBase {
@Produces(MediaType.APPLICATION_XML)
@NoCache
public String getDescriptor() throws Exception {
- return getIDPMetadataDescriptor(uriInfo, realm);
+ return getIDPMetadataDescriptor(uriInfo, session, realm);
}
- public static String getIDPMetadataDescriptor(UriInfo uriInfo, RealmModel realm) throws IOException {
+ public static String getIDPMetadataDescriptor(UriInfo uriInfo, KeycloakSession session, RealmModel realm) throws IOException {
InputStream is = SamlService.class.getResourceAsStream("/idp-metadata-template.xml");
String template = StreamUtil.readString(is);
template = template.replace("${idp.entityID}", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
template = template.replace("${idp.sso.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
template = template.replace("${idp.sso.HTTP-Redirect}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
template = template.replace("${idp.sls.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
- template = template.replace("${idp.signing.certificate}", realm.getCertificatePem());
+ template = template.replace("${idp.signing.certificate}", PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()));
return template;
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
index 93c2648..2f4a19b 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
@@ -79,7 +79,7 @@ public class ClientRegistrationAuth {
return;
}
- ClientRegistrationTokenUtils.TokenVerification tokenVerification = ClientRegistrationTokenUtils.verifyToken(realm, uri, split[1]);
+ ClientRegistrationTokenUtils.TokenVerification tokenVerification = ClientRegistrationTokenUtils.verifyToken(session, realm, uri, split[1]);
if (tokenVerification.getError() != null) {
throw unauthorized(tokenVerification.getError().getMessage());
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
index 2f33e5d..8df24f4 100755
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
@@ -24,6 +24,7 @@ import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientInitialAccessModel;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -32,6 +33,7 @@ import org.keycloak.services.Urls;
import org.keycloak.util.TokenUtil;
import javax.ws.rs.core.UriInfo;
+import java.security.PublicKey;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -42,21 +44,21 @@ public class ClientRegistrationTokenUtils {
public static final String TYPE_REGISTRATION_ACCESS_TOKEN = "RegistrationAccessToken";
public static String updateRegistrationAccessToken(KeycloakSession session, ClientModel client) {
- return updateRegistrationAccessToken(session.getContext().getRealm(), session.getContext().getUri(), client);
+ return updateRegistrationAccessToken(session, session.getContext().getRealm(), session.getContext().getUri(), client);
}
- public static String updateRegistrationAccessToken(RealmModel realm, UriInfo uri, ClientModel client) {
+ public static String updateRegistrationAccessToken(KeycloakSession session, RealmModel realm, UriInfo uri, ClientModel client) {
String id = KeycloakModelUtils.generateId();
client.setRegistrationToken(id);
- String token = createToken(realm, uri, id, TYPE_REGISTRATION_ACCESS_TOKEN, 0);
+ String token = createToken(session, realm, uri, id, TYPE_REGISTRATION_ACCESS_TOKEN, 0);
return token;
}
- public static String createInitialAccessToken(RealmModel realm, UriInfo uri, ClientInitialAccessModel model) {
- return createToken(realm, uri, model.getId(), TYPE_INITIAL_ACCESS_TOKEN, model.getExpiration() > 0 ? model.getTimestamp() + model.getExpiration() : 0);
+ public static String createInitialAccessToken(KeycloakSession session, RealmModel realm, UriInfo uri, ClientInitialAccessModel model) {
+ return createToken(session, realm, uri, model.getId(), TYPE_INITIAL_ACCESS_TOKEN, model.getExpiration() > 0 ? model.getTimestamp() + model.getExpiration() : 0);
}
- public static TokenVerification verifyToken(RealmModel realm, UriInfo uri, String token) {
+ public static TokenVerification verifyToken(KeycloakSession session, RealmModel realm, UriInfo uri, String token) {
if (token == null) {
return TokenVerification.error(new RuntimeException("Missing token"));
}
@@ -68,7 +70,9 @@ public class ClientRegistrationTokenUtils {
return TokenVerification.error(new RuntimeException("Invalid token", e));
}
- if (!RSAProvider.verify(input, realm.getPublicKey())) {
+ PublicKey publicKey = session.keys().getPublicKey(realm, input.getHeader().getKeyId());
+
+ if (!RSAProvider.verify(input, publicKey)) {
return TokenVerification.error(new RuntimeException("Failed verify token"));
}
@@ -96,7 +100,7 @@ public class ClientRegistrationTokenUtils {
return TokenVerification.success(jwt);
}
- private static String createToken(RealmModel realm, UriInfo uri, String id, String type, int expiration) {
+ private static String createToken(KeycloakSession session, RealmModel realm, UriInfo uri, String id, String type, int expiration) {
JsonWebToken jwt = new JsonWebToken();
String issuer = getIssuer(realm, uri);
@@ -108,7 +112,9 @@ public class ClientRegistrationTokenUtils {
jwt.issuer(issuer);
jwt.audience(issuer);
- String token = new JWSBuilder().jsonContent(jwt).rsa256(realm.getPrivateKey());
+ KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+
+ String token = new JWSBuilder().kid(keys.getKid()).jsonContent(jwt).rsa256(keys.getPrivateKey());
return token;
}
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 0b971d2..935421c 100644
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -17,10 +17,12 @@
package org.keycloak.services;
import org.keycloak.credential.UserCredentialStoreManager;
+import org.keycloak.keys.DefaultKeyManager;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransactionManager;
+import org.keycloak.models.KeyManager;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.UserCredentialManager;
import org.keycloak.models.UserFederationManager;
@@ -60,6 +62,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
private UserFederationManager federationManager;
private UserFederatedStorageProvider userFederatedStorageProvider;
private KeycloakContext context;
+ private KeyManager keyManager;
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
this.factory = factory;
@@ -221,6 +224,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
return sessionProvider;
}
+ @Override
+ public KeyManager keys() {
+ if (keyManager == null) {
+ keyManager = new DefaultKeyManager(this);
+ }
+ return keyManager;
+ }
+
public void close() {
for (Provider p : providers.values()) {
try {
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
index bd1eabd..89cf129 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -26,6 +26,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.DefaultKeyProviders;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.ServicesLogger;
@@ -83,7 +84,9 @@ public class ApplianceBootstrap {
realm.setSslRequired(SslRequired.EXTERNAL);
realm.setRegistrationAllowed(false);
realm.setRegistrationEmailAsUsername(false);
- KeycloakModelUtils.generateRealmKeys(realm);
+
+ session.getContext().setRealm(realm);
+ DefaultKeyProviders.createProviders(realm);
return true;
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 2177a9b..f6d2c68 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.services.managers;
+import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.RSATokenVerifier;
@@ -35,6 +36,7 @@ import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
@@ -63,6 +65,7 @@ import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
+import java.security.PublicKey;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -81,7 +84,8 @@ public class AuthenticationManager {
// clientSession note with flag that clientSession was authenticated through SSO cookie
public static final String SSO_AUTH = "SSO_AUTH";
- protected static ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+ protected static final Logger logger = Logger.getLogger(AuthenticationManager.class);
+
public static final String FORM_USERNAME = "username";
// used for auth login
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
@@ -107,7 +111,13 @@ public class AuthenticationManager {
Cookie cookie = headers.getCookies().get(KEYCLOAK_IDENTITY_COOKIE);
if (cookie == null) return;
String tokenString = cookie.getValue();
- AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), false, false);
+
+ RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(false).checkTokenType(false);
+
+ String kid = verifier.getHeader().getKeyId();
+ PublicKey publicKey = session.keys().getPublicKey(realm, kid);
+
+ AccessToken token = verifier.publicKey(publicKey).verify().getToken();
UserSessionModel cookieSession = session.sessions().getUserSession(realm, token.getSessionState());
if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return;
expireIdentityCookie(realm, uriInfo, connection);
@@ -214,7 +224,7 @@ public class AuthenticationManager {
protocol.backchannelLogout(userSession, clientSession);
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
} catch (Exception e) {
- logger.failedToLogoutClient(e);
+ ServicesLogger.ROOT_LOGGER.failedToLogoutClient(e);
}
}
}
@@ -235,7 +245,7 @@ public class AuthenticationManager {
return response;
}
} catch (Exception e) {
- logger.failedToLogoutClient(e);
+ ServicesLogger.ROOT_LOGGER.failedToLogoutClient(e);
}
}
@@ -284,7 +294,7 @@ public class AuthenticationManager {
String cookiePath = getIdentityCookiePath(realm, uriInfo);
String issuer = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName());
AccessToken identityToken = createIdentityToken(realm, user, session, issuer);
- String encoded = encodeToken(realm, identityToken);
+ String encoded = encodeToken(keycloakSession, realm, identityToken);
boolean secureOnly = realm.getSslRequired().isRequired(connection);
int maxAge = NewCookie.DEFAULT_MAX_AGE;
if (session.isRememberMe()) {
@@ -326,10 +336,15 @@ public class AuthenticationManager {
return null;
}
- protected static String encodeToken(RealmModel realm, Object token) {
+ protected static String encodeToken(KeycloakSession session, RealmModel realm, Object token) {
+ KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
+
+ logger.tracef("Encoding token with kid '%s'", activeKey.getKid());
+
String encodedToken = new JWSBuilder()
+ .kid(activeKey.getKid())
.jsonContent(token)
- .rsa256(realm.getPrivateKey());
+ .rsa256(activeKey.getPrivateKey());
return encodedToken;
}
@@ -430,7 +445,7 @@ public class AuthenticationManager {
userSession.setNote(AUTH_TIME, String.valueOf(authTime));
}
- return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
+ return protocol.authenticated(userSession, new ClientSessionCode(session, realm, clientSession));
}
@@ -479,7 +494,7 @@ public class AuthenticationManager {
UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user, client.getId());
- ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
+ ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
for (RoleModel r : accessCode.getRequestedRoles()) {
// Consent already granted by user
@@ -535,7 +550,7 @@ public class AuthenticationManager {
List<RoleModel> realmRoles = new LinkedList<>();
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<>();
- ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
+ ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
for (RoleModel r : accessCode.getRequestedRoles()) {
// Consent already granted by user
@@ -664,13 +679,21 @@ public class AuthenticationManager {
protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType,
String tokenString, HttpHeaders headers) {
try {
- AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), checkActive, checkTokenType);
+ RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(checkActive).checkTokenType(checkTokenType);
+ String kid = verifier.getHeader().getKeyId();
+
+ PublicKey publicKey = session.keys().getPublicKey(realm, kid);
+ if (publicKey == null) {
+ logger.debugf("Identity cookie signed with unknown kid '%s'", kid);
+ return null;
+ }
+ verifier.publicKey(publicKey);
+
+ AccessToken token = verifier.verify().getToken();
if (checkActive) {
if (!token.isActive() || token.getIssuedAt() < realm.getNotBefore()) {
- logger.debug("identity cookie expired");
+ logger.debug("Identity cookie expired");
return null;
- } else {
- logger.debugv("token active - active: {0}, issued-at: {1}, not-before: {2}", token.isActive(), token.getIssuedAt(), realm.getNotBefore());
}
}
@@ -689,7 +712,7 @@ public class AuthenticationManager {
return new AuthResult(user, userSession, token);
} catch (VerificationException e) {
- logger.debug("Failed to verify identity token", e);
+ logger.debugf("Failed to verify identity token: %s", e.getMessage());
}
return null;
}
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index 2e21465..488df50 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -244,7 +244,7 @@ public class ResourceAdminManager {
protected boolean sendLogoutRequest(RealmModel realm, ClientModel resource, List<String> adapterSessionIds, List<String> userSessions, int notBefore, String managementUrl) {
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), adapterSessionIds, notBefore, userSessions);
- String token = new TokenManager().encodeToken(realm, adminAction);
+ String token = new TokenManager().encodeToken(session, realm, adminAction);
if (logger.isDebugEnabled()) logger.debugv("logout resource {0} url: {1} sessionIds: " + adapterSessionIds, resource.getClientId(), managementUrl);
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build();
try {
@@ -295,7 +295,7 @@ public class ResourceAdminManager {
protected boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) {
PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), notBefore);
- String token = new TokenManager().encodeToken(realm, adminAction);
+ String token = new TokenManager().encodeToken(session, realm, adminAction);
logger.debugv("pushRevocation resource: {0} url: {1}", resource.getClientId(), managementUrl);
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build();
try {
@@ -333,7 +333,7 @@ public class ResourceAdminManager {
protected boolean sendTestNodeAvailabilityRequest(RealmModel realm, ClientModel client, String managementUrl) {
TestAvailabilityAction adminAction = new TestAvailabilityAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, client.getClientId());
- String token = new TokenManager().encodeToken(realm, adminAction);
+ String token = new TokenManager().encodeToken(session, realm, adminAction);
logger.debugv("testNodes availability resource: {0} url: {1}", client.getClientId(), managementUrl);
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_TEST_AVAILABLE).build();
try {
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 4c8b2c7..e95f580 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -714,7 +714,7 @@ public class AccountService extends AbstractSecuredLocalService {
try {
ClientSessionModel clientSession = auth.getClientSession();
- ClientSessionCode clientSessionCode = new ClientSessionCode(realm, clientSession);
+ ClientSessionCode clientSessionCode = new ClientSessionCode(session, realm, clientSession);
clientSessionCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
clientSession.setRedirectUri(redirectUri);
clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
index 0649b25..87d9091 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
@@ -125,6 +125,7 @@ public class AdminRoot {
if (realm == null) {
throw new NotFoundException("Realm not found. Did you type in a bad URL?");
}
+ session.getContext().setRealm(realm);
return realm;
}
@@ -170,6 +171,7 @@ public class AdminRoot {
if (realm == null) {
throw new UnauthorizedException("Unknown realm in token");
}
+ session.getContext().setRealm(realm);
AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, headers);
if (authResult == null) {
logger.debug("Token not valid");
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
index 4e1d7a4..20d6731 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
@@ -946,6 +946,7 @@ public class AuthenticationManagementResource {
propRep.setType(prop.getType());
propRep.setDefaultValue(prop.getDefaultValue());
propRep.setHelpText(prop.getHelpText());
+ propRep.setSecret(prop.isSecret());
return propRep;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
index aa1f089..09582a5 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
@@ -30,6 +30,7 @@ import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKParser;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -372,11 +373,9 @@ public class ClientAttributeCertificateResource {
if (config.isRealmCertificate() == null || config.isRealmCertificate().booleanValue()) {
- X509Certificate certificate = realm.getCertificate();
- if (certificate == null) {
- KeycloakModelUtils.generateRealmCertificate(realm);
- certificate = realm.getCertificate();
- }
+ KeyManager keys = session.keys();
+ String kid = keys.getActiveKey(realm).getKid();
+ Certificate certificate = keys.getCertificate(realm, kid);
String certificateAlias = config.getRealmAlias();
if (certificateAlias == null) certificateAlias = realm.getName();
keyStore.setCertificateEntry(certificateAlias, certificate);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
index 532bffc..1bc6411 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
@@ -84,13 +84,9 @@ public class ClientInitialAccessResource {
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientInitialAccessModel.getId()).representation(config).success();
- if (session.getTransactionManager().isActive()) {
- session.getTransactionManager().commit();
- }
-
ClientInitialAccessPresentation rep = wrap(clientInitialAccessModel);
- String token = ClientRegistrationTokenUtils.createInitialAccessToken(realm, uriInfo, clientInitialAccessModel);
+ String token = ClientRegistrationTokenUtils.createInitialAccessToken(session, realm, uriInfo, clientInitialAccessModel);
rep.setToken(token);
response.setStatus(Response.Status.CREATED.getStatusCode());
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 1352431..6487725 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -274,7 +274,7 @@ public class ClientResource {
throw new NotFoundException("Could not find client");
}
- String token = ClientRegistrationTokenUtils.updateRegistrationAccessToken(realm, uriInfo, client);
+ String token = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, realm, uriInfo, client);
ClientRepresentation rep = ModelToRepresentation.toRepresentation(client);
rep.setRegistrationAccessToken(token);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java
index f3e099a..dcd40a6 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java
@@ -19,12 +19,14 @@ package org.keycloak.services.resources.admin;
import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.common.ClientConnection;
import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ServicesLogger;
import javax.ws.rs.Consumes;
@@ -93,7 +95,7 @@ public class ComponentResource {
}
List<ComponentRepresentation> reps = new LinkedList<>();
for (ComponentModel component : components) {
- ComponentRepresentation rep = ModelToRepresentation.toRepresentation(component);
+ ComponentRepresentation rep = ModelToRepresentation.toRepresentation(session, component, false);
reps.add(rep);
}
return reps;
@@ -103,27 +105,28 @@ public class ComponentResource {
@Consumes(MediaType.APPLICATION_JSON)
public Response create(ComponentRepresentation rep) {
auth.requireManage();
- ComponentModel model = RepresentationToModel.toModel(rep);
- if (model.getParentId() == null) model.setParentId(realm.getId());
- adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(rep).success();
-
-
-
- model = realm.addComponentModel(model);
- return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
+ try {
+ ComponentModel model = RepresentationToModel.toModel(session, rep);
+ if (model.getParentId() == null) model.setParentId(realm.getId());
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(rep).success();
+
+ model = realm.addComponentModel(model);
+ return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
+ } catch (ComponentValidationException e) {
+ return ErrorResponse.error(e.getMessage(), Response.Status.BAD_REQUEST);
+ }
}
@GET
@Path("{id}")
+ @Produces(MediaType.APPLICATION_JSON)
public ComponentRepresentation getComponent(@PathParam("id") String id) {
auth.requireManage();
ComponentModel model = realm.getComponent(id);
if (model == null) {
throw new NotFoundException("Could not find component");
}
- return ModelToRepresentation.toRepresentation(model);
-
-
+ return ModelToRepresentation.toRepresentation(session, model, false);
}
@PUT
@@ -135,8 +138,7 @@ public class ComponentResource {
if (model == null) {
throw new NotFoundException("Could not find component");
}
- model = RepresentationToModel.toModel(rep);
- model.setId(id);
+ RepresentationToModel.updateComponent(session, rep, model, false);
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo, model.getId()).representation(rep).success();
realm.updateComponent(model);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
index 4c40991..d3b89d0 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
@@ -267,6 +267,7 @@ public class IdentityProviderResource {
propRep.setType(prop.getType());
propRep.setDefaultValue(prop.getDefaultValue());
propRep.setHelpText(prop.getHelpText());
+ propRep.setSecret(prop.isSecret());
rep.getProperties().add(propRep);
}
types.put(rep.getId(), rep);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java b/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java
new file mode 100644
index 0000000..e58f102
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java
@@ -0,0 +1,79 @@
+/*
+ * 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.services.resources.admin;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.keys.KeyMetadata;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeyManager;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.KeysMetadataRepresentation;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class KeyResource {
+
+ private RealmModel realm;
+ private KeycloakSession session;
+ private RealmAuth auth;
+
+ public KeyResource(RealmModel realm, KeycloakSession session, RealmAuth auth) {
+ this.realm = realm;
+ this.session = session;
+ this.auth = auth;
+ }
+
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public KeysMetadataRepresentation getKeyMetadata() {
+ auth.requireView();
+
+ KeyManager keystore = session.keys();
+
+ KeysMetadataRepresentation keys = new KeysMetadataRepresentation();
+ keys.setActive(Collections.singletonMap(KeyMetadata.Type.RSA.name(), keystore.getActiveKey(realm).getKid()));
+
+ List<KeysMetadataRepresentation.KeyMetadataRepresentation> l = new LinkedList<>();
+ for (KeyMetadata m : session.keys().getKeys(realm, true)) {
+ KeysMetadataRepresentation.KeyMetadataRepresentation r = new KeysMetadataRepresentation.KeyMetadataRepresentation();
+ r.setProviderId(m.getProviderId());
+ r.setProviderPriority(m.getProviderPriority());
+ r.setKid(m.getKid());
+ r.setStatus(m.getStatus() != null ? m.getStatus().name() : null);
+ r.setType(m.getType() != null ? m.getType().name() : null);
+ r.setPublicKey(PemUtils.encodeKey(m.getPublicKey()));
+ r.setCertificate(PemUtils.encodeCertificate(m.getCertificate()));
+ l.add(r);
+ }
+
+ keys.setKeys(l);
+
+ return keys;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 29c4cbf..47e0217 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -874,4 +874,11 @@ public class RealmAdminResource {
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
}
+ @Path("keys")
+ public KeyResource keys() {
+ KeyResource resource = new KeyResource(realm, session, this.auth);
+ ResteasyProviderFactory.getInstance().injectProperties(resource);
+ return resource;
+ }
+
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
index 04849b5..71abed5 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
@@ -236,6 +236,7 @@ public class UserFederationProviderResource {
propRep.setType(prop.getType());
propRep.setDefaultValue(prop.getDefaultValue());
propRep.setHelpText(prop.getHelpText());
+ propRep.setSecret(prop.isSecret());
rep.getProperties().add(propRep);
}
rep.setDefaultConfig(mapperFactory.getDefaultConfig(this.federationProviderModel));
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java
index 36b0b69..7b4ccef 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java
@@ -256,7 +256,7 @@ public class UserFederationProvidersResource {
propRep.setType(prop.getType());
propRep.setDefaultValue(prop.getDefaultValue());
propRep.setHelpText(prop.getHelpText());
-
+ propRep.setSecret(prop.isSecret());
return propRep;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index e6d53bc..2b06c4c 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -839,7 +839,7 @@ public class UsersResource {
for (String action : actions) {
clientSession.addRequiredAction(action);
}
- ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
+ ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
accessCode.setAction(ClientSessionModel.Action.EXECUTE_ACTIONS.name());
try {
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 9f7e4bf..5464384 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -320,7 +320,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
ctx.saveToClientSession(clientSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo)
- .queryParam(OAuth2Constants.CODE, context.getCode())
+ .queryParam(OAuth2Constants.CODE, clientCode.getCode())
.build(realmModel.getName());
return Response.status(302).location(redirect).build();
@@ -333,7 +333,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
updateFederatedIdentity(context, federatedUser);
clientSession.setAuthenticatedUser(federatedUser);
- return finishOrRedirectToPostBrokerLogin(clientSession, context, false);
+ return finishOrRedirectToPostBrokerLogin(clientSession, context, false, parsedCode.clientSessionCode);
}
}
@@ -359,7 +359,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
if (parsedCode.response != null) {
return parsedCode.response;
}
- ClientSessionModel clientSession = parsedCode.clientSessionCode.getClientSession();
+ return afterFirstBrokerLogin(parsedCode.clientSessionCode);
+ }
+
+ private Response afterFirstBrokerLogin(ClientSessionCode clientSessionCode) {
+ ClientSessionModel clientSession = clientSessionCode.getClientSession();
try {
this.event.detail(Details.CODE_ID, clientSession.getId())
@@ -435,7 +439,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
updateFederatedIdentity(context, federatedUser);
}
- return finishOrRedirectToPostBrokerLogin(clientSession, context, true);
+ return finishOrRedirectToPostBrokerLogin(clientSession, context, true, clientSessionCode);
} catch (Exception e) {
return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
@@ -443,12 +447,12 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
- private Response finishOrRedirectToPostBrokerLogin(ClientSessionModel clientSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin) {
+ private Response finishOrRedirectToPostBrokerLogin(ClientSessionModel clientSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin, ClientSessionCode clientSessionCode) {
String postBrokerLoginFlowId = context.getIdpConfig().getPostBrokerLoginFlowId();
if (postBrokerLoginFlowId == null) {
logger.debugf("Skip redirect to postBrokerLogin flow. PostBrokerLogin flow not set for identityProvider '%s'.", context.getIdpConfig().getAlias());
- return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin);
+ return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin, clientSessionCode);
} else {
logger.debugf("Redirect to postBrokerLogin flow after authentication with identityProvider '%s'.", context.getIdpConfig().getAlias());
@@ -461,7 +465,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
clientSession.setNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN, String.valueOf(wasFirstBrokerLogin));
URI redirect = LoginActionsService.postBrokerLoginProcessor(uriInfo)
- .queryParam(OAuth2Constants.CODE, context.getCode())
+ .queryParam(OAuth2Constants.CODE, clientSessionCode.getCode())
.build(realmModel.getName());
return Response.status(302).location(redirect).build();
}
@@ -499,13 +503,13 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
clientSession.removeNote(PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
clientSession.removeNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN);
- return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin);
+ return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin, parsedCode.clientSessionCode);
} catch (IdentityBrokerException e) {
return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
}
}
- private Response afterPostBrokerLoginFlowSuccess(ClientSessionModel clientSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin) {
+ private Response afterPostBrokerLoginFlowSuccess(ClientSessionModel clientSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin, ClientSessionCode clientSessionCode) {
String providerId = context.getIdpConfig().getAlias();
UserModel federatedUser = clientSession.getAuthenticatedUser();
@@ -532,7 +536,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return redirectToErrorPage(Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, federatedUser.getUsername(), linkingUser.getUsername());
}
- return afterFirstBrokerLogin(context.getCode());
+ return afterFirstBrokerLogin(clientSessionCode);
} else {
return finishBrokerAuthentication(context, federatedUser, clientSession, providerId);
}
@@ -556,7 +560,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
logger.debugf("Performing local authentication for user [%s].", federatedUser);
}
- return AuthenticationProcessor.redirectToRequiredActions(realmModel, clientSession, uriInfo);
+ return AuthenticationProcessor.redirectToRequiredActions(session, realmModel, clientSession, uriInfo);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 8b23ee2..91cfa28 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -619,7 +619,7 @@ public class LoginActionsService {
}
private Response redirectToAfterBrokerLoginEndpoint(ClientSessionModel clientSession, boolean firstBrokerLogin) {
- ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
+ ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
clientSession.setTimestamp(Time.currentTime());
URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) :
@@ -736,7 +736,7 @@ public class LoginActionsService {
event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN);
- return AuthenticationProcessor.redirectToRequiredActions(realm, clientSession, uriInfo);
+ return AuthenticationProcessor.redirectToRequiredActions(session, realm, clientSession, uriInfo);
} else {
Checks checks = new Checks();
if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
@@ -779,7 +779,7 @@ public class LoginActionsService {
clientSession.getUserSession().getUser().setEmailVerified(true);
clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
clientSession.setNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), "true");
- return AuthenticationProcessor.redirectToRequiredActions(realm, clientSession, uriInfo);
+ return AuthenticationProcessor.redirectToRequiredActions(session, realm, clientSession, uriInfo);
} else {
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.INVALID_CODE);
@@ -886,7 +886,7 @@ public class LoginActionsService {
if (AuthenticationManager.isActionRequired(session, userSession, clientSession, clientConnection, request, uriInfo, event)) {
// redirect to a generic code URI so that browser refresh will work
- return redirectToRequiredActions(code);
+ return redirectToRequiredActions(checks.clientCode.getCode());
} else {
return AuthenticationManager.finishedRequiredActions(session, userSession, clientSession, clientConnection, request, uriInfo, event);
diff --git a/services/src/main/java/org/keycloak/services/resources/PublicRealmResource.java b/services/src/main/java/org/keycloak/services/resources/PublicRealmResource.java
index d7aeaaa..0b41e51 100755
--- a/services/src/main/java/org/keycloak/services/resources/PublicRealmResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/PublicRealmResource.java
@@ -19,6 +19,8 @@ package org.keycloak.services.resources;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.idm.PublishedRealmRepresentation;
@@ -52,6 +54,9 @@ public class PublicRealmResource {
@Context
protected HttpResponse response;
+ @Context
+ protected KeycloakSession session;
+
protected RealmModel realm;
public PublicRealmResource(RealmModel realm) {
@@ -79,16 +84,16 @@ public class PublicRealmResource {
@Produces(MediaType.APPLICATION_JSON)
public PublishedRealmRepresentation getRealm() {
Cors.add(request).allowedOrigins(Cors.ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD).auth().build(response);
- return realmRep(realm, uriInfo);
+ return realmRep(session, realm, uriInfo);
}
- public static PublishedRealmRepresentation realmRep(RealmModel realm, UriInfo uriInfo) {
+ public static PublishedRealmRepresentation realmRep(KeycloakSession session, RealmModel realm, UriInfo uriInfo) {
PublishedRealmRepresentation rep = new PublishedRealmRepresentation();
rep.setRealm(realm.getName());
rep.setTokenServiceUrl(OIDCLoginProtocolService.tokenServiceBaseUrl(uriInfo).build(realm.getName()).toString());
rep.setAccountServiceUrl(AccountService.accountServiceBaseUrl(uriInfo).build(realm.getName()).toString());
rep.setAdminApiUrl(uriInfo.getBaseUriBuilder().path(AdminRoot.class).build().toString());
- rep.setPublicKeyPem(realm.getPublicKeyPem());
+ rep.setPublicKeyPem(PemUtils.encodeKey(session.keys().getActiveKey(realm).getPublicKey()));
rep.setNotBefore(realm.getNotBefore());
return rep;
}
diff --git a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
index 98d091e..7da4f61 100755
--- a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -148,8 +148,8 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
tokenBuilder.append("}");
identity.setToken(tokenBuilder.toString());
- identity.setCode(state);
identity.setIdpConfig(getConfig());
+ identity.setCode(state);
return callback.authenticated(identity);
} catch (Exception e) {
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory
new file mode 100644
index 0000000..a606c6e
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+
+org.keycloak.keys.GeneratedRsaKeyProviderFactory
+org.keycloak.keys.JavaKeystoreKeyProviderFactory
+org.keycloak.keys.RsaKeyProviderFactory
diff --git a/services/src/test/java/org/keycloak/test/RealmKeyGenerator.java b/services/src/test/java/org/keycloak/test/RealmKeyGenerator.java
index 8f783a1..40987b5 100755
--- a/services/src/test/java/org/keycloak/test/RealmKeyGenerator.java
+++ b/services/src/test/java/org/keycloak/test/RealmKeyGenerator.java
@@ -18,11 +18,8 @@
package org.keycloak.test;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.common.util.PemUtils;
-import java.io.IOException;
-import java.io.StringWriter;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
@@ -44,21 +41,7 @@ public class RealmKeyGenerator {
throw new RuntimeException(e);
}
- System.out.println("privateKey : " + printKey(keyPair.getPrivate()));
- System.out.println("publicKey : " + printKey(keyPair.getPublic()));
- }
-
- private static String printKey(Object key){
- StringWriter writer = new StringWriter();
- PEMWriter pemWriter = new PEMWriter(writer);
- try {
- pemWriter.writeObject(key);
- pemWriter.flush();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- String s = writer.toString();
- return PemUtils.removeBeginEnd(s);
-
+ System.out.println("privateKey : " + PemUtils.encodeKey(keyPair.getPrivate()));
+ System.out.println("publicKey : " + PemUtils.encodeKey(keyPair.getPublic()));
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index a602ffd..25e046f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
@@ -34,13 +34,11 @@ import java.security.PublicKey;
*/
public class AdapterTest {
- public static PublicKey realmPublicKey;
@ClassRule
public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
@Override
protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
- RealmModel realm = AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass());
- realmPublicKey = realm.getPublicKey();
+ AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass());
URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
createApplicationDeployment()
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java
index a533133..17531a0 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java
@@ -34,13 +34,11 @@ import java.security.PublicKey;
*/
public class FilterAdapterTest {
- public static PublicKey realmPublicKey;
@ClassRule
public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
@Override
protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
- RealmModel realm = AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass());
- realmPublicKey = realm.getPublicKey();
+ AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass());
URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
createApplicationDeployment()
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java
index e0d5d6c..a147608 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java
@@ -53,15 +53,12 @@ import java.util.Map;
public class RelativeUriAdapterTest {
public static final String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString();
- public static PublicKey realmPublicKey;
@ClassRule
public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule(){
@Override
protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
RealmRepresentation representation = KeycloakServer.loadJson(getClass().getResourceAsStream("/adapter-test/demorealm-relative.json"), RealmRepresentation.class);
- RealmModel realm = manager.importRealm(representation);
-
- realmPublicKey = realm.getPublicKey();
+ manager.importRealm(representation);
URL url = getClass().getResource("/adapter-test/cust-app-keycloak-relative.json");
createApplicationDeployment()
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/ApiUtil.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/ApiUtil.java
new file mode 100644
index 0000000..bdaf10b
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/ApiUtil.java
@@ -0,0 +1,218 @@
+/*
+ * 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.testsuite;
+
+import org.jboss.logging.Logger;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.admin.client.resource.RoleResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.KeysMetadataRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.Response.StatusType;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
+
+/**
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class ApiUtil {
+
+ private static final Logger log = Logger.getLogger(ApiUtil.class);
+
+ public static String getCreatedId(Response response) {
+ URI location = response.getLocation();
+ if (!response.getStatusInfo().equals(Status.CREATED)) {
+ StatusType statusInfo = response.getStatusInfo();
+ throw new WebApplicationException("Create method returned status " +
+ statusInfo.getReasonPhrase() + " (Code: " + statusInfo.getStatusCode() + "); expected status: Created (201)", response);
+ }
+ if (location == null) {
+ return null;
+ }
+ String path = location.getPath();
+ return path.substring(path.lastIndexOf('/') + 1);
+ }
+
+ public static ClientResource findClientResourceById(RealmResource realm, String id) {
+ for (ClientRepresentation c : realm.clients().findAll()) {
+ if (c.getId().equals(id)) {
+ return realm.clients().get(c.getId());
+ }
+ }
+ return null;
+ }
+
+ public static ClientResource findClientResourceByClientId(RealmResource realm, String clientId) {
+ for (ClientRepresentation c : realm.clients().findAll()) {
+ if (c.getClientId().equals(clientId)) {
+ return realm.clients().get(c.getId());
+ }
+ }
+ return null;
+ }
+
+ public static ClientResource findClientResourceByName(RealmResource realm, String name) {
+ for (ClientRepresentation c : realm.clients().findAll()) {
+ if (c.getName().equals(name)) {
+ return realm.clients().get(c.getId());
+ }
+ }
+ return null;
+ }
+
+ public static ClientResource findClientByClientId(RealmResource realm, String clientId) {
+ for (ClientRepresentation c : realm.clients().findAll()) {
+ if (c.getClientId().equals(clientId)) {
+ return realm.clients().get(c.getId());
+ }
+ }
+ return null;
+ }
+
+ public static RoleResource findClientRoleByName(ClientResource client, String role) {
+ return client.roles().get(role);
+ }
+
+ public static ProtocolMapperRepresentation findProtocolMapperByName(ClientResource client, String name) {
+ for (ProtocolMapperRepresentation p : client.getProtocolMappers().getMappers()) {
+ if (p.getName().equals(name)) {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ public static RoleResource findRealmRoleByName(RealmResource realm, String role) {
+ return realm.roles().get(role);
+ }
+
+ public static UserRepresentation findUserByUsername(RealmResource realm, String username) {
+ UserRepresentation user = null;
+ List<UserRepresentation> ur = realm.users().search(username, null, null);
+ if (ur.size() == 1) {
+ user = ur.get(0);
+ }
+
+ if (ur.size() > 1) { // try to be more specific
+ for (UserRepresentation rep : ur) {
+ if (rep.getUsername().equalsIgnoreCase(username)) return rep;
+ }
+ }
+
+ return user;
+ }
+
+ public static UserResource findUserByUsernameId(RealmResource realm, String username) {
+ return realm.users().get(findUserByUsername(realm, username).getId());
+ }
+
+ public static String createUserWithAdminClient(RealmResource realm, UserRepresentation user) {
+ Response response = realm.users().create(user);
+ String createdId = getCreatedId(response);
+ response.close();
+ return createdId;
+ }
+
+ public static String createUserAndResetPasswordWithAdminClient(RealmResource realm, UserRepresentation user, String password) {
+ String id = createUserWithAdminClient(realm, user);
+ resetUserPassword(realm.users().get(id), password, false);
+ return id;
+ }
+
+ public static void resetUserPassword(UserResource userResource, String newPassword, boolean temporary) {
+ CredentialRepresentation newCredential = new CredentialRepresentation();
+ newCredential.setType(PASSWORD);
+ newCredential.setValue(newPassword);
+ newCredential.setTemporary(temporary);
+ userResource.resetPassword(newCredential);
+ }
+
+ public static void assignClientRoles(RealmResource realm, String userId, String clientName, String... roles) {
+ String realmName = realm.toRepresentation().getRealm();
+ String clientId = "";
+ for (ClientRepresentation clientRepresentation : realm.clients().findAll()) {
+ if (clientRepresentation.getClientId().equals(clientName)) {
+ clientId = clientRepresentation.getId();
+ }
+ }
+
+ if (!clientId.isEmpty()) {
+ ClientResource clientResource = realm.clients().get(clientId);
+
+ List<RoleRepresentation> roleRepresentations = new ArrayList<>();
+ for (String roleName : roles) {
+ RoleRepresentation role = clientResource.roles().get(roleName).toRepresentation();
+ roleRepresentations.add(role);
+ }
+
+ UserResource userResource = realm.users().get(userId);
+ log.debug("assigning role: " + Arrays.toString(roles) + " to user: \""
+ + userResource.toRepresentation().getUsername() + "\" of client: \""
+ + clientName + "\" in realm: \"" + realmName + "\"");
+ userResource.roles().clientLevel(clientId).add(roleRepresentations);
+ } else {
+ log.warn("client with name " + clientName + " doesn't exist in realm " + realmName);
+ }
+ }
+
+ public static boolean groupContainsSubgroup(GroupRepresentation group, GroupRepresentation subgroup) {
+ boolean contains = false;
+ for (GroupRepresentation sg : group.getSubGroups()) {
+ if (subgroup.getId().equals(sg.getId())) {
+ contains = true;
+ break;
+ }
+ }
+ return contains;
+ }
+
+ public static AuthorizationResource findAuthorizationSettings(RealmResource realm, String clientId) {
+ for (ClientRepresentation c : realm.clients().findAll()) {
+ if (c.getClientId().equals(clientId)) {
+ return realm.clients().get(c.getId()).authorization();
+ }
+ }
+ return null;
+ }
+
+ public static KeysMetadataRepresentation.KeyMetadataRepresentation findActiveKey(RealmResource realm) {
+ KeysMetadataRepresentation keyMetadata = realm.keys().getKeyMetadata();
+ String activeKid = keyMetadata.getActive().get("RSA");
+ for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
+ if (rep.getKid().equals(activeKid)) {
+ return rep;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithSignatureTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithSignatureTest.java
index 910356d..28c6625 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithSignatureTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithSignatureTest.java
@@ -18,6 +18,7 @@
package org.keycloak.testsuite.broker;
+import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.junit.BeforeClass;
@@ -25,18 +26,26 @@ import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
+import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time;
+import org.keycloak.keys.KeyProvider;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.ApiUtil;
import org.keycloak.testsuite.Constants;
import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import java.util.List;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
/**
@@ -112,6 +121,8 @@ public class OIDCKeycloakServerBrokerWithSignatureTest extends AbstractIdentityP
assertSuccessfulAuthentication(getIdentityProviderModel(), "test-user", "test-user@localhost", false);
// Rotate public keys on the parent broker
+ rotateKeys("realm-with-oidc-identity-provider");
+
RealmRepresentation realm = keycloak2.realm("realm-with-oidc-identity-provider").toRepresentation();
realm.setPublicKey(org.keycloak.models.Constants.GENERATE);
keycloak2.realm("realm-with-oidc-identity-provider").update(realm);
@@ -138,8 +149,8 @@ public class OIDCKeycloakServerBrokerWithSignatureTest extends AbstractIdentityP
OIDCIdentityProviderConfig cfg = new OIDCIdentityProviderConfig(idpModel);
cfg.setUseJwksUrl(false);
- RealmRepresentation realm = keycloak2.realm("realm-with-oidc-identity-provider").toRepresentation();
- cfg.setPublicKeySignatureVerifier(realm.getPublicKey());
+ KeysMetadataRepresentation.KeyMetadataRepresentation key = ApiUtil.findActiveKey(keycloak2.realm("realm-with-oidc-identity-provider"));
+ cfg.setPublicKeySignatureVerifier(key.getPublicKey());
getRealm().updateIdentityProvider(cfg);
brokerServerRule.stopSession(this.session, true);
@@ -149,8 +160,7 @@ public class OIDCKeycloakServerBrokerWithSignatureTest extends AbstractIdentityP
assertSuccessfulAuthentication(getIdentityProviderModel(), "test-user", "test-user@localhost", false);
// Rotate public keys on the parent broker
- realm.setPublicKey(org.keycloak.models.Constants.GENERATE);
- keycloak2.realm("realm-with-oidc-identity-provider").update(realm);
+ rotateKeys("realm-with-oidc-identity-provider");
// User not able to login now as new keys can't be yet downloaded (10s timeout)
loginIDP("test-user");
@@ -171,4 +181,25 @@ public class OIDCKeycloakServerBrokerWithSignatureTest extends AbstractIdentityP
Time.setOffset(0);
}
+
+ private void rotateKeys(String realmName) {
+ String activeKid = keycloak2.realm("realm-with-oidc-identity-provider").keys().getKeyMetadata().getActive().get("RSA");
+
+ // Rotate public keys on the parent broker
+ String realmId = keycloak2.realm(realmName).toRepresentation().getId();
+ ComponentRepresentation keys = new ComponentRepresentation();
+ keys.setName("generated");
+ keys.setProviderType(KeyProvider.class.getName());
+ keys.setProviderId("rsa-generated");
+ keys.setParentId(realmId);
+ keys.setConfig(new MultivaluedHashMap<>());
+ keys.getConfig().putSingle("priority", Long.toString(System.currentTimeMillis()));
+ Response response = keycloak2.realm("realm-with-oidc-identity-provider").components().add(keys);
+ assertEquals(201, response.getStatus());
+ response.close();
+
+ String updatedActiveKid = keycloak2.realm("realm-with-oidc-identity-provider").keys().getKeyMetadata().getActive().get("RSA");
+ assertNotEquals(activeKid, updatedActiveKid);
+ }
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/jaxrs/JaxrsFilterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/jaxrs/JaxrsFilterTest.java
index 6cd02bf..3a257fd 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/jaxrs/JaxrsFilterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/jaxrs/JaxrsFilterTest.java
@@ -290,46 +290,6 @@ public class JaxrsFilterTest {
goodResp.close();
}
- @Test
- public void testPushNotBefore() {
- keycloakRule.update(new KeycloakRule.KeycloakSetup() {
-
- @Override
- public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
- Map<String,String> initParams = new TreeMap<String,String>();
- initParams.put(CONFIG_FILE_INIT_PARAM, "classpath:jaxrs-test/jaxrs-keycloak.json");
- keycloakRule.deployJaxrsApplication("JaxrsSimpleApp", "/jaxrs-simple", JaxrsTestApplication.class, initParams);
- }
-
- });
-
- // Retrieve token
- OAuthClient.AccessTokenResponse accessTokenResp = retrieveAccessToken();
- String authHeader = "Bearer " + accessTokenResp.getAccessToken();
-
- // Send GET request with token and assert it's passing
- JaxrsTestResource.SimpleRepresentation getRep = client.target(JAXRS_APP_URL).request()
- .header(HttpHeaders.AUTHORIZATION, authHeader)
- .get(JaxrsTestResource.SimpleRepresentation.class);
- Assert.assertEquals("get", getRep.getMethod());
- Assert.assertTrue(getRep.getHasUserRole());
-
- // Push new notBefore now TODO: should use admin console (admin client) instead..
- int currentTime = Time.currentTime();
- PushNotBeforeAction action = new PushNotBeforeAction(TokenIdGenerator.generateId(), currentTime + 30, "jaxrs-app", currentTime + 1);
- String token = new TokenManager().encodeToken(appRealm, action);
- Response response = client.target(JAXRS_APP_PUSN_NOT_BEFORE_URL).request().post(Entity.text(token));
- Assert.assertEquals(204, response.getStatus());
- response.close();
-
- // Assert that previous token shouldn't pass anymore
- response = client.target(JAXRS_APP_URL).request()
- .header(HttpHeaders.AUTHORIZATION, authHeader)
- .get();
- Assert.assertEquals(401, response.getStatus());
- response.close();
- }
-
// @Test
public void testCxfExample() {
//String uri = "http://localhost:9000/customerservice/customers/123";
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestImplProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestImplProviderFactory.java
new file mode 100644
index 0000000..2a1a911
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestImplProviderFactory.java
@@ -0,0 +1,107 @@
+/*
+ * 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.testsuite.components;
+
+import org.keycloak.Config;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ConfigurationValidationHelper;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.provider.ProviderConfigurationBuilder;
+
+import java.util.List;
+
+import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class TestImplProviderFactory implements TestProviderFactory {
+
+ private List<ProviderConfigProperty> config = ProviderConfigurationBuilder.create()
+ .property("secret", "Secret", "A secret value", STRING_TYPE, null, true)
+ .property("number", "Number", "A number value", STRING_TYPE, null, false)
+ .property("required", "Required", "A required value", STRING_TYPE, null, false)
+ .property("val1", "Value 1", "Some more values", STRING_TYPE, null, false)
+ .property("val2", "Value 2", "Some more values", STRING_TYPE, null, false)
+ .property("val3", "Value 3", "Some more values", STRING_TYPE, null, false)
+ .build();
+
+ @Override
+ public Object create(KeycloakSession session, ComponentModel model) {
+ return new TestImplProvider(model);
+ }
+
+ @Override
+ public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
+ ConfigurationValidationHelper.check(model)
+ .checkRequired("required", "Required")
+ .checkInt("number", "Number", false);
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Provider to test component storage";
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return config;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return "test";
+ }
+
+ public static class TestImplProvider implements TestProvider {
+
+ private ComponentModel model;
+
+ public TestImplProvider(ComponentModel model) {
+ this.model = model;
+ }
+
+ @Override
+ public DetailsRepresentation getDetails() {
+ DetailsRepresentation rep = new DetailsRepresentation();
+ rep.setConfig(model.getConfig());
+ return rep;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ }
+
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestProvider.java
new file mode 100644
index 0000000..163f150
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestProvider.java
@@ -0,0 +1,44 @@
+/*
+ * 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.testsuite.components;
+
+import org.keycloak.provider.Provider;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface TestProvider extends Provider {
+
+ DetailsRepresentation getDetails();
+
+ class DetailsRepresentation {
+ Map<String, List<String>> config;
+
+ public Map<String, List<String>> getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map<String, List<String>> config) {
+ this.config = config;
+ }
+ }
+
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestProviderFactory.java
new file mode 100644
index 0000000..69dea13
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestProviderFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.testsuite.components;
+
+import org.keycloak.component.ComponentFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface TestProviderFactory<T extends TestProvider> extends ComponentFactory<T, TestProvider> {
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestSpi.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestSpi.java
new file mode 100644
index 0000000..2413e65
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/components/TestSpi.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.testsuite.components;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class TestSpi implements Spi {
+ @Override
+ public boolean isInternal() {
+ return false;
+ }
+
+ @Override
+ public String getName() {
+ return "test";
+ }
+
+ @Override
+ public Class<? extends Provider> getProviderClass() {
+ return TestProvider.class;
+ }
+
+ @Override
+ public Class<? extends ProviderFactory> getProviderFactoryClass() {
+ return TestProviderFactory.class;
+ }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
index 6daa6b1..cf0f55e 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
@@ -20,12 +20,13 @@ package org.keycloak.testsuite.rest.resource;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.BadRequestException;
import org.keycloak.OAuth2Constants;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.PemUtils;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.jose.jws.JWSBuilder;
-import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.testsuite.rest.TestApplicationResourceProviderFactory;
@@ -69,8 +70,8 @@ public class TestingOIDCEndpointsApplicationResource {
throw new BadRequestException("Error generating signing keypair", e);
}
- String privateKeyPem = KeycloakModelUtils.getPemFromKey(clientData.getSigningKeyPair().getPrivate());
- String publicKeyPem = KeycloakModelUtils.getPemFromKey(clientData.getSigningKeyPair().getPublic());
+ String privateKeyPem = PemUtils.encodeKey(clientData.getSigningKeyPair().getPrivate());
+ String publicKeyPem = PemUtils.encodeKey(clientData.getSigningKeyPair().getPublic());
Map<String, String> res = new HashMap<>();
res.put(PRIVATE_KEY, privateKeyPem);
@@ -120,7 +121,7 @@ public class TestingOIDCEndpointsApplicationResource {
}
PrivateKey privateKey = clientData.getSigningKeyPair().getPrivate();
- String kid = JWKBuilder.createKeyId(clientData.getSigningKeyPair().getPublic());
+ String kid = KeyUtils.createKeyId(clientData.getSigningKeyPair().getPublic());
clientData.setOidcRequest(new JWSBuilder().kid(kid).jsonContent(oidcRequest).rsa256(privateKey));
} else {
throw new BadRequestException("Unknown argument: " + jwaAlgorithm);
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index 4cbf4e7..9798036 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -21,6 +21,7 @@ import org.infinispan.Cache;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.BadRequestException;
import org.keycloak.common.util.Time;
+import org.keycloak.component.ComponentModel;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.events.Event;
import org.keycloak.events.EventQuery;
@@ -31,6 +32,8 @@ import org.keycloak.events.admin.AdminEventQuery;
import org.keycloak.events.admin.AuthDetails;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.keys.KeyProviderFactory;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.FederatedIdentityModel;
@@ -44,6 +47,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.AuthDetailsRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
@@ -52,6 +56,8 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resource.RealmResourceProvider;
+import org.keycloak.testsuite.components.TestProvider;
+import org.keycloak.testsuite.components.TestProviderFactory;
import org.keycloak.testsuite.events.EventsListenerProvider;
import org.keycloak.testsuite.forms.PassThroughAuthenticator;
import org.keycloak.testsuite.forms.PassThroughClientAuthenticator;
@@ -526,25 +532,6 @@ public class TestingResourceProvider implements RealmResourceProvider {
public void close() {
}
- /*
- * Migration from KeycloakRule#verifyCode
- */
- @GET
- @Path("/verify-code")
- @Produces(MediaType.APPLICATION_JSON)
- public String verifyCode(@QueryParam("realm") String realmName, @QueryParam("code") String code) {
- RealmModel realm = session.realms().getRealm(realmName);
- try {
- ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
- if (accessCode == null) {
- throw new AssertionError("Invalid code");
- }
- return accessCode.getClientSession().getId();
- } catch (Throwable t) {
- throw new AssertionError("Failed to parse code", t);
- }
- }
-
@POST
@Path("/update-pass-through-auth-state")
@Produces(MediaType.APPLICATION_JSON)
@@ -634,6 +621,23 @@ public class TestingResourceProvider implements RealmResourceProvider {
return new TestingExportImportResource(session);
}
+ @GET
+ @Path("/test-component")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Map<String, TestProvider.DetailsRepresentation> getTestComponentDetails() {
+ Map<String, TestProvider.DetailsRepresentation> reps = new HashMap<>();
+
+ RealmModel realm = session.getContext().getRealm();
+ for (ComponentModel c : realm.getComponents(realm.getId(), TestProvider.class.getName())) {
+ ProviderFactory<TestProvider> f = session.getKeycloakSessionFactory().getProviderFactory(TestProvider.class, c.getProviderId());
+ TestProviderFactory factory = (TestProviderFactory) f;
+ TestProvider p = (TestProvider) factory.create(session, c);
+ reps.put(c.getName(), p.getDetails());
+ }
+
+ return reps;
+ }
+
private RealmModel getRealmByName(String realmName) {
RealmProvider realmProvider = session.getProvider(RealmProvider.class);
return realmProvider.getRealmByName(realmName);
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 1c31c00..df1b912 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -16,3 +16,4 @@
#
org.keycloak.testsuite.domainextension.spi.ExampleSpi
+org.keycloak.testsuite.components.TestSpi
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.testsuite.components.TestProviderFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.testsuite.components.TestProviderFactory
new file mode 100644
index 0000000..701b835
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.testsuite.components.TestProviderFactory
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+org.keycloak.testsuite.components.TestImplProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java
index dedc08d..ae3a873 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java
@@ -41,7 +41,11 @@ public class KeycloakTestingClient {
}
public TestingResource testing() {
- return target.proxy(TestingResource.class);
+ return target.path("/realms/master").proxy(TestingResource.class);
+ }
+
+ public TestingResource testing(String realm) {
+ return target.path("/realms/" + realm).proxy(TestingResource.class);
}
public TestApplicationResource testApp() { return target.proxy(TestApplicationResource.class); }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
index 9638229..50352d4 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
@@ -22,6 +22,7 @@ import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.components.TestProvider;
import org.keycloak.testsuite.rest.representation.AuthenticatorState;
import javax.ws.rs.Consumes;
@@ -41,7 +42,7 @@ import java.util.Map;
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
-@Path("/realms/master/testing")
+@Path("/testing")
@Consumes(MediaType.APPLICATION_JSON)
public interface TestingResource {
@@ -194,11 +195,6 @@ public interface TestingResource {
@Produces(MediaType.APPLICATION_JSON)
boolean isCached(@PathParam("cache") String cacheName, @PathParam("id") String id);
- @GET
- @Path("/verify-code")
- @Produces(MediaType.APPLICATION_JSON)
- String verifyCode(@QueryParam("realm") String realmName, @QueryParam("code") String code);
-
@POST
@Path("/update-pass-through-auth-state")
@Produces(MediaType.APPLICATION_JSON)
@@ -241,4 +237,9 @@ public interface TestingResource {
@Path("export-import")
TestingExportImportResource exportImport();
+ @GET
+ @Path("/test-component")
+ @Produces(MediaType.APPLICATION_JSON)
+ Map<String, TestProvider.DetailsRepresentation> getTestComponentDetails();
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
index 8786ff0..374585b 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
@@ -45,6 +45,7 @@ import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken;
+import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.JsonSerialization;
@@ -814,12 +815,13 @@ public class OAuthClient {
public PublicKey getRealmPublicKey(String realm) {
if (!publicKeys.containsKey(realm)) {
- String publicKeyPem = adminClient.realms().realm(realm).toRepresentation().getPublicKey();
+ KeysMetadataRepresentation keyMetadata = adminClient.realms().realm(realm).keys().getKeyMetadata();
+ String activeKid = keyMetadata.getActive().get("RSA");
PublicKey publicKey = null;
- try {
- publicKey = PemUtils.decodePublicKey(publicKeyPem);
- } catch (Exception e) {
- throw new RuntimeException(e);
+ for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
+ if (rep.getKid().equals(activeKid)) {
+ publicKey = PemUtils.decodePublicKey(rep.getPublicKey());
+ }
}
publicKeys.put(realm, publicKey);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java
index 4d55e97..50a5027 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java
@@ -25,14 +25,17 @@ import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
+import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.Response.StatusType;
import java.net.URI;
+import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -50,8 +53,8 @@ public class ApiUtil {
URI location = response.getLocation();
if (!response.getStatusInfo().equals(Status.CREATED)) {
StatusType statusInfo = response.getStatusInfo();
- throw new RuntimeException("Create method returned status " +
- statusInfo.getReasonPhrase() + " (Code: " + statusInfo.getStatusCode() + "); expected status: Created (201)");
+ throw new WebApplicationException("Create method returned status " +
+ statusInfo.getReasonPhrase() + " (Code: " + statusInfo.getStatusCode() + "); expected status: Created (201)", response);
}
if (location == null) {
return null;
@@ -201,4 +204,16 @@ public class ApiUtil {
}
return null;
}
+
+ public static KeysMetadataRepresentation.KeyMetadataRepresentation findActiveKey(RealmResource realm) {
+ KeysMetadataRepresentation keyMetadata = realm.keys().getKeyMetadata();
+ String activeKid = keyMetadata.getActive().get("RSA");
+ for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
+ if (rep.getKid().equals(activeKid)) {
+ return rep;
+ }
+ }
+ return null;
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
index bac5fc0..626c0d4 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
@@ -22,10 +22,12 @@ import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
/**
* Test getting the installation/configuration files for OIDC and SAML.
@@ -61,8 +63,8 @@ public class InstallationTest extends AbstractClientTest {
return AuthServerTestEnricher.getAuthServerContextRoot() + "/auth";
}
- private String samlUrl(RealmRepresentation realmRep) {
- return authServerUrl() + "/realms/" + realmRep.getId() + "/protocol/saml";
+ private String samlUrl() {
+ return authServerUrl() + "/realms/master/protocol/saml";
}
@Test
@@ -79,30 +81,27 @@ public class InstallationTest extends AbstractClientTest {
}
private void assertOidcInstallationConfig(String config) {
- RealmRepresentation realmRep = realmRep();
- assertTrue(config.contains(realmRep.getId()));
- assertFalse(config.contains(realmRep.getPublicKey()));
+ assertTrue(config.contains("master"));
+ assertFalse(config.contains(ApiUtil.findActiveKey(testRealmResource()).getPublicKey()));
assertTrue(config.contains(authServerUrl()));
}
@Test
public void testSamlMetadataIdpDescriptor() {
String xml = samlClient.getInstallationProvider("saml-idp-descriptor");
- RealmRepresentation realmRep = realmRep();
assertTrue(xml.contains("<EntityDescriptor"));
assertTrue(xml.contains("<IDPSSODescriptor"));
- assertTrue(xml.contains(realmRep.getCertificate()));
- assertTrue(xml.contains(samlUrl(realmRep)));
+ assertTrue(xml.contains(ApiUtil.findActiveKey(testRealmResource()).getCertificate()));
+ assertTrue(xml.contains(samlUrl()));
}
@Test
public void testSamlAdapterXml() {
String xml = samlClient.getInstallationProvider("keycloak-saml");
- RealmRepresentation realmRep = realmRep();
assertTrue(xml.contains("<keycloak-saml-adapter>"));
assertTrue(xml.contains(SAML_NAME));
- assertTrue(xml.contains(realmRep.getCertificate()));
- assertTrue(xml.contains(samlUrl(realmRep)));
+ assertTrue(xml.contains(ApiUtil.findActiveKey(testRealmResource()).getCertificate()));
+ assertTrue(xml.contains(samlUrl()));
}
@Test
@@ -116,10 +115,9 @@ public class InstallationTest extends AbstractClientTest {
@Test
public void testSamlJBossXml() {
String xml = samlClient.getInstallationProvider("keycloak-saml-subsystem");
- RealmRepresentation realmRep = realmRep();
assertTrue(xml.contains("<secure-deployment"));
assertTrue(xml.contains(SAML_NAME));
- assertTrue(xml.contains(realmRep.getCertificate()));
- assertTrue(xml.contains(samlUrl(realmRep)));
+ assertTrue(xml.contains(ApiUtil.findActiveKey(testRealmResource()).getCertificate()));
+ assertTrue(xml.contains(samlUrl()));
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java
new file mode 100644
index 0000000..9088447
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.testsuite.admin;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ComponentsResource;
+import org.keycloak.common.util.CertificateUtils;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.keys.Attributes;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.keys.RsaKeyProviderFactory;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.ErrorRepresentation;
+import org.keycloak.representations.idm.KeysMetadataRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.components.TestImplProviderFactory;
+import org.keycloak.testsuite.components.TestProvider;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ComponentsTest extends AbstractAdminTest {
+
+ private ComponentsResource components;
+
+ @Before
+ public void before() throws Exception {
+ components = adminClient.realm(REALM_NAME).components();
+ }
+
+ @Test
+ public void testNotDeadlocked() {
+ for (int i = 0; i < 100; i++) {
+ ComponentRepresentation rep = createComponentRepresentation("test-" + i);
+ rep.getConfig().putSingle("required", "required-value");
+ createComponent(rep);
+
+ List<ComponentRepresentation> list = realm.components().query(realmId, TestProvider.class.getName());
+ assertEquals(i + 1, list.size());
+ }
+ }
+
+ @Test
+ public void testCreateValidation() {
+ ComponentRepresentation rep = createComponentRepresentation("mycomponent");
+
+ // Check validation is invoked
+ try {
+ createComponent(rep);
+ } catch (WebApplicationException e) {
+ assertErrror(e.getResponse(), "Required is required");
+ }
+
+ rep.getConfig().putSingle("required", "Required");
+ rep.getConfig().putSingle("number", "invalid");
+
+ // Check validation is invoked
+ try {
+ createComponent(rep);
+ } catch (WebApplicationException e) {
+ assertErrror(e.getResponse(), "Number should be a number");
+ }
+ }
+
+ @Test
+ public void testCreateEmptyValues() {
+ ComponentRepresentation rep = createComponentRepresentation("mycomponent");
+
+ rep.getConfig().addFirst("required", "foo");
+ rep.getConfig().addFirst("val1", "");
+ rep.getConfig().put("val2", null);
+ rep.getConfig().put("val3", Collections.emptyList());
+
+ String id = createComponent(rep);
+ ComponentRepresentation returned = components.component(id).toRepresentation();
+ assertEquals(1, returned.getConfig().size());
+ assertTrue(returned.getConfig().containsKey("required"));
+ }
+
+ @Test
+ public void testUpdate() {
+ ComponentRepresentation rep = createComponentRepresentation("mycomponent");
+
+ rep.getConfig().addFirst("required", "foo");
+ rep.getConfig().addFirst("val1", "one");
+ rep.getConfig().addFirst("val2", "two");
+ rep.getConfig().addFirst("val3", "three");
+
+ String id = createComponent(rep);
+ ComponentRepresentation returned = components.component(id).toRepresentation();
+ assertEquals(4, returned.getConfig().size());
+ assertEquals("foo", returned.getConfig().getFirst("required"));
+ assertEquals("one", returned.getConfig().getFirst("val1"));
+ assertEquals("two", returned.getConfig().getFirst("val2"));
+ assertEquals("three", returned.getConfig().getFirst("val3"));
+
+ // Check value updated
+ returned.getConfig().putSingle("val1", "one-updated");
+
+ // Check null deletes property
+ returned.getConfig().putSingle("val2", null);
+
+ components.component(id).update(returned);
+
+ returned = components.component(id).toRepresentation();
+ assertEquals(3, returned.getConfig().size());
+ assertEquals("one-updated", returned.getConfig().getFirst("val1"));
+ assertFalse(returned.getConfig().containsKey("val2"));
+
+ // Check empty string is deleted
+ returned.getConfig().addFirst("val1", "");
+
+ components.component(id).update(returned);
+
+ returned = components.component(id).toRepresentation();
+ assertEquals(2, returned.getConfig().size());
+
+ // Check empty list removes property
+ returned.getConfig().put("val3", Collections.emptyList());
+
+ components.component(id).update(returned);
+
+ returned = components.component(id).toRepresentation();
+ assertEquals(1, returned.getConfig().size());
+ }
+
+ @Test
+ public void testSecretConfig() throws Exception {
+ ComponentRepresentation rep = createComponentRepresentation("mycomponent");
+ rep.getConfig().addFirst("secret", "some secret value!!");
+ rep.getConfig().addFirst("required", "some required value");
+
+ String id = createComponent(rep);
+
+ // Check secret value is not returned
+ ComponentRepresentation returned = components.component(id).toRepresentation();
+ assertEquals(ComponentRepresentation.SECRET_VALUE, returned.getConfig().getFirst("secret"));
+
+ Map<String, TestProvider.DetailsRepresentation> details = testingClient.testing(REALM_NAME).getTestComponentDetails();
+
+ // Check value is set correctly
+ assertEquals("some secret value!!", details.get("mycomponent").getConfig().get("secret").get(0));
+
+ returned.getConfig().putSingle("priority", "200");
+ components.component(id).update(returned);
+
+ ComponentRepresentation returned2 = components.component(id).toRepresentation();
+ assertEquals(ComponentRepresentation.SECRET_VALUE, returned2.getConfig().getFirst("secret"));
+
+ // Check secret value is not set to '*********'
+ details = testingClient.testing(REALM_NAME).getTestComponentDetails();
+ assertEquals("some secret value!!", details.get("mycomponent").getConfig().get("secret").get(0));
+
+ returned2.getConfig().putSingle("secret", "updated secret value!!");
+ components.component(id).update(returned2);
+
+ // Check secret value is updated
+ details = testingClient.testing(REALM_NAME).getTestComponentDetails();
+ assertEquals("updated secret value!!", details.get("mycomponent").getConfig().get("secret").get(0));
+ }
+
+ private String createComponent(ComponentRepresentation rep) {
+ ComponentsResource components = realm.components();
+ Response response = components.add(rep);
+ String id = ApiUtil.getCreatedId(response);
+ response.close();
+ return id;
+ }
+
+ private void assertErrror(Response response, String error) {
+ if (!response.hasEntity()) {
+ fail("No error message set");
+ }
+
+ ErrorRepresentation errorRepresentation = response.readEntity(ErrorRepresentation.class);
+ assertEquals(error, errorRepresentation.getErrorMessage());
+ }
+
+ private ComponentRepresentation createComponentRepresentation(String name) {
+ ComponentRepresentation rep = new ComponentRepresentation();
+ rep.setName(name);
+ rep.setParentId(realmId);
+ rep.setProviderId("test");
+ rep.setProviderType(TestProvider.class.getName());
+
+ MultivaluedHashMap config = new MultivaluedHashMap();
+ rep.setConfig(config);
+ return rep;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/AbstractGroupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/AbstractGroupTest.java
index 4574de1..07fcf5f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/AbstractGroupTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/AbstractGroupTest.java
@@ -29,6 +29,7 @@ import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.util.AssertAdminEvents;
import org.keycloak.testsuite.util.OAuthClient.AccessTokenResponse;
@@ -55,7 +56,7 @@ public abstract class AbstractGroupTest extends AbstractKeycloakTest {
String accessToken = tokenResponse.getAccessToken();
String refreshToken = tokenResponse.getRefreshToken();
- PublicKey publicKey = PemUtils.decodePublicKey(adminClient.realm("test").toRepresentation().getPublicKey());
+ PublicKey publicKey = PemUtils.decodePublicKey(ApiUtil.findActiveKey(adminClient.realm("test")).getPublicKey());
AccessToken accessTokenRepresentation = RSATokenVerifier.verifyToken(accessToken, publicKey, AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/realms/test");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
index 522f794..7621f14 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
@@ -76,21 +76,10 @@ public class RealmTest extends AbstractAdminTest {
@Rule
public AssertEvents events = new AssertEvents(this);
- public static final String PRIVATE_KEY = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=";
- public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
- public static final String CERTIFICATE = "MIICsTCCAZkCBgFTLB5bhDANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFhZG1pbi1jbGllbnQtdGVzdDAeFw0xNjAyMjkwODIwMDBaFw0yNjAyMjgwODIxNDBaMBwxGjAYBgNVBAMMEWFkbWluLWNsaWVudC10ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAquzJtpAlpTFnJzILjTOHW+SOWav1eIsCtlAqiFTvBskbod6b4BtVaR3FVrQm8rFiwDOIEWT3IG3ZIz0LKYxnqvuffyLHGHjiroqrR63kY9Wa9B790lSEWVaGeNOMnKleqKu5QUNfL3wVebUh/C/QfxZ29R1EIbxNe2ThN8yuIca8Ltn43D5VlyatptojffxpCYiYqAmIwQDaq1um2cQ+4rPBLxC5jM9UBvYOMUP4u0caNSaPI1o9lHVKgTtWcdQzUeMmAGsnLV26XGhA/OwRduUxksumR1kh/KSqowasjgSrpVqtF/uo5TY57s7drD+zKG58cdHLreclB9AQNvNwZwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBh4iwg8GnadeQP52pV5vKJ4Z8A1R2aYCzoW7Lc3FI/pXWX9Af5dKILX5O2j/daamPS+WtDWxIuwvZC5drrkvJn/r8e4KstnXQzPQggIJbI9v3wfIX3VlFvwvZVGiuE5PSLSWb0L57PEojZVpIU5bLchq4yRSD2zK4dWX8Y6I/D40a74KDvPOlEL8405/T1iW7ytKT9awNJW04N91owoI+kdUL+DMnnGzIxDAoYAeZI/1vcwoaH24zyTLGItkzpKxqLOdB05cnxn5jCWY2Hyd1zqtRkadhgZaqu4lcDHAHEMDp6dEjLZW8ym8bnlto+MD2y//CsyPCzyCLlA726vrli";
-
@Test
public void getRealms() {
List<RealmRepresentation> realms = adminClient.realms().findAll();
Assert.assertNames(realms, "master", AuthRealm.TEST, REALM_NAME);
-
- for (RealmRepresentation rep : realms) {
- assertNull(rep.getPrivateKey());
- assertNull(rep.getCodeSecret());
- assertNotNull(rep.getPublicKey());
- assertNotNull(rep.getCertificate());
- }
}
@Test
@@ -313,11 +302,6 @@ public class RealmTest extends AbstractAdminTest {
RealmRepresentation rep = realm.toRepresentation();
Assert.assertEquals(REALM_NAME, rep.getRealm());
assertTrue(rep.isEnabled());
-
- assertNull(rep.getPrivateKey());
- assertNull(rep.getCodeSecret());
- assertNotNull(rep.getPublicKey());
- assertNotNull(rep.getCertificate());
}
@Test
@@ -445,146 +429,6 @@ public class RealmTest extends AbstractAdminTest {
}
@Test
- public void uploadRealmKeys() throws Exception {
- String originalPublicKey = realm.toRepresentation().getPublicKey();
-
- RealmRepresentation rep = new RealmRepresentation();
- rep.setPrivateKey("INVALID");
- rep.setPublicKey(PUBLIC_KEY);
-
- try {
- realm.update(rep);
- fail("Expected BadRequestException");
- } catch (BadRequestException e) {
- // Expected
- assertAdminEvents.assertEmpty();
- }
-
- rep.setPrivateKey(PRIVATE_KEY);
- rep.setPublicKey("INVALID");
-
- try {
- realm.update(rep);
- fail("Expected BadRequestException");
- } catch (BadRequestException e) {
- // Expected
- assertAdminEvents.assertEmpty();
- }
-
- Assert.assertEquals(originalPublicKey, realm.toRepresentation().getPublicKey());
-
- rep.setPublicKey(PUBLIC_KEY);
- realm.update(rep);
- assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM);
-
- assertEquals(PUBLIC_KEY, rep.getPublicKey());
-
- String privateKey2048 = StreamUtil.readString(getClass().getResourceAsStream("/keys/private2048.pem"));
- String publicKey2048 = StreamUtil.readString(getClass().getResourceAsStream("/keys/public2048.pem"));
-
- rep.setPrivateKey(privateKey2048);
-
- try {
- realm.update(rep);
- fail("Expected BadRequestException");
- } catch (BadRequestException e) {
- // Expected
- assertAdminEvents.assertEmpty();
- }
-
- Assert.assertEquals(PUBLIC_KEY, realm.toRepresentation().getPublicKey());
-
- rep.setPrivateKey("{}{}{}{}{}{}324re9gvj0r");
- rep.setPublicKey("{}{}{}{}{}{}324re9gvj0r");
- try {
- realm.update(rep);
- fail("Expected BadRequestException");
- } catch (BadRequestException e) {
- // Expected
- assertAdminEvents.assertEmpty();
- }
-
- Assert.assertEquals(PUBLIC_KEY, realm.toRepresentation().getPublicKey());
-
- rep.setPrivateKey(privateKey2048);
- rep.setPublicKey(publicKey2048);
-
- realm.update(rep);
- assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM);
-
- Assert.assertEquals(publicKey2048, realm.toRepresentation().getPublicKey());
-
- String privateKey4096 = StreamUtil.readString(getClass().getResourceAsStream("/keys/private4096.pem"));
- String publicKey4096 = StreamUtil.readString(getClass().getResourceAsStream("/keys/public4096.pem"));
- rep.setPrivateKey(privateKey4096);
- rep.setPublicKey(publicKey4096);
-
- realm.update(rep);
- assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM);
-
- Assert.assertEquals(publicKey4096, realm.toRepresentation().getPublicKey());
- }
-
- @Test
- public void uploadCertificate() throws IOException {
- RealmRepresentation rep = new RealmRepresentation();
- rep.setCertificate(CERTIFICATE);
-
- realm.update(rep);
- assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM);
-
- assertEquals(CERTIFICATE, rep.getCertificate());
-
- String certificate = IOUtils.toString(getClass().getResourceAsStream("/keys/certificate.pem"));
- rep.setCertificate(certificate);
-
- realm.update(rep);
- assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM);
-
- assertEquals(certificate, realm.toRepresentation().getCertificate());
-
- rep.setCertificate("{}{}{}{}{}{}324re9gvj0r");
- try {
- realm.update(rep);
- fail("Expected BadRequestException");
- } catch (BadRequestException e) {
- // Expected
- assertAdminEvents.assertEmpty();
- }
-
- rep.setCertificate("invalid");
- try {
- realm.update(rep);
- fail("Expected BadRequestException");
- } catch (BadRequestException e) {
- // Expected
- assertAdminEvents.assertEmpty();
- }
-
- assertEquals(certificate, realm.toRepresentation().getCertificate());
- }
-
- @Test
- public void rotateRealmKeys() {
- RealmRepresentation realmRep = realm.toRepresentation();
- String publicKey = realmRep.getPublicKey();
- String cert = realmRep.getCertificate();
- assertNotNull(publicKey);
- assertNotNull(cert);
-
- RealmRepresentation newRealmRep = new RealmRepresentation();
- newRealmRep.setRealm(REALM_NAME);
- newRealmRep.setPublicKey("GENERATE");
- realm.update(newRealmRep);
-
- realmRep = realm.toRepresentation();
- assertNotNull(realmRep.getPublicKey());
- assertNotNull(realmRep.getCertificate());
- assertNotEquals(publicKey, realmRep.getPublicKey());
- assertNotEquals(cert, realmRep.getCertificate());
- }
-
- @Test
public void clearRealmCache() {
RealmRepresentation realmRep = realm.toRepresentation();
assertTrue(testingClient.testing().isCached("realms", realmRep.getId()));
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
index d79d4dc..632b594 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
@@ -21,6 +21,7 @@ import org.jboss.arquillian.container.spi.client.container.LifecycleException;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
+import org.keycloak.admin.client.resource.ComponentsResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.exportimport.ExportImportConfig;
@@ -28,6 +29,7 @@ import org.keycloak.exportimport.dir.DirExportProvider;
import org.keycloak.exportimport.dir.DirExportProviderFactory;
import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory;
import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.util.UserBuilder;
@@ -35,7 +37,11 @@ import org.keycloak.testsuite.util.UserBuilder;
import java.io.File;
import java.net.URL;
import java.util.List;
+import java.util.regex.Matcher;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
/**
@@ -83,7 +89,7 @@ public class ExportImportTest extends AbstractExportImportTest {
testFullExportImport();
// There should be 6 files in target directory (3 realm, 3 user)
- Assert.assertEquals(6, new File(targetDirPath).listFiles().length);
+ assertEquals(6, new File(targetDirPath).listFiles().length);
}
@Test
@@ -100,7 +106,7 @@ public class ExportImportTest extends AbstractExportImportTest {
// There should be 3 files in target directory (1 realm, 3 user)
File[] files = new File(targetDirPath).listFiles();
- Assert.assertEquals(4, files.length);
+ assertEquals(4, files.length);
}
@Test
@@ -141,64 +147,6 @@ public class ExportImportTest extends AbstractExportImportTest {
ExportImportUtil.assertDataImportedInRealm(adminClient, testingClient, testRealmRealm.toRepresentation());
}
- @Test
- public void testComponentExportImport() throws Throwable {
- RealmRepresentation realmRep = new RealmRepresentation();
- realmRep.setRealm("component-realm");
- adminClient.realms().create(realmRep);
- Assert.assertEquals(4, adminClient.realms().findAll().size());
- RealmResource realm = adminClient.realm("component-realm");
- realmRep = realm.toRepresentation();
- ComponentRepresentation component = new ComponentRepresentation();
- component.setProviderId("dummy");
- component.setProviderType("dummyType");
- component.setName("dummy-name");
- component.setParentId(realmRep.getId());
- component.setConfig(new MultivaluedHashMap<>());
- component.getConfig().add("name", "value");
- realm.components().add(component);
-
-
- testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
-
- String targetFilePath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "singleFile-realm.json";
- testingClient.testing().exportImport().setFile(targetFilePath);
- testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_EXPORT);
- testingClient.testing().exportImport().setRealmName("component-realm");
-
- testingClient.testing().exportImport().runExport();
-
- // Delete some realm (and some data in admin realm)
- adminClient.realm("component-realm").remove();
-
- Assert.assertEquals(3, adminClient.realms().findAll().size());
-
- // Configure import
- testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT);
-
- testingClient.testing().exportImport().runImport();
-
- realmRep = realm.toRepresentation();
-
- List<ComponentRepresentation> components = realm.components().query();
-
- Assert.assertEquals(1, components.size());
-
- component = components.get(0);
-
- Assert.assertEquals("dummy-name", component.getName());
- Assert.assertEquals("dummyType", component.getProviderType());
- Assert.assertEquals("dummy", component.getProviderId());
- Assert.assertEquals(realmRep.getId(), component.getParentId());
- Assert.assertEquals(1, component.getConfig().size());
- Assert.assertEquals("value", component.getConfig().getFirst("name"));
-
- adminClient.realm("component-realm").remove();
- }
-
-
-
-
private void removeRealm(String realmName) {
adminClient.realm(realmName).remove();
}
@@ -211,7 +159,7 @@ public class ExportImportTest extends AbstractExportImportTest {
removeRealm("test");
removeRealm("test-realm");
- Assert.assertEquals(1, adminClient.realms().findAll().size());
+ assertEquals(1, adminClient.realms().findAll().size());
assertNotAuthenticated("test", "test-user@localhost", "password");
assertNotAuthenticated("test", "user1", "password");
@@ -224,7 +172,7 @@ public class ExportImportTest extends AbstractExportImportTest {
testingClient.testing().exportImport().runImport();
// Ensure data are imported back
- Assert.assertEquals(3, adminClient.realms().findAll().size());
+ assertEquals(3, adminClient.realms().findAll().size());
assertAuthenticated("test", "test-user@localhost", "password");
assertAuthenticated("test", "user1", "password");
@@ -238,10 +186,13 @@ public class ExportImportTest extends AbstractExportImportTest {
testingClient.testing().exportImport().runExport();
+ List<ComponentRepresentation> components = adminClient.realm("test").components().query();
+ KeysMetadataRepresentation keyMetadata = adminClient.realm("test").keys().getKeyMetadata();
+
// Delete some realm (and some data in admin realm)
adminClient.realm("test").remove();
- Assert.assertEquals(2, adminClient.realms().findAll().size());
+ assertEquals(2, adminClient.realms().findAll().size());
assertNotAuthenticated("test", "test-user@localhost", "password");
assertNotAuthenticated("test", "user1", "password");
@@ -254,12 +205,18 @@ public class ExportImportTest extends AbstractExportImportTest {
testingClient.testing().exportImport().runImport();
// Ensure data are imported back, but just for "test" realm
- Assert.assertEquals(3, adminClient.realms().findAll().size());
+ assertEquals(3, adminClient.realms().findAll().size());
assertAuthenticated("test", "test-user@localhost", "password");
assertAuthenticated("test", "user1", "password");
assertAuthenticated("test", "user2", "password");
assertAuthenticated("test", "user3", "password");
+
+ List<ComponentRepresentation> componentsImported = adminClient.realm("test").components().query();
+ assertComponents(components, componentsImported);
+
+ KeysMetadataRepresentation keyMetadataImported = adminClient.realm("test").keys().getKeyMetadata();
+ assertEquals(keyMetadata.getActive(), keyMetadataImported.getActive());
}
private void assertAuthenticated(String realmName, String username, String password) {
@@ -271,7 +228,26 @@ public class ExportImportTest extends AbstractExportImportTest {
}
private void assertAuth(boolean expectedResult, String realmName, String username, String password) {
- Assert.assertEquals(expectedResult, testingClient.testing().validCredentials(realmName, username, password));
+ assertEquals(expectedResult, testingClient.testing().validCredentials(realmName, username, password));
+ }
+
+ private void assertComponents(List<ComponentRepresentation> expected, List<ComponentRepresentation> actual) {
+ expected.sort((o1, o2) -> o1.getId().compareTo(o2.getId()));
+ actual.sort((o1, o2) -> o1.getId().compareTo(o2.getId()));
+
+ assertEquals(expected.size(), actual.size());
+ for (int i = 0 ; i < expected.size(); i++) {
+ ComponentRepresentation e = expected.get(i);
+ ComponentRepresentation a = actual.get(i);
+
+ assertEquals(e.getId(), a.getId());
+ assertEquals(e.getName(), a.getName());
+ assertEquals(e.getProviderId(), a.getProviderId());
+ assertEquals(e.getProviderType(), a.getProviderType());
+ assertEquals(e.getParentId(), a.getParentId());
+ assertEquals(e.getConfig(), a.getConfig());
+ }
}
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 870af00..de89d57 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -193,7 +193,6 @@ public class ResetPasswordTest extends TestRealmKeycloakTest {
.session((String) null)
.detail(Details.EMAIL, "test-user@localhost").assertEvent();
-
loginPage.login("login@test.com", "password");
EventRepresentation loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/KeyRotationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/KeyRotationTest.java
new file mode 100644
index 0000000..fd4fdfe
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/KeyRotationTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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.testsuite.keys;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.common.VerificationException;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.keys.Attributes;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.keys.RsaKeyProviderFactory;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.KeysMetadataRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.AppPage.RequestType;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.util.OAuthClient;
+
+import javax.ws.rs.core.Response;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.List;
+
+import static org.junit.Assert.*;
+import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class KeyRotationTest extends AbstractKeycloakTest {
+
+ @Rule
+ public AssertEvents events = new AssertEvents(this);
+
+ @Page
+ protected AppPage appPage;
+
+ @Page
+ protected LoginPage loginPage;
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
+ testRealms.add(realm);
+ }
+
+ @Test
+ public void testIdentityCookie() throws Exception {
+ // Create keys #1
+ createKeys1();
+
+ // Login with keys #1
+ loginPage.open();
+ loginPage.login("test-user@localhost", "password");
+ assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+ // Create keys #2
+ createKeys2();
+
+ // Login again with cookie signed with old keys
+ appPage.open();
+ oauth.openLoginForm();
+ assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+ // Drop key #1
+ dropKeys1();
+
+ // Login again with key #1 dropped - should pass as cookie should be refreshed
+ appPage.open();
+ oauth.openLoginForm();
+ assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+ // Drop key #2
+ dropKeys2();
+
+ // Login again with key #2 dropped - should fail as cookie hasn't been refreshed
+ appPage.open();
+ oauth.openLoginForm();
+ assertTrue(loginPage.isCurrent());
+ }
+
+ @Test
+ public void testTokens() throws Exception {
+ // Create keys #1
+ PublicKey key1 = createKeys1();
+
+ // Get token with keys #1
+ oauth.doLogin("test-user@localhost", "password");
+ OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password");
+ assertEquals(200, response.getStatusCode());
+ assertTokenSignature(key1, response.getAccessToken());
+ assertTokenSignature(key1, response.getRefreshToken());
+
+ // Create keys #2
+ PublicKey key2 = createKeys2();
+
+ // Refresh token with keys #2
+ response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password");
+ assertEquals(200, response.getStatusCode());
+ assertTokenSignature(key2, response.getAccessToken());
+ assertTokenSignature(key2, response.getRefreshToken());
+
+ // Drop key #1
+ dropKeys1();
+
+ // Refresh token with keys #1 dropped - should pass as refresh token should be signed with key #2
+ response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password");
+ assertTokenSignature(key2, response.getAccessToken());
+ assertTokenSignature(key2, response.getRefreshToken());
+
+ // Drop key #2
+ dropKeys2();
+
+ // Refresh token with keys #2 dropped - should fail as refresh token is signed with key #2
+ response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password");
+ assertEquals(400, response.getStatusCode());
+ assertEquals("Invalid refresh token", response.getErrorDescription());
+ }
+
+ @Test
+ public void providerOrder() throws Exception {
+ PublicKey keys1 = createKeys1();
+ PublicKey keys2 = createKeys2();
+
+ KeysMetadataRepresentation keyMetadata = adminClient.realm("test").keys().getKeyMetadata();
+ assertEquals(PemUtils.encodeKey(keys2), keyMetadata.getKeys().get(0).getPublicKey());
+ }
+
+ @Test
+ public void rotateKeys() throws InterruptedException {
+ for (int i = 0; i < 10; i++) {
+ String activeKid = adminClient.realm("test").keys().getKeyMetadata().getActive().get("RSA");
+
+ // Rotate public keys on the parent broker
+ String realmId = adminClient.realm("test").toRepresentation().getId();
+ ComponentRepresentation keys = new ComponentRepresentation();
+ keys.setName("generated" + i);
+ keys.setProviderType(KeyProvider.class.getName());
+ keys.setProviderId("rsa-generated");
+ keys.setParentId(realmId);
+ keys.setConfig(new MultivaluedHashMap<>());
+ keys.getConfig().putSingle("priority", "1000" + i);
+ Response response = adminClient.realm("test").components().add(keys);
+ assertEquals(201, response.getStatus());
+ response.close();
+
+ String updatedActiveKid = adminClient.realm("test").keys().getKeyMetadata().getActive().get("RSA");
+ assertNotEquals(activeKid, updatedActiveKid);
+ }
+ }
+
+
+ static void assertTokenSignature(PublicKey expectedKey, String token) {
+ String kid = null;
+ try {
+ RSATokenVerifier verifier = RSATokenVerifier.create(token).checkTokenType(false).checkRealmUrl(false).checkActive(false).publicKey(expectedKey);
+ kid = verifier.getHeader().getKeyId();
+ verifier.verify();
+ } catch (VerificationException e) {
+ fail("Token not signed by expected keys, kid was " + kid);
+ }
+ }
+
+
+ private PublicKey createKeys1() throws Exception {
+ return createKeys("1000");
+ }
+
+ private PublicKey createKeys2() throws Exception {
+ return createKeys("2000");
+ }
+
+ private PublicKey createKeys(String priority) throws Exception {
+ KeyPair keyPair = KeyUtils.generateRsaKeyPair(1024);
+ String privateKeyPem = PemUtils.encodeKey(keyPair.getPrivate());
+ PublicKey publicKey = keyPair.getPublic();
+
+ ComponentRepresentation rep = new ComponentRepresentation();
+ rep.setName("mycomponent");
+ rep.setParentId("test");
+ rep.setProviderId(RsaKeyProviderFactory.ID);
+ rep.setProviderType(KeyProvider.class.getName());
+
+ org.keycloak.common.util.MultivaluedHashMap config = new org.keycloak.common.util.MultivaluedHashMap();
+ config.addFirst("priority", priority);
+ config.addFirst(Attributes.PRIVATE_KEY_KEY, privateKeyPem);
+ rep.setConfig(config);
+
+ adminClient.realm("test").components().add(rep);
+
+ return publicKey;
+ }
+
+ private void dropKeys1() {
+ dropKeys("1000");
+ }
+
+ private void dropKeys2() {
+ dropKeys("2000");
+ }
+
+ private void dropKeys(String priority) {
+ for (ComponentRepresentation c : adminClient.realm("test").components().query("test", KeyProvider.class.getName())) {
+ if (c.getConfig().getFirst("priority").equals(priority)) {
+ adminClient.realm("test").components().component(c.getId()).remove();
+ return;
+ }
+ }
+ throw new RuntimeException("Failed to find keys1");
+ }
+
+}
+
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java
new file mode 100644
index 0000000..649c662
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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.testsuite.keys;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.common.VerificationException;
+import org.keycloak.common.util.CertificateUtils;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.keys.Attributes;
+import org.keycloak.keys.KeyMetadata;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.keys.RsaKeyProvider;
+import org.keycloak.keys.RsaKeyProviderFactory;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.ErrorRepresentation;
+import org.keycloak.representations.idm.KeysMetadataRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.AppPage.RequestType;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.util.OAuthClient;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import static org.junit.Assert.*;
+import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RsaKeyProviderTest extends AbstractKeycloakTest {
+
+ @Rule
+ public AssertEvents events = new AssertEvents(this);
+
+ @Page
+ protected AppPage appPage;
+
+ @Page
+ protected LoginPage loginPage;
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
+ testRealms.add(realm);
+ }
+
+ @Test
+ public void privateKeyOnly() throws Exception {
+ KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
+ String kid = KeyUtils.createKeyId(keyPair.getPublic());
+
+ ComponentRepresentation rep = createRep("valid", RsaKeyProviderFactory.ID);
+ rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
+ rep.getConfig().putSingle(Attributes.PRIORITY_KEY, "1000");
+
+ Response response = adminClient.realm("test").components().add(rep);
+ String id = ApiUtil.getCreatedId(response);
+
+ ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation();
+ assertEquals(ComponentRepresentation.SECRET_VALUE, createdRep.getConfig().getFirst(Attributes.PRIVATE_KEY_KEY));
+ assertNotNull(createdRep.getConfig().getFirst(Attributes.CERTIFICATE_KEY));
+
+ assertEquals(keyPair.getPublic(), PemUtils.decodeCertificate(createdRep.getConfig().getFirst(Attributes.CERTIFICATE_KEY)).getPublicKey());
+
+ KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata();
+
+ assertEquals(kid, keys.getActive().get(KeyMetadata.Type.RSA.name()));
+
+ KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);
+
+ assertEquals(id, key.getProviderId());
+ assertEquals(KeyMetadata.Type.RSA.name(), key.getType());
+ assertEquals(1000l, key.getProviderPriority());
+ assertEquals(kid, key.getKid());
+ assertEquals(PemUtils.encodeKey(keyPair.getPublic()), keys.getKeys().get(0).getPublicKey());
+ assertEquals(keyPair.getPublic(), PemUtils.decodeCertificate(key.getCertificate()).getPublicKey());
+ }
+
+ @Test
+ public void keyAndCertificate() throws Exception {
+ KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
+ Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, "test");
+ String certificatePem = PemUtils.encodeCertificate(certificate);
+
+ ComponentRepresentation rep = createRep("valid", RsaKeyProviderFactory.ID);
+ rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
+ rep.getConfig().putSingle(Attributes.CERTIFICATE_KEY, certificatePem);
+ rep.getConfig().putSingle(Attributes.PRIORITY_KEY, "1000");
+
+ Response response = adminClient.realm("test").components().add(rep);
+ String id = ApiUtil.getCreatedId(response);
+
+ ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation();
+ assertEquals(ComponentRepresentation.SECRET_VALUE, createdRep.getConfig().getFirst(Attributes.PRIVATE_KEY_KEY));
+ assertEquals(certificatePem, createdRep.getConfig().getFirst(Attributes.CERTIFICATE_KEY));
+
+ KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata();
+
+ KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);
+ assertEquals(certificatePem, key.getCertificate());
+ }
+
+ @Test
+ public void invalidPriority() throws Exception {
+ KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
+
+ ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID);
+ rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
+ rep.getConfig().putSingle(Attributes.PRIORITY_KEY, "invalid");
+
+ Response response = adminClient.realm("test").components().add(rep);
+ assertErrror(response, "Priority should be a number");
+ }
+
+ @Test
+ public void invalidEnabled() throws Exception {
+ KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
+
+ ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID);
+ rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
+ rep.getConfig().putSingle(Attributes.ENABLED_KEY, "invalid");
+
+ Response response = adminClient.realm("test").components().add(rep);
+ assertErrror(response, "Enabled should be 'true' or 'false'");
+ }
+
+ @Test
+ public void invalidActive() throws Exception {
+ KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
+
+ ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID);
+ rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
+ rep.getConfig().putSingle(Attributes.ACTIVE_KEY, "invalid");
+
+ Response response = adminClient.realm("test").components().add(rep);
+ assertErrror(response, "Active should be 'true' or 'false'");
+ }
+
+ @Test
+ public void invalidPrivateKey() throws Exception {
+ KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
+
+ ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID);
+
+ Response response = adminClient.realm("test").components().add(rep);
+ assertErrror(response, "Private RSA Key is required");
+
+ rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, "nonsense");
+ response = adminClient.realm("test").components().add(rep);
+ assertErrror(response, "Failed to decode private key");
+
+ rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPublic()));
+ response = adminClient.realm("test").components().add(rep);
+ assertErrror(response, "Failed to decode private key");
+ }
+
+ @Test
+ public void invalidCertificate() throws Exception {
+ KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
+ Certificate invalidCertificate = CertificateUtils.generateV1SelfSignedCertificate(KeyUtils.generateRsaKeyPair(2048), "test");
+
+ ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID);
+ rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
+
+ rep.getConfig().putSingle(Attributes.CERTIFICATE_KEY, "nonsense");
+ Response response = adminClient.realm("test").components().add(rep);
+ assertErrror(response, "Failed to decode certificate");
+
+ rep.getConfig().putSingle(Attributes.CERTIFICATE_KEY, PemUtils.encodeCertificate(invalidCertificate));
+ response = adminClient.realm("test").components().add(rep);
+ assertErrror(response, "Certificate does not match private key");
+
+ }
+
+ protected void assertErrror(Response response, String error) {
+ if (!response.hasEntity()) {
+ fail("No error message set");
+ }
+
+ ErrorRepresentation errorRepresentation = response.readEntity(ErrorRepresentation.class);
+ assertEquals(error, errorRepresentation.getErrorMessage());
+ }
+
+ protected ComponentRepresentation createRep(String name, String providerId) {
+ ComponentRepresentation rep = new ComponentRepresentation();
+ rep.setName(name);
+ rep.setParentId("test");
+ rep.setProviderId(providerId);
+ rep.setProviderType(KeyProvider.class.getName());
+ rep.setConfig(new MultivaluedHashMap<>());
+ return rep;
+ }
+
+}
+
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index 99ee7c6..c05bc7e 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -50,10 +50,6 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
@Rule
public AssertEvents events = new AssertEvents(this);
- @Page
- protected ErrorPage errorPage;
-
-
@Override
public void beforeAbstractKeycloakTest() throws Exception {
super.beforeAbstractKeycloakTest();
@@ -61,11 +57,8 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
-
RealmRepresentation realmRepresentation = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
-
testRealms.add(realmRepresentation);
-
}
@Before
@@ -85,8 +78,6 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
assertEquals("OpenIdConnect.AuthenticationProperties=2302984sdlk", response.getState());
Assert.assertNull(response.getError());
- testingClient.testing().verifyCode("test", response.getCode());
-
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
assertCode(codeId, response.getCode());
}
@@ -102,7 +93,6 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
Assert.assertEquals("Success code", title);
String code = driver.findElement(By.id(OAuth2Constants.CODE)).getAttribute("value");
- testingClient.testing().verifyCode("test", code);
String codeId = events.expectLogin().detail(Details.REDIRECT_URI, "http://localhost:8180/auth/realms/test/protocol/openid-connect/oauth/oob").assertEvent().getDetails().get(Details.CODE_ID);
assertCode(codeId, code);
@@ -121,8 +111,6 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
Assert.assertTrue(response.isRedirected());
Assert.assertNotNull(response.getCode());
- testingClient.testing().verifyCode("test", response.getCode());
-
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
assertCode(codeId, response.getCode());
}
@@ -138,8 +126,6 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
Assert.assertNull(response.getState());
Assert.assertNull(response.getError());
- testingClient.testing().verifyCode("test", response.getCode());
-
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
assertCode(codeId, response.getCode());
}
@@ -172,14 +158,12 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
assertEquals("OpenIdConnect.AuthenticationProperties=2302984sdlk", state);
- testingClient.testing().verifyCode("test", code);
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
assertCode(codeId, code);
}
private void assertCode(String expectedCodeId, String actualCode) {
- String code = testingClient.testing().verifyCode("test", actualCode);
- assertEquals(expectedCodeId, code);
+ assertEquals(expectedCodeId, actualCode.split("\\.")[1]);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
index 520f910..c0c8601 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
@@ -16,7 +16,11 @@
*/
package org.keycloak.testsuite.oauth;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
import org.jboss.arquillian.graphene.page.Page;
+import org.jgroups.protocols.TP;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
@@ -34,6 +38,8 @@ import org.keycloak.testsuite.util.RealmBuilder;
import org.openqa.selenium.By;
import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
import java.net.URL;
import java.util.List;
@@ -53,10 +59,33 @@ public class OAuthRedirectUriTest extends AbstractKeycloakTest {
protected ErrorPage errorPage;
@Page
protected LoginPage loginPage;
+ private HttpServer server;
@Override
public void beforeAbstractKeycloakTest() throws Exception {
super.beforeAbstractKeycloakTest();
+
+ server = HttpServer.create(new InetSocketAddress(8280), 0);
+ server.createContext("/", new MyHandler());
+ server.setExecutor(null); // creates a default executor
+ server.start();
+ }
+
+ @Override
+ public void afterAbstractKeycloakTest() {
+ super.afterAbstractKeycloakTest();
+
+ server.stop(0);
+ }
+
+ static class MyHandler implements HttpHandler {
+ public void handle(HttpExchange t) throws IOException {
+ String response = "Hello";
+ t.sendResponseHeaders(200, response.length());
+ OutputStream os = t.getResponseBody();
+ os.write(response.getBytes());
+ os.close();
+ }
}
@Override
@@ -76,24 +105,24 @@ public class OAuthRedirectUriTest extends AbstractKeycloakTest {
realm.client(installedApp2);
ClientBuilder installedApp3 = ClientBuilder.create().id("test-wildcard").name("test-wildcard")
- .redirectUris("http://example.com/foo/*", "http://with-dash.example.com/foo/*", "http://localhost:8180/foo/*")
+ .redirectUris("http://example.com/foo/*", "http://with-dash.example.local/foo/*", "http://localhost:8280/foo/*")
.secret("password");
realm.client(installedApp3);
ClientBuilder installedApp4 = ClientBuilder.create().id("test-dash").name("test-dash")
- .redirectUris("http://with-dash.example.com", "http://with-dash.example.com/foo")
+ .redirectUris("http://with-dash.example.local", "http://with-dash.example.local/foo")
.secret("password");
realm.client(installedApp4);
ClientBuilder installedApp5 = ClientBuilder.create().id("test-root-url").name("test-root-url")
- .rootUrl("http://with-dash.example.com")
+ .rootUrl("http://with-dash.example.local")
.redirectUris("/foo")
.secret("password");
realm.client(installedApp5);
ClientBuilder installedApp6 = ClientBuilder.create().id("test-relative-url").name("test-relative-url")
.rootUrl("")
- .redirectUris("/foo")
+ .redirectUris("/auth")
.secret("password");
realm.client(installedApp6);
@@ -217,46 +246,46 @@ public class OAuthRedirectUriTest extends AbstractKeycloakTest {
checkRedirectUri("http://localhost:8080", false, true);
checkRedirectUri("http://example.com/foo", true);
checkRedirectUri("http://example.com/foo/bar", true);
- checkRedirectUri("http://localhost:8180/foo", true, true);
- checkRedirectUri("http://localhost:8180/foo/bar", true, true);
+ checkRedirectUri("http://localhost:8280/foo", true, true);
+ checkRedirectUri("http://localhost:8280/foo/bar", true, true);
checkRedirectUri("http://example.com/foobar", false);
- checkRedirectUri("http://localhost:8180/foobar", false, true);
+ checkRedirectUri("http://localhost:8280/foobar", false, true);
}
@Test
public void testDash() throws IOException {
oauth.clientId("test-dash");
- checkRedirectUri("http://with-dash.example.com/foo", true);
+ checkRedirectUri("http://with-dash.example.local/foo", true);
}
@Test
public void testDifferentCaseInHostname() throws IOException {
oauth.clientId("test-dash");
- checkRedirectUri("http://with-dash.example.com", true);
- checkRedirectUri("http://wiTh-dAsh.example.com", true);
- checkRedirectUri("http://with-dash.example.com/foo", true);
- checkRedirectUri("http://wiTh-dAsh.example.com/foo", true);
- checkRedirectUri("http://with-dash.eXampLe.com/foo", true);
- checkRedirectUri("http://wiTh-dAsh.eXampLe.com/foo", true);
- checkRedirectUri("http://wiTh-dAsh.eXampLe.com/Foo", false);
- checkRedirectUri("http://wiTh-dAsh.eXampLe.com/foO", false);
+ checkRedirectUri("http://with-dash.example.local", true);
+ checkRedirectUri("http://wiTh-dAsh.example.local", true);
+ checkRedirectUri("http://with-dash.example.local/foo", true);
+ checkRedirectUri("http://wiTh-dAsh.example.local/foo", true);
+ checkRedirectUri("http://with-dash.example.local/foo", true);
+ checkRedirectUri("http://wiTh-dAsh.example.local/foo", true);
+ checkRedirectUri("http://wiTh-dAsh.example.local/Foo", false);
+ checkRedirectUri("http://wiTh-dAsh.example.local/foO", false);
}
@Test
public void testDifferentCaseInScheme() throws IOException {
oauth.clientId("test-dash");
- checkRedirectUri("HTTP://with-dash.example.com", true);
- checkRedirectUri("Http://wiTh-dAsh.example.com", true);
+ checkRedirectUri("HTTP://with-dash.example.local", true);
+ checkRedirectUri("Http://wiTh-dAsh.example.local", true);
}
@Test
public void testRelativeWithRoot() throws IOException {
oauth.clientId("test-root-url");
- checkRedirectUri("http://with-dash.example.com/foo", true);
+ checkRedirectUri("http://with-dash.example.local/foo", true);
checkRedirectUri("http://localhost:8180/foo", false);
}
@@ -264,8 +293,8 @@ public class OAuthRedirectUriTest extends AbstractKeycloakTest {
public void testRelative() throws IOException {
oauth.clientId("test-relative-url");
- checkRedirectUri("http://with-dash.example.com/foo", false);
- checkRedirectUri("http://localhost:8180/foo", true);
+ checkRedirectUri("http://with-dash.example.local/foo", false);
+ checkRedirectUri("http://localhost:8180/auth", true);
}
@Test
@@ -296,30 +325,18 @@ public class OAuthRedirectUriTest extends AbstractKeycloakTest {
private void checkRedirectUri(String redirectUri, boolean expectValid, boolean checkCodeToToken) throws IOException {
oauth.redirectUri(redirectUri);
- oauth.openLoginForm();
- if (expectValid) {
- Assert.assertTrue(loginPage.isCurrent());
- } else {
+ if (!expectValid) {
+ oauth.openLoginForm();
Assert.assertTrue(errorPage.isCurrent());
Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
- }
-
- if (expectValid) {
- Assert.assertTrue(loginPage.isCurrent());
-
- if (checkCodeToToken) {
+ } else {
+ if (!checkCodeToToken) {
+ oauth.openLoginForm();
+ Assert.assertTrue(loginPage.isCurrent());
+ } else {
oauth.doLogin("test-user@localhost", "password");
- /*
- * Dirty workaround. For some reason the form is not being submitted when you have
- * redirectUri like http://localhost:8180 or http://localhost:8180/myapp
- * TODO: Revisit this, because it's a weird behavior
- */
- if (driver.findElements(By.name("login")).size() != 0) {
- driver.findElement(By.name("login")).click();
- }
-
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
Assert.assertNotNull(code);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
index 517cc67..208e6e3 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
@@ -274,38 +274,6 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
String publicKey;
@Test
- public void refreshTokenRealmKeysChanged() throws Exception {
- oauth.doLogin("test-user@localhost", "password");
-
- EventRepresentation loginEvent = events.expectLogin().assertEvent();
-
- String sessionId = loginEvent.getSessionId();
- String codeId = loginEvent.getDetails().get(Details.CODE_ID);
-
- String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
-
- OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
- String refreshTokenString = response.getRefreshToken();
- RefreshToken refreshToken = oauth.verifyRefreshToken(refreshTokenString);
-
- events.expectCodeToToken(codeId, sessionId).assertEvent();
-
- try {
-
- RealmManager.realm(adminClient.realm("test")).generateKeys();
-
- response = oauth.doRefreshTokenRequest(refreshTokenString, "password");
-
- assertEquals(400, response.getStatusCode());
- assertEquals("invalid_grant", response.getError());
-
- events.expectRefresh(refreshToken.getId(), sessionId).user((String) null).session((String) null).clearDetails().error(Errors.INVALID_TOKEN).assertEvent();
- } finally {
- RealmManager.realm(adminClient.realm("test")).keyPair(privateKey, publicKey);
- }
- }
-
- @Test
public void refreshTokenClientDisabled() throws Exception {
oauth.doLogin("test-user@localhost", "password");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java
index e557c08..85bd77f 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java
@@ -173,7 +173,7 @@ public class TokenIntrospectionTest extends TestRealmKeycloakTest {
@Test
public void testInactiveAccessToken() throws Exception {
oauth.doLogin("test-user@localhost", "password");
- String inactiveAccessToken = "eyJhbGciOiJSUzI1NiJ9.eyJub25jZSI6IjczMGZjNjQ1LTBlMDQtNDE3Yi04MDY0LTkyYWIyY2RjM2QwZSIsImp0aSI6ImU5ZGU1NjU2LWUzMjctNDkxNC1hNjBmLTI1MzJlYjBiNDk4OCIsImV4cCI6MTQ1MjI4MTAwMCwibmJmIjowLCJpYXQiOjE0NTIyODA3MDAsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9leGFtcGxlIiwiYXVkIjoianMtY29uc29sZSIsInN1YiI6IjFkNzQ0MDY5LWYyOTgtNGU3Yy1hNzNiLTU1YzlhZjgzYTY4NyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImpzLWNvbnNvbGUiLCJzZXNzaW9uX3N0YXRlIjoiNzc2YTA0OTktODNjNC00MDhkLWE5YjctYTZiYzQ5YmQ3MThjIiwiY2xpZW50X3Nlc3Npb24iOiJjN2Y5ODczOC05MDhlLTQxOWYtYTdkNC1kODYxYjRhYTI3NjkiLCJhbGxvd2VkLW9yaWdpbnMiOltdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsidXNlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJ2aWV3LXByb2ZpbGUiXX19LCJuYW1lIjoiU2FtcGxlIFVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyIiwiZ2l2ZW5fbmFtZSI6IlNhbXBsZSIsImZhbWlseV9uYW1lIjoiVXNlciIsImVtYWlsIjoic2FtcGxlLXVzZXJAZXhhbXBsZSJ9.YyPV74j9CqOG2Jmq692ZZpqycjNpUgtYVRfQJccS_FU84tGVXoKKsXKYeY2UJ1Y_bPiYG1I1J6JSXC8XqgQijCG7Nh7oK0yN74JbRN58HG75fvg6K9BjR6hgJ8mHT8qPrCux2svFucIMIZ180eoBoRvRstkidOhl_mtjT_i31fU";
+ String inactiveAccessToken = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJGSjg2R2NGM2pUYk5MT2NvNE52WmtVQ0lVbWZZQ3FvcXRPUWVNZmJoTmxFIn0.eyJqdGkiOiI5NjgxZTRlOC01NzhlLTQ3M2ItOTIwNC0yZWE5OTdhYzMwMTgiLCJleHAiOjE0NzYxMDY4NDksIm5iZiI6MCwiaWF0IjoxNDc2MTA2NTQ5LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgxODAvYXV0aC9yZWFsbXMvdGVzdCIsImF1ZCI6InRlc3QtYXBwIiwic3ViIjoiZWYyYzk0NjAtZDRkYy00OTk5LWJlYmUtZWVmYWVkNmJmMGU3IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdC1hcHAiLCJhdXRoX3RpbWUiOjE0NzYxMDY1NDksInNlc3Npb25fc3RhdGUiOiI1OGY4M2MzMi03MDhkLTQzNjktODhhNC05YjI5OGRjMDY5NzgiLCJhY3IiOiIxIiwiY2xpZW50X3Nlc3Npb24iOiI2NTYyOTVkZC1kZWNkLTQyZDAtYWJmYy0zZGJjZjJlMDE3NzIiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDo4MTgwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJ1c2VyIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsidGVzdC1hcHAiOnsicm9sZXMiOlsiY3VzdG9tZXItdXNlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsInZpZXctcHJvZmlsZSJdfX0sIm5hbWUiOiJUb20gQnJhZHkiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0LXVzZXJAbG9jYWxob3N0IiwiZ2l2ZW5fbmFtZSI6IlRvbSIsImZhbWlseV9uYW1lIjoiQnJhZHkiLCJlbWFpbCI6InRlc3QtdXNlckBsb2NhbGhvc3QifQ.LYU7opqZsc9e-ZmdsIhcecjHL3kQkpP13VpwO4MHMqEVNeJsZI1WOkTM5HGVAihcPfQazhaYvcik0gFTF_6ZcKzDqanjx80TGhSIrV5FoCeUrbp7w_66VKDH7ImPc8T2kICQGHh2d521WFBnvXNifw7P6AR1rGg4qrUljHdf_KU";
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", inactiveAccessToken);
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(tokenResponse);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java
index e0588e7..e6ec392 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java
@@ -22,6 +22,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.common.util.PemUtils;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
@@ -190,8 +191,7 @@ public class UserInfoTest extends AbstractKeycloakTest {
.assertEvent();
// Check signature and content
- RealmRepresentation realmRep = adminClient.realm("test").toRepresentation();
- PublicKey publicKey = KeycloakModelUtils.getPublicKey(realmRep.getPublicKey());
+ PublicKey publicKey = PemUtils.decodePublicKey(ApiUtil.findActiveKey(adminClient.realm("test")).getPublicKey());
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(response.getHeaderString(HttpHeaders.CONTENT_TYPE), MediaType.APPLICATION_JWT);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties
index 6a891c3..7fa4d6e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties
@@ -29,6 +29,9 @@ log4j.logger.org.keycloak=off, keycloak
log4j.logger.org.jboss.resteasy.resteasy_jaxrs.i18n=off
+#log4j.logger.org.keycloak.keys.DefaultKeyManager=trace
+#log4j.logger.org.keycloak.services.managers.AuthenticationManager=trace
+
log4j.logger.org.keycloak.testsuite=debug, testsuite
log4j.additivity.org.keycloak.testsuite=false
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index e9f3449..3abd719 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -1151,3 +1151,14 @@ authz-evaluation-policies.tooltip=Details about which policies were evaluated an
authz-evaluation-authorization-data=Response
authz-evaluation-authorization-data.tooltip=Represents a token carrying authorization data as a result of the processing of an authorization request. This representation is basically what Keycloak issues to clients asking for permissions. Check the 'authorization' claim for the permissions that were granted based on the current authorization request.
authz-show-authorization-data=Show Authorization Data
+
+kid=KID
+keys=Keys
+all=All
+status=Status
+keystore=Keystore
+keystores=Keystores
+add-keystore=Add Keystore
+add-keystore.placeholder=Add keystore...
+view=View
+active=Active
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js
index c3b25ec..cae3e36 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -248,14 +248,84 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'ClientRegistrationTrustedHostDetailCtrl'
})
- .when('/realms/:realm/keys-settings', {
+ .when('/realms/:realm/keys', {
templateUrl : resourceUrl + '/partials/realm-keys.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
+ },
+ serverInfo : function(ServerInfoLoader) {
+ return ServerInfoLoader();
+ },
+ keys: function(RealmKeysLoader) {
+ return RealmKeysLoader();
+ }
+ },
+ controller : 'RealmKeysCtrl'
+ })
+ .when('/realms/:realm/keys/list', {
+ templateUrl : resourceUrl + '/partials/realm-keys-list.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ serverInfo : function(ServerInfoLoader) {
+ return ServerInfoLoader();
+ },
+ keys: function(RealmKeysLoader) {
+ return RealmKeysLoader();
+ }
+ },
+ controller : 'RealmKeysCtrl'
+ })
+ .when('/realms/:realm/keys/providers', {
+ templateUrl : resourceUrl + '/partials/realm-keys-providers.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ serverInfo : function(ServerInfoLoader) {
+ return ServerInfoLoader();
}
},
- controller : 'RealmKeysDetailCtrl'
+ controller : 'RealmKeysProvidersCtrl'
+ })
+ .when('/create/keys/:realm/providers/:provider', {
+ templateUrl : resourceUrl + '/partials/realm-keys-generic.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ instance : function() {
+ return {
+ };
+ },
+ providerId : function($route) {
+ return $route.current.params.provider;
+ },
+ serverInfo : function(ServerInfoLoader) {
+ return ServerInfoLoader();
+ }
+ },
+ controller : 'GenericKeystoreCtrl'
+ })
+ .when('/realms/:realm/keys/providers/:provider/:componentId', {
+ templateUrl : resourceUrl + '/partials/realm-keys-generic.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ instance : function(ComponentLoader) {
+ return ComponentLoader();
+ },
+ providerId : function($route) {
+ return $route.current.params.provider;
+ },
+ serverInfo : function(ServerInfoLoader) {
+ return ServerInfoLoader();
+ }
+ },
+ controller : 'GenericKeystoreCtrl'
})
.when('/realms/:realm/identity-provider-settings', {
templateUrl : resourceUrl + '/partials/realm-identity-provider.html',
@@ -2365,6 +2435,9 @@ module.controller('RoleSelectorModalCtrl', function($scope, realm, config, confi
});
module.controller('ProviderConfigCtrl', function ($modal, $scope) {
+ $scope.fileNames = {};
+
+
$scope.openRoleSelector = function (configName, config) {
$modal.open({
templateUrl: resourceUrl + '/partials/modal/role-selector.html',
@@ -2382,6 +2455,17 @@ module.controller('ProviderConfigCtrl', function ($modal, $scope) {
}
})
}
+
+ $scope.uploadFile = function($files, optionName, config) {
+ var reader = new FileReader();
+ reader.onload = function(e) {
+ $scope.$apply(function() {
+ config[optionName][0] = e.target.result;
+ });
+ };
+ reader.readAsText($files[0]);
+ $scope.fileNames[optionName] = $files[0].name;
+ }
});
module.directive('kcProviderConfig', function ($modal) {
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 1589ffd..8237749 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -1054,99 +1054,167 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
};
});
-module.controller('RealmKeysDetailCtrl', function($scope, Realm, realm, $http, $route, $location, Dialog, Notifications) {
+module.controller('ViewKeyCtrl', function($scope, key) {
+ $scope.key = key;
+});
+
+module.controller('RealmKeysCtrl', function($scope, Realm, realm, $http, $route, $location, Dialog, Notifications, serverInfo, keys, Components, $modal) {
$scope.realm = angular.copy(realm);
- $scope.enableUpload = false;
+ $scope.keys = keys.keys;
+ $scope.active = {};
+
+ Components.query({realm: realm.realm,
+ parent: realm.id,
+ type: 'org.keycloak.keys.KeyProvider'
+ }, function(data) {
+ for (var i = 0; i < keys.keys.length; i++) {
+ for (var j = 0; j < data.length; j++) {
+ if (keys.keys[i].providerId == data[j].id) {
+ keys.keys[i].provider = data[j];
+ }
+ }
+ }
- $scope.$watch('realm', function () {
- if (!angular.equals($scope.realm, realm)) {
- if ($scope.realm.privateKey && $scope.realm.publicKey != realm.publicKey) {
- $scope.enableUpload = true;
- } else if ($scope.realm.certificate != realm.certificate) {
- $scope.enableUpload = true;
- } else {
- $scope.enableUpload = false;
+ for (var t in keys.active) {
+ for (var i = 0; i < keys.keys.length; i++) {
+ if (keys.active[t] == keys.keys[i].kid) {
+ $scope.active[t] = keys.keys[i];
+ }
}
}
- }, true);
+ });
- $scope.generate = function() {
- Dialog.confirmGenerateKeys($scope.realm.realm, 'realm', function() {
- Realm.update({ realm: realm.realm, publicKey : 'GENERATE' }, function () {
- $route.reload();
- Notifications.success('New keys generated for realm.');
- });
- });
- };
+ $scope.viewKey = function(key) {
+ $modal.open({
+ templateUrl: resourceUrl + '/partials/modal/view-key.html',
+ controller: 'ViewKeyCtrl',
+ resolve: {
+ key: function () {
+ return key;
+ }
+ }
+ })
+ }
+});
- $scope.privateKeyUpload = function($files){
- var reader = new FileReader();
- reader.onload = function(e) {
- $scope.$apply(function() {
- $scope.privateKeyUploadContent = e.target.result;
- });
- };
- reader.readAsText($files[0]);
- $scope.privateKeyUploadName = $files[0].name;
- };
+module.controller('RealmKeysProvidersCtrl', function($scope, Realm, realm, $http, $route, $location, Dialog, Notifications, serverInfo, Components, $modal) {
+ $scope.realm = angular.copy(realm);
+ $scope.enableUpload = false;
- $scope.publicKeyUpload = function($files){
- var reader = new FileReader();
- reader.onload = function(e) {
- $scope.$apply(function() {
- $scope.publicKeyUploadContent = e.target.result;
- });
- };
- reader.readAsText($files[0]);
- $scope.publicKeyUploadName = $files[0].name;
+ $scope.providers = serverInfo.componentTypes['org.keycloak.keys.KeyProvider'];
+
+ Components.query({realm: realm.realm,
+ parent: realm.id,
+ type: 'org.keycloak.keys.KeyProvider'
+ }, function(data) {
+ console.debug(data);
+ $scope.instances = data;
+ });
+
+ $scope.addProvider = function(provider) {
+ console.log('Add provider: ' + provider.id);
+ $location.url("/create/keys/" + realm.realm + "/providers/" + provider.id);
};
- $scope.certificateUpload = function($files){
- var reader = new FileReader();
- reader.onload = function(e) {
- $scope.$apply(function() {
- $scope.certificateUploadContent = e.target.result;
+ $scope.removeInstance = function(instance) {
+ Dialog.confirmDelete(instance.name, 'key provider', function() {
+ Components.remove({
+ realm : realm.realm,
+ componentId : instance.id
+ }, function() {
+ $route.reload();
+ Notifications.success("The provider has been deleted.");
});
- };
- reader.readAsText($files[0]);
- $scope.certificateUploadName = $files[0].name;
+ });
};
+});
- $scope.clearImport = function() {
- $route.reload();
+module.controller('GenericKeystoreCtrl', function($scope, $location, Notifications, $route, Dialog, realm, serverInfo, instance, providerId, Components) {
+ $scope.create = !instance.providerId;
+ $scope.realm = realm;
+
+ var providers = serverInfo.componentTypes['org.keycloak.keys.KeyProvider'];
+ var providerFactory = null;
+ for (var i = 0; i < providers.length; i++) {
+ var p = providers[i];
+ if (p.id == providerId) {
+ $scope.providerFactory = p;
+ providerFactory = p;
+ break;
+ }
}
- $scope.import = function() {
- var title = 'Upload keys for realm';
- var msg = 'Are you sure you want to upload keys for ' + $scope.realm.realm + '?';
- var btns = {
- ok: {
- label: 'Upload Keys',
- cssClass: 'btn btn-danger'
- },
- cancel: {
- label: 'Cancel',
- cssClass: 'btn btn-default'
+ if ($scope.create) {
+ $scope.instance = {
+ name: providerFactory.id,
+ providerId: providerFactory.id,
+ providerType: 'org.keycloak.keys.KeyProvider',
+ parentId: realm.id,
+ config: {
+ 'priority': ["0"]
}
- };
-
- Dialog.open(title, msg, btns, function() {
- var upload = { realm : $scope.realm.realm };
+ }
- if ($scope.privateKeyUploadContent && $scope.publicKeyUploadContent) {
- upload.privateKey = $scope.privateKeyUploadContent;
- upload.publicKey = $scope.publicKeyUploadContent;
+ if (providerFactory.properties) {
+ for (var i = 0; i < providerFactory.properties.length; i++) {
+ var configProperty = providerFactory.properties[i];
+ if (configProperty.defaultValue) {
+ $scope.instance.config[configProperty.name] = [configProperty.defaultValue];
+ } else {
+ $scope.instance.config[configProperty.name] = [''];
+ }
}
+ }
+ } else {
+ $scope.instance = angular.copy(instance);
+ }
- if ($scope.certificateUploadContent) {
- upload.certificate = $scope.certificateUploadContent;
- }
+ $scope.$watch('instance', function() {
+ if (!angular.equals($scope.instance, instance)) {
+ $scope.changed = true;
+ }
- Realm.update(upload, function () {
- $route.reload();
- Notifications.success('Keys imported for realm.');
+ }, true);
+
+ $scope.save = function() {
+ $scope.changed = false;
+ if ($scope.create) {
+ Components.save({realm: realm.realm}, $scope.instance, function (data, headers) {
+ var l = headers().location;
+ var id = l.substring(l.lastIndexOf("/") + 1);
+
+ $location.url("/realms/" + realm.realm + "/keys/providers/" + $scope.instance.providerId + "/" + id);
+ Notifications.success("The provider has been created.");
+ }, function (errorResponse) {
+ if (errorResponse.data && errorResponse.data['error_description']) {
+ Notifications.error(errorResponse.data['error_description']);
+ }
});
- });
+ } else {
+ Components.update({realm: realm.realm,
+ componentId: instance.id
+ },
+ $scope.instance, function () {
+ $route.reload();
+ Notifications.success("The provider has been updated.");
+ }, function (errorResponse) {
+ if (errorResponse.data && errorResponse.data['error_description']) {
+ Notifications.error(errorResponse.data['error_description']);
+ }
+ });
+ }
+ };
+
+ $scope.reset = function() {
+ $route.reload();
+ };
+
+ $scope.cancel = function() {
+ if ($scope.create) {
+ $location.url("/realms/" + realm.realm + "/keys");
+ } else {
+ $route.reload();
+ }
};
});
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/themes/src/main/resources/theme/base/admin/resources/js/loaders.js
index 4b76863..7e481d3 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/loaders.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/loaders.js
@@ -49,6 +49,14 @@ module.factory('RealmLoader', function(Loader, Realm, $route, $q) {
});
});
+module.factory('RealmKeysLoader', function(Loader, RealmKeys, $route, $q) {
+ return Loader.get(RealmKeys, function() {
+ return {
+ id : $route.current.params.realm
+ }
+ });
+});
+
module.factory('RealmEventsConfigLoader', function(Loader, RealmEventsConfig, $route, $q) {
return Loader.get(RealmEventsConfig, function() {
return {
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js
index 236ad44..47c2b04 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -206,6 +206,12 @@ module.factory('Realm', function($resource) {
});
});
+module.factory('RealmKeys', function($resource) {
+ return $resource(authUrl + '/admin/realms/:id/keys', {
+ id : '@realm'
+ });
+});
+
module.factory('RealmEventsConfig', function($resource) {
return $resource(authUrl + '/admin/realms/:id/events/config', {
id : '@realm'
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/modal/view-key.html b/themes/src/main/resources/theme/base/admin/resources/partials/modal/view-key.html
new file mode 100644
index 0000000..5d53d3f
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/modal/view-key.html
@@ -0,0 +1,18 @@
+<!--
+ ~ 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.
+ -->
+
+<div style="padding: 20px 20px 20px 20px; word-wrap: break-word;">{{key}}</div>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html
index 7bebc3b..cd9cbcd 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html
@@ -1,89 +1,37 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<kc-tabs-realm></kc-tabs-realm>
- <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
- <input type="text" readonly value="this is not a login form" style="display: none;">
- <input type="password" readonly value="this is not a login form" style="display: none;">
-
- <fieldset class="border-top">
- <div class="form-group">
- <label class="col-md-2 control-label" for="publicKey">{{:: 'publicKey' | translate}}</label>
-
- <div class="col-md-10">
- <textarea type="text" id="publicKey" name="publicKey" class="form-control" rows="4"
- kc-select-action="click" data-ng-model="realm.publicKey" readonly></textarea>
- </div>
- </div>
- <div class="form-group">
- <label class="col-md-2 control-label" for="certificate">{{:: 'certificate' | translate}}</label>
-
- <div class="col-md-10">
- <textarea type="text" id="certificate" name="certificate" class="form-control" rows="8" kc-select-action="click" data-ng-model="realm.certificate" readonly></textarea>
- </div>
- </div>
- </fieldset>
-
- <div class="form-group" data-ng-show="access.manageRealm">
- <div class="col-md-10 col-md-offset-2">
- <button class="btn btn-danger" type="button" data-ng-click="generate()">{{:: 'gen-new-keys' | translate}}</button>
- </div>
- </div>
-
- <fieldset>
- <legend>Import keys</legend>
-
- <div class="form-group">
- <label class="col-md-2 control-label" for="privateKeyUpload">{{:: 'privateKey' | translate}}</label>
- <div class="col-md-6">
- <div class="controls kc-button-input-file" data-ng-show="!privateKeyUploadContent">
- <label for="privateKeyUpload" class="btn btn-default">{{:: 'select-file' | translate}} <i class="pficon pficon-import"></i></label>
- <input id="privateKeyUpload" type="file" class="hidden" ng-file-select="privateKeyUpload($files)">
- </div>
- <span class="kc-uploaded-file">
- {{privateKeyUploadName}}
- </span>
- </div>
- </div>
-
- <div class="form-group">
- <label class="col-md-2 control-label" for="publicKeyUpload">{{:: 'publicKey' | translate}}</label>
- <div class="col-md-6">
- <div class="controls kc-button-input-file" data-ng-show="!publicKeyUploadContent">
- <label for="publicKeyUpload" class="btn btn-default">{{:: 'select-file' | translate}} <i class="pficon pficon-import"></i></label>
- <input id="publicKeyUpload" type="file" class="hidden" ng-file-select="publicKeyUpload($files)">
- </div>
- <span class="kc-uploaded-file">
- {{publicKeyUploadName}}
- </span>
- </div>
- </div>
-
- <div class="form-group">
- <label class="col-md-2 control-label" for="certificateUpload">{{:: 'certificate' | translate}}</label>
- <div class="col-md-6">
- <div class="controls kc-button-input-file" data-ng-show="!certificateUploadContent">
- <label for="certificateUpload" class="btn btn-default">{{:: 'select-file' | translate}} <i class="pficon pficon-import"></i></label>
- <input id="certificateUpload" type="file" class="hidden" ng-file-select="certificateUpload($files)">
- </div>
- <span class="kc-uploaded-file">
- {{certificateUploadName}}
- </span>
- </div>
- </div>
-
- <div class="form-group" data-ng-show="access.manageRealm">
- <div class="col-md-10 col-md-offset-2">
- <button class="btn btn-danger" type="button" data-ng-click="import()" data-ng-disabled="!((privateKeyUploadContent && publicKeyUploadContent) || certificateUploadContent)">{{:: 'upload-keys' | translate}}</button>
- <button class="btn btn-default" type="button" data-ng-click="clearImport()" data-ng-disabled="!privateKeyUploadContent && !publicKeyUploadContent && !certificateUploadContent">{{:: 'clear' | translate}}</button>
- </div>
- </div>
- </fieldset>
- </form>
-
-
-
+ <ul class="nav nav-tabs nav-tabs-pf">
+ <li class="active"><a href="#/realms/{{realm.realm}}/keys">{{:: 'active' | translate}}</a></li>
+ <li><a href="#/realms/{{realm.realm}}/keys/list">{{:: 'all' | translate}}</a></li>
+ <li><a href="#/realms/{{realm.realm}}/keys/providers">{{:: 'providers' | translate}}</a></li>
+ </ul>
+
+ <table class="table table-striped table-bordered">
+ <thead>
+ <tr>
+ <th>{{:: 'type' | translate}}</th>
+ <th>{{:: 'kid' | translate}}</th>
+ <th>{{:: 'provider' | translate}}</th>
+ <th>{{:: 'publicKey' | translate}}</th>
+ <th>{{:: 'certificate' | translate}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="key in active">
+ <td>{{key.type}}</td>
+ <td>{{key.kid}}</td>
+ <td><a href="#/realms/{{realm.realm}}/keys/providers/{{key.provider.providerId}}/{{key.provider.id}}">{{key.provider.name}}</a></td>
+
+ <td data-ng-show="key.publicKey" class="kc-action-cell" data-ng-click="viewKey(key.publicKey)">{{:: 'view' | translate}}</td>
+ <td data-ng-hide="key.publicKey"></td>
+
+ <td data-ng-show="key.certificate" class="kc-action-cell" data-ng-click="viewKey(key.certificate)">{{:: 'view' | translate}}</td>
+ <td data-ng-hide="key.certificate"></td>
+ </tr>
+ </tbody>
+ </table>
</div>
-
<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-generic.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-generic.html
new file mode 100755
index 0000000..0f9857c
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-generic.html
@@ -0,0 +1,69 @@
+<!--
+ ~ 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.
+ -->
+
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+ <kc-tabs-realm></kc-tabs-realm>
+
+ <ul class="nav nav-tabs nav-tabs-pf">
+ <li><a href="#/realms/{{realm.realm}}/keys">{{:: 'active' | translate}}</a></li>
+ <li><a href="#/realms/{{realm.realm}}/keys/list">{{:: 'all' | translate}}</a></li>
+ <li class="active"><a href="#/realms/{{realm.realm}}/keys/providers">{{:: 'providers' | translate}}</a></li>
+ </ul>
+
+ <ol class="breadcrumb">
+ <li><a href="#/realms/{{realm.realm}}/keys/providers">{{:: 'keystores' | translate}}</a></li>
+ <li data-ng-hide="create">{{instance.id}}</li>
+ <li data-ng-show="create">{{:: 'add-keystore' | translate}}</li>
+ </ol>
+
+ <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
+ <fieldset>
+ <div class="form-group clearfix" data-ng-show="!create">
+ <label class="col-md-2 control-label" for="providerId">{{:: 'provider-id' | translate}} </label>
+ <div class="col-md-6">
+ <input class="form-control" id="providerId" type="text" ng-model="instance.id" readonly>
+ </div>
+ </div>
+ <div class="form-group clearfix">
+ <label class="col-md-2 control-label" for="consoleDisplayName">{{:: 'console-display-name' | translate}} </label>
+ <div class="col-md-6">
+ <input class="form-control" id="consoleDisplayName" type="text" ng-model="instance.name" placeholder="{{:: 'defaults-to-id' | translate}}">
+ </div>
+ <kc-tooltip>{{:: 'console-display-name.tooltip' | translate}}</kc-tooltip>
+ </div>
+
+ <kc-component-config realm="realm" config="instance.config" properties="providerFactory.properties"></kc-component-config>
+
+ </fieldset>
+
+ <div class="form-group">
+ <div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageRealm">
+ <button kc-save>{{:: 'save' | translate}}</button>
+ <button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageRealm">
+ <button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
+ <button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
+ </div>
+ </div>
+ </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-list.html
new file mode 100755
index 0000000..ff47219
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-list.html
@@ -0,0 +1,58 @@
+<!--
+ ~ 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.
+ -->
+
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+ <kc-tabs-realm></kc-tabs-realm>
+
+ <ul class="nav nav-tabs nav-tabs-pf">
+ <li><a href="#/realms/{{realm.realm}}/keys">{{:: 'active' | translate}}</a></li>
+ <li class="active"><a href="#/realms/{{realm.realm}}/keys/list">{{:: 'all' | translate}}</a></li>
+ <li><a href="#/realms/{{realm.realm}}/keys/providers">{{:: 'providers' | translate}}</a></li>
+ </ul>
+
+ <table class="table table-striped table-bordered">
+ <thead>
+ <tr>
+ <th>{{:: 'status' | translate}}</th>
+ <th>{{:: 'type' | translate}}</th>
+ <th>{{:: 'kid' | translate}}</th>
+ <th>{{:: 'priority' | translate}}</th>
+ <th>{{:: 'provider' | translate}}</th>
+ <th>{{:: 'publicKey' | translate}}</th>
+ <th>{{:: 'certificate' | translate}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="key in keys">
+ <td>{{key.status}}</td>
+ <td>{{key.type}}</td>
+ <td>{{key.kid}}</td>
+ <td>{{key.providerPriority}}</td>
+ <td><a href="#/realms/{{realm.realm}}/keys/providers/{{key.provider.providerId}}/{{key.provider.id}}">{{key.provider.name}}</a></td>
+
+ <td data-ng-show="key.publicKey" class="kc-action-cell" data-ng-click="viewKey(key.publicKey)">{{:: 'view' | translate}}</td>
+ <td data-ng-hide="key.publicKey"></td>
+
+ <td data-ng-show="key.certificate" class="kc-action-cell" data-ng-click="viewKey(key.certificate)">{{:: 'view' | translate}}</td>
+ <td data-ng-hide="key.certificate"></td>
+ </tr>
+ </tbody>
+ </table>
+
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-providers.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-providers.html
new file mode 100755
index 0000000..2036e52
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-providers.html
@@ -0,0 +1,65 @@
+<!--
+ ~ 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.
+ -->
+
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+ <kc-tabs-realm></kc-tabs-realm>
+
+ <ul class="nav nav-tabs nav-tabs-pf">
+ <li><a href="#/realms/{{realm.realm}}/keys">{{:: 'active' | translate}}</a></li>
+ <li><a href="#/realms/{{realm.realm}}/keys/list">{{:: 'all' | translate}}</a></li>
+ <li class="active"><a href="#/realms/{{realm.realm}}/keys/providers">{{:: 'providers' | translate}}</a></li>
+ </ul>
+
+ <table class="table table-striped table-bordered">
+ <thead>
+ <tr ng-show="providers.length > 0 && access.manageUsers">
+ <th colspan="6" class="kc-table-actions">
+ <div class="pull-right">
+ <div>
+ <select class="form-control" ng-model="selectedProvider"
+ ng-options="p.id for p in providers"
+ data-ng-change="addProvider(selectedProvider); selectedProvider = null">
+ <option value="" disabled selected>{{:: 'add-keystore.placeholder' | translate}}</option>
+ </select>
+ </div>
+ </div>
+ </th>
+ </tr>
+ <tr data-ng-show="instances && instances.length > 0">
+ <th>{{:: 'name' | translate}}</th>
+ <th>{{:: 'id' | translate}}</th>
+ <th>{{:: 'provider' | translate}}</th>
+ <th>{{:: 'priority' | translate}}</th>
+ <th colspan="2">{{:: 'actions' | translate}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="instance in instances">
+ <td>{{instance.name}}</td>
+ <td><a href="#/realms/{{realm.realm}}/keys/providers/{{instance.providerId}}/{{instance.id}}">{{instance.id}}</a></td>
+ <td>{{instance.providerId}}</td>
+ <td>{{instance.config['priority'][0]}}</td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/keys/providers/{{instance.providerId}}/{{instance.id}}">{{:: 'edit' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="removeInstance(instance)">{{:: 'delete' | translate}}</td>
+ </tr>
+ </tbody>
+ </table>
+
+</div>
+
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html
index 57bbc06..cd0df94 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html
@@ -2,14 +2,14 @@
<div data-ng-repeat="option in properties" class="form-group" data-ng-controller="ProviderConfigCtrl">
<label class="col-md-2 control-label">{{:: option.label | translate}}</label>
- <div class="col-md-6" data-ng-hide="option.type == 'boolean' || option.type == 'List' || option.type == 'Role' || option.type == 'ClientList' || option.type == 'Password' || option.type=='Script'">
+ <div class="col-md-6" data-ng-hide="option.type == 'boolean' || option.type == 'List' || option.type == 'Role' || option.type == 'ClientList' || option.type == 'Password' || option.type=='Script' || option.type=='File'">
<input class="form-control" type="text" data-ng-model="config[ option.name ][0]" >
</div>
<div class="col-md-6" data-ng-show="option.type == 'Password'">
<input class="form-control" type="password" data-ng-model="config[ option.name ][0]" >
</div>
<div class="col-md-6" data-ng-show="option.type == 'boolean'">
- <input ng-model="config[ option.name ][0]" value="'true'" name="option.name" id="option.name" onoffswitchstring on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+ <input ng-model="config[ option.name ][0]" value="'true'" name="option.name" onoffswitchstring on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
<div class="col-md-6" data-ng-show="option.type == 'List'">
<select ng-model="config[ option.name ][0]" ng-options="data for data in option.defaultValue">
@@ -32,7 +32,15 @@
</select>
</div>
- <div class="col-md-6" data-ng-show="option.type == 'Script'">
+ <div class="col-md-6" data-ng-if="option.type == 'File'">
+ <div class="controls kc-button-input-file">
+ <input class="form-control" type="text" data-ng-model="config[option.name][0]" >
+ <label for="{{option.name}}" class="btn btn-default">{{:: 'select-file' | translate}} <i class="pficon pficon-import"></i></label>
+ <input id="{{option.name}}" type="file" class="hidden" ng-file-select="uploadFile($files, option.name, config)">
+ </div>
+ </div>
+
+ <div class="col-md-6" data-ng-if="option.type == 'Script'">
<div ng-model="config[option.name]" placeholder="Enter your script..." ui-ace="{ useWrapMode: true, showGutter: true, theme:'github', mode: 'javascript'}">
{{config[option.name]}}
</div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html
index 7f10767..dfedb75 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html
@@ -8,7 +8,7 @@
<ul class="nav nav-tabs">
<li ng-class="{active: !path[2]}"><a href="#/realms/{{realm.realm}}">{{:: 'realm-tab-general' | translate}}</a></li>
<li ng-class="{active: path[2] == 'login-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/login-settings">{{:: 'realm-tab-login' | translate}}</a></li>
- <li ng-class="{active: path[2] == 'keys-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/keys-settings">{{:: 'realm-tab-keys' | translate}}</a></li>
+ <li ng-class="{active: path[2] == 'keys'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/keys">{{:: 'realm-tab-keys' | translate}}</a></li>
<li ng-class="{active: path[2] == 'smtp-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/smtp-settings">{{:: 'realm-tab-email' | translate}}</a></li>
<li ng-class="{active: path[2] == 'theme-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/theme-settings">{{:: 'realm-tab-themes' | translate}}</a></li>
<li ng-class="{active: path[2] == 'cache-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/cache-settings">{{:: 'realm-tab-cache' | translate}}</a></li>
diff --git a/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css b/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css
index 9253c8c..2eb155e 100755
--- a/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css
+++ b/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css
@@ -379,4 +379,15 @@ h1 i {
.ace_editor {
height: 600px;
width: 100%;
+}
+
+.kc-button-input-file input {
+ float: left;
+ width: 73%;
+}
+
+.kc-button-input-file label {
+ float: left;
+ margin-left: 2%;
+ width: 25%;
}
\ No newline at end of file