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 25c899eadf..9a291c071b 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 236844ebc1..2faa84ab26 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 469dd266d0..2aa51a44c3 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 502d0c06e1..9305f32a49 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 62cef81971..3efd90a3b7 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 9d3e76f5a0..7fc440ec94 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 0000000000..a49b9ab5df --- /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 Stian Thorgersen + */ +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/PemException.java b/common/src/main/java/org/keycloak/common/util/PemException.java new file mode 100644 index 0000000000..7f646162ce --- /dev/null +++ b/common/src/main/java/org/keycloak/common/util/PemException.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * @author Stian Thorgersen + */ +public class PemException extends RuntimeException { + + public PemException(Throwable cause) { + super(cause); + } + +} 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 4bff8c95b9..fc4e193158 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); - } + /** + * Decode a X509 Certificate from a PEM string + * + * @param cert + * @return + * @throws Exception + */ + public static X509Certificate decodeCertificate(String cert) { + if (cert == null) { + return null; + } - public static X509Certificate decodeCertificate(String cert) throws Exception { - byte[] der = pemToDer(cert); - ByteArrayInputStream bis = new ByteArrayInputStream(der); - return DerUtils.decodeCertificate(bis); + 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(String pem) { + if (pem == null) { + return null; + } - public static PrivateKey decodePrivateKey(InputStream is) throws Exception { - String pem = pemFromStream(is); - return decodePrivateKey(pem); + 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/RSATokenVerifier.java b/core/src/main/java/org/keycloak/RSATokenVerifier.java index 562261de5e..1cbc2e2ac7 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); - - tokenVerifications(token, realmUrl, checkActive, checkTokenType); - - return token; + public static RSATokenVerifier create(String tokenString) { + return new RSATokenVerifier(tokenString); } - 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 static AccessToken verifyToken(String tokenString, PublicKey publicKey, String realmUrl) throws VerificationException { + return RSATokenVerifier.create(tokenString).publicKey(publicKey).realmUrl(realmUrl).verify().getToken(); + } - } + 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(); + } - 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 + "'"); + public RSATokenVerifier publicKey(PublicKey publicKey) { + this.publicKey = publicKey; + return this; + } + + public RSATokenVerifier realmUrl(String realmUrl) { + this.realmUrl = realmUrl; + return this; + } + + 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 RSATokenVerifier parse() throws VerificationException { + if (jws == null) { + if (tokenString == null) { + throw new VerificationException("Token not set"); + } + + 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 RSATokenVerifier verify() throws VerificationException { + parse(); + + if (publicKey == null) { + throw new VerificationException("Public key not set"); + } + + if (checkRealmUrl && realmUrl == null) { + throw new VerificationException("Realm URL not set"); + } + + 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() + "'"); + } + + 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."); + throw new VerificationException("Token is not active"); } + 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."); - - AccessToken token; - try { - token = input.readJsonContent(AccessToken.class); - } catch (JWSInputException e) { - throw new VerificationException("Couldn't parse token signature", e); - } - return token; - } - - - 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."); - - AccessToken token; - try { - token = input.readJsonContent(AccessToken.class); - } catch (JWSInputException e) { - throw new VerificationException("Couldn't parse token signature", e); - } - - tokenVerifications(token, realmUrl, checkActive, checkTokenType); - - return token; - } - - - 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); - } - } } 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 ae37ebf80a..2333ba94cd 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 2fac965c8f..ec1f378a8c 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 0baa66a1c3..6558c410e1 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 0000000000..54d3ba9385 --- /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 Stian Thorgersen + */ +public class KeysMetadataRepresentation { + + private Map active; + + private List keys; + + public Map getActive() { + return active; + } + + public void setActive(Map active) { + this.active = active; + } + + public List getKeys() { + return keys; + } + + public void setKeys(List 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 ceb2fe92b5..eceb7592fc 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 776aa0e005..bcbb02d5f0 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 groups; 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 0000000000..aeb1fdb5af --- /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 Stian Thorgersen + */ +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 252157a6fb..4ce3a41f48 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/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index af3c7ad30c..4802a6b1be 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; @@ -423,123 +419,6 @@ public class RealmAdapter implements RealmModel { updated.setAccessCodeLifespanLogin(seconds); } - @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 getRequiredCredentials() { if (isUpdated()) return updated.getRequiredCredentials(); 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 8ca5a6d723..99b718c595 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 getRequiredCredentials() { return requiredCredentials; } @@ -507,10 +473,6 @@ public class CachedRealm extends AbstractRevisioned { return userFederationMappers; } - public String getCertificatePem() { - return certificatePem; - } - public List 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 getUserFederationMapperSet() { return userFederationMapperSet; } 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 73027b8232..f6d1640d2d 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 0000000000..d3def65991 --- /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 Marek Posolda + */ +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/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index 21aa8d0648..2a9bd45992 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 { 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 { 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 { @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 { @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/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java index e45d819800..eef1d91246 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 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 getIdentityProviders() { return this.identityProviders; } 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 a73ee6bc5d..b1f98879a8 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 @@ + + + + + + + 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 7b18863641..3e429e2cf5 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 0000000000..063676102c --- /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 Marek Posolda + */ +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 4ebfca851d..10e45c08c9 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 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; @@ -455,110 +451,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme return realm.getAccessCodeLifespanLogin(); } - @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 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 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 d1f16f72df..71512167ac 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 284d29dbb0..a0603dbb81 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 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 c80df69824..b46f5b6687 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 config = new MultivaluedHashMap<>(); + private transient ConcurrentHashMap 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 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 0000000000..b2d0a58470 --- /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 Stian Thorgersen + */ +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 0000000000..31ca54194f --- /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 Stian Thorgersen + */ +public interface KeyProvider extends Provider { + + /** + * Return the KID for the active keypair, or null if no active key is available. + * + * @return + */ + String getKid(); + + /** + * Return the private key for the active keypair, or null if no active key is available. + * + * @return + */ + PrivateKey getPrivateKey(); + + /** + * Return the public key for the specified kid, or null if the kid is unknown. + * + * @param kid + * @return + */ + PublicKey getPublicKey(String kid); + + /** + * Return the certificate for the specified kid, or null if the kid is unknown. + * + * @param kid + * @return + */ + X509Certificate getCertificate(String kid); + + /** + * Return metadata about all keypairs held by the provider + * @return + */ + List 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 0000000000..be005b807e --- /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 Stian Thorgersen + */ +public interface KeyProviderFactory extends ComponentFactory { + + T create(KeycloakSession session, ComponentModel model); + +} diff --git a/core/src/main/java/org/keycloak/representations/PasswordToken.java b/server-spi/src/main/java/org/keycloak/keys/KeySpi.java similarity index 50% rename from core/src/main/java/org/keycloak/representations/PasswordToken.java rename to server-spi/src/main/java/org/keycloak/keys/KeySpi.java index 616383131e..22adc0d500 100644 --- a/core/src/main/java/org/keycloak/representations/PasswordToken.java +++ b/server-spi/src/main/java/org/keycloak/keys/KeySpi.java @@ -15,50 +15,33 @@ * limitations under the License. */ -package org.keycloak.representations; +package org.keycloak.keys; -import org.keycloak.common.util.Time; +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; /** * @author Stian Thorgersen */ -public class PasswordToken { - - private String realm; - private String user; - private int timestamp; - - public PasswordToken() { +public class KeySpi implements Spi { + @Override + public boolean isInternal() { + return true; } - public PasswordToken(String realm, String user) { - this.realm = realm; - this.user = user; - this.timestamp = Time.currentTime(); + @Override + public String getName() { + return "keys"; } - public String getRealm() { - return realm; + @Override + public Class getProviderClass() { + return KeyProvider.class; } - public void setRealm(String realm) { - this.realm = realm; + @Override + public Class getProviderFactoryClass() { + return KeyProviderFactory.class; } - - public String getUser() { - return user; - } - - public void setUser(String user) { - this.user = user; - } - - public int getTimestamp() { - return timestamp; - } - - public void setTimestamp(int timestamp) { - this.timestamp = timestamp; - } - } 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 6c1cb3e96c..6b7e9d5715 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 Stian Thorgersen */ 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/KeyManager.java b/server-spi/src/main/java/org/keycloak/models/KeyManager.java new file mode 100644 index 0000000000..757e9a931e --- /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 Stian Thorgersen + */ +public interface KeyManager { + + ActiveKey getActiveKey(RealmModel realm); + + PublicKey getPublicKey(RealmModel realm, String kid); + + Certificate getCertificate(RealmModel realm, String kid); + + List 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/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java index 60893ccb67..f0481ba4cb 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/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index 7ff7813a8c..aa8b440021 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 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 0000000000..a0d915edd7 --- /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 Stian Thorgersen + */ +public class ComponentUtil { + + public static Map getComponentConfigProperties(KeycloakSession session, ComponentModel component) { + try { + List l = getComponentFactory(session, component).getConfigProperties(); + Map 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 provider; + try { + provider = (Class) Class.forName(component.getProviderType()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Invalid provider type '" + component.getProviderType() + "'"); + } + + ProviderFactory 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 e2e31e6838..598e3e9eda 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 Bill Burke @@ -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 0000000000..30ff7d7a58 --- /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 Stian Thorgersen + */ +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 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 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 c291c42bb6..1eae85946c 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 193bbc2d58..551d62e3c8 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 configProperties = ComponentUtil.getComponentConfigProperties(session, component); + MultivaluedHashMap config = new MultivaluedHashMap<>(); + + for (Map.Entry> 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 8d4ac2d58f..fc8b52f432 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 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 keys = new HashSet<>(rep.getConfig().keySet()); + for (String k : keys) { + List values = rep.getConfig().get(k); + if (values != null) { + ListIterator 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 providerConfiguration = null; + if (!internal) { + providerConfiguration = ComponentUtil.getComponentConfigProperties(session, component); + } + + if (rep.getConfig() != null) { + Set keys = new HashSet<>(rep.getConfig().keySet()); + for (String k : keys) { + if (!internal && !providerConfiguration.containsKey(k)) { + break; + } + + List 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 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 0000000000..dd9561f517 --- /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 Stian Thorgersen + */ +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 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 5ddfb4c092..dee0b4e4fa 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 0000000000..5382355ce3 --- /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 Stian Thorgersen + */ +public class ProviderConfigurationBuilder { + + private List 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 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 5cf883d655..800ed9c4f3 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 getRequestedRoles() { - Set requestedRoles = new HashSet(); + Set 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 getRequestedProtocolMappers() { - Set requestedProtocolMappers = new HashSet(); + Set requestedProtocolMappers = new HashSet<>(); Set 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); - - StringBuilder sb = new StringBuilder(); - sb.append(hash); - sb.append("."); - sb.append(clientSession.getId()); - - return sb.toString(); - } - - private static String createHash(RealmModel realm, ClientSessionModel clientSession) { + private static String generateCode(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()); + 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(); + + Signature signature = RSAProvider.getSignature(Algorithm.RS256); + signature.initSign(keys.getPrivateKey()); + signature.update(code.getBytes("utf-8")); + + 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 boolean verifyCode(String code, KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) { + try { + 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 48357e9baa..bbd588ea50 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 691acf8cdb..7469511f86 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/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java index 684075e848..8f830d1e40 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/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java index d1c749be66..1e404621c7 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 0a764d851e..f8408a4ecc 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 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/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java index cdb7cd00ae..77a35e9cf5 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 07e5908666..884f028e00 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 e28106397f..9f89e4a376 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 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 09bf694c61..eee504ad22 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 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 0deeef5eca..056f9fb13f 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 3b218ccf11..170925b63d 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 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 f7db32b7d9..104b8f81ca 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 AbstractIdentityProviderStian Thorgersen + */ +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 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 0000000000..7b2d526086 --- /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 Stian Thorgersen + */ +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 0000000000..758ec953fd --- /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 Stian Thorgersen + */ +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 0000000000..df18005d4e --- /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 Stian Thorgersen + */ +public class DefaultKeyManager implements KeyManager { + + private static final Logger logger = Logger.getLogger(DefaultKeyManager.class); + + private final KeycloakSession session; + private final Map> 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 getKeys(RealmModel realm, boolean includeDisabled) { + List 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 getProviders(RealmModel realm) { + boolean active = false; + List providers = providersMap.get(realm.getId()); + if (providers == null) { + providers = new LinkedList<>(); + + List components = new LinkedList<>(realm.getComponents(realm.getId(), KeyProvider.class.getName())); + components.sort(new ProviderComparator()); + + for (ComponentModel c : components) { + try { + ProviderFactory 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 { + + @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 0000000000..00586a8c18 --- /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 Stian Thorgersen + */ +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 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 0000000000..9042b48989 --- /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 Stian Thorgersen + */ +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 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 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 0000000000..07e91a4390 --- /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 Stian Thorgersen + */ +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 0000000000..6b6e530273 --- /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 Stian Thorgersen + */ +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 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 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/RsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/RsaKeyProvider.java new file mode 100644 index 0000000000..27f30777f8 --- /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 Stian Thorgersen + */ +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 0000000000..f6814dac9d --- /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 Stian Thorgersen + */ +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 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 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 81688327e9..23eb726985 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 439f51d3e7..f41ded8196 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/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java index 8eed142185..c1a101c014 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/RestartLoginCookie.java b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java index fd32f2b7f1..cc7c3244a1 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/oidc/AccessTokenIntrospectionProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java index 03787652b9..f1132af221 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 Pedro Igor @@ -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/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java index 9e214df5c4..a07ea709be 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 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 e8b4e11ffd..308c771d79 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 mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers(); + Set 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 mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers(); + Set 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 mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers(); + Set 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/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java index 1a47301802..44822c6b62 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 6d9df6c7a8..9a7fe3ef19 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 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 739fd96e90..89094da7ca 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/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java index 9751c6bec9..14726d3547 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 cff68ae304..29e809d6a8 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/protocol/saml/installation/KeycloakSamlClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java index 4a91db950b..2175b32cc6 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("\n"); - baseXml(realm, client, baseUri, samlClient, buffer); + baseXml(session, realm, client, baseUri, samlClient, buffer); buffer.append("\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(" \n"); @@ -116,7 +117,7 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide buffer.append(" \n"); buffer.append(" \n"); buffer.append(" \n"); - buffer.append(" ").append(realm.getCertificatePem()).append("\n"); + buffer.append(" ").append(PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate())).append("\n"); buffer.append(" \n"); buffer.append(" \n"); buffer.append(" \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 1310ea486e..ea77d474b5 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("\n"); - KeycloakSamlClientInstallation.baseXml(realm, client, baseUri, samlClient, buffer); + KeycloakSamlClientInstallation.baseXml(session, realm, client, baseUri, samlClient, buffer); buffer.append("\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 1d8ca2f31f..39bfda056e 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 5d155d2e5e..4b84363809 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 = "\n" + @@ -75,7 +76,7 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr " \n" + " \n" + " \n" + - " " + realm.getCertificatePem() + "\n" + + " " + PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()) + "\n" + " \n" + " \n" + " \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 0f9fdeb619..82d26d3c2b 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> roleNameMappers = new LinkedList<>(); KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); AttributeType singleAttributeType = null; - Set requestedProtocolMappers = new ClientSessionCode(clientSession.getRealm(), clientSession).getRequestedProtocolMappers(); + Set 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/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index 0b971d23da..935421c23e 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/clientregistration/ClientRegistrationAuth.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java index 93c264825a..2f4a19bd95 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 2f33e5d5ce..8df24f4b91 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 Stian Thorgersen @@ -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/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java index bd1eabddde..89cf129958 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 2177a9b6d5..f6d2c68b2f 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 realmRoles = new LinkedList<>(); MultivaluedMap 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 2e21465eaf..488df50b4e 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 adapterSessionIds, List 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 4c8b2c79a0..e95f580a4f 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/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index 9f7e4bf8c3..546438424e 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 8b23ee22a3..91cfa28849 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 d7aeaaa82a..0b41e519b1 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/services/resources/admin/AdminRoot.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java index 0649b25fff..87d9091428 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 4e1d7a4399..20d673127f 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 aa1f08959b..09582a551f 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 532bffcd17..1bc6411902 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 1352431883..6487725b6b 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 f3e099afcd..dcd40a6ece 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 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(); + 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(); + 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 4c40991579..d3b89d010a 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 0000000000..e58f102061 --- /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 Stian Thorgersen + */ +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 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 29c4cbf3bd..47e0217310 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 04849b52fe..71abed55fb 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 36b0b69171..7b4ccef3fc 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 e6d53bceb5..2b06c4ccb5 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/social/twitter/TwitterIdentityProvider.java b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java index 98d091e7ef..7da4f6198a 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 AbstractIdentityProviderStian Thorgersen + */ +public class TestImplProviderFactory implements TestProviderFactory { + + private List 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 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 0000000000..163f150e4e --- /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 Stian Thorgersen + */ +public interface TestProvider extends Provider { + + DetailsRepresentation getDetails(); + + class DetailsRepresentation { + Map> config; + + public Map> getConfig() { + return config; + } + + public void setConfig(Map> 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 0000000000..69dea135c7 --- /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 Stian Thorgersen + */ +public interface TestProviderFactory extends ComponentFactory { +} 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 0000000000..2413e65346 --- /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 Stian Thorgersen + */ +public class TestSpi implements Spi { + @Override + public boolean isInternal() { + return false; + } + + @Override + public String getName() { + return "test"; + } + + @Override + public Class getProviderClass() { + return TestProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return TestProviderFactory.class; + } +} 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 4cbf4e7c77..97980360d3 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 getTestComponentDetails() { + Map reps = new HashMap<>(); + + RealmModel realm = session.getContext().getRealm(); + for (ComponentModel c : realm.getComponents(realm.getId(), TestProvider.class.getName())) { + ProviderFactory 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/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 6daa6b1a44..cf0f55e9a2 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 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/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 1c31c00fdb..df1b912986 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 0000000000..701b835c51 --- /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 dedc08df1f..ae3a873ec0 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 9638229946..50352d43c2 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 Marko Strukelj */ -@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 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 8786ff095d..374585b3f2 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 4d55e976b4..50a5027248 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/ComponentsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java new file mode 100644 index 0000000000..90884470df --- /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 Stian Thorgersen + */ +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 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 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/client/InstallationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java index bac5fc0fc8..626c0d4d51 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("")); 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(" 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 @@ -444,146 +428,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(); 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 d79d4dc6f6..632b59484a 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 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 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 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 expected, List 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 870af00a33..de89d57f36 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 0000000000..fd4fdfede0 --- /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 Stian Thorgersen + */ +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 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 0000000000..649c6622a8 --- /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 Stian Thorgersen + */ +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 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 99ee7c6462..c05bc7ee1d 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 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 520f910e77..c0c8601614 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 517cc67ae6..208e6e3a9e 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 @@ -273,38 +273,6 @@ public class RefreshTokenTest extends AbstractKeycloakTest { String privateKey; 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 e557c08b0b..85bd77fda2 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 e0588e79e5..e6ec392e6e 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 6a891c3cfc..7fa4d6e3e7 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/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 0000000000..bdaf10be78 --- /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 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 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/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java index a602ffd636..25e046f1ea 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 a53313333b..17531a019e 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 e0d5d6ce37..a147608bec 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/broker/OIDCKeycloakServerBrokerWithSignatureTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithSignatureTest.java index 910356d20d..28c6625b93 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 6cd02bf84b..3a257fd8e7 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 initParams = new TreeMap(); - 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/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java deleted file mode 100755 index 95ac032705..0000000000 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java +++ /dev/null @@ -1,897 +0,0 @@ -/* - * 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.model; - -import org.junit.Assert; -import org.junit.FixMethodOrder; -import org.junit.Test; -import org.junit.runners.MethodSorters; -import org.keycloak.Config; -import org.keycloak.component.ComponentModel; -import org.keycloak.credential.CredentialModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.FederatedIdentityModel; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.PasswordPolicy; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredCredentialModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserProvider; -import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.services.managers.RealmManager; -import org.keycloak.storage.UserStorageProvider; -import org.keycloak.storage.UserStorageProviderModel; -import org.keycloak.testsuite.federation.storage.UserMapStorageFactory; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class AdapterTest extends AbstractModelTest { - private RealmModel realmModel; - - @Test - public void test1CreateRealm() throws Exception { - realmModel = realmManager.createRealm("JUGGLER"); - realmModel.setAccessCodeLifespan(100); - realmModel.setAccessCodeLifespanUserAction(600); - realmModel.setEnabled(true); - realmModel.setName("JUGGLER"); - - KeyPair keyPair = generateKeypair(); - - realmModel.setPrivateKey(keyPair.getPrivate()); - realmModel.setPublicKey(keyPair.getPublic()); - realmModel.setAccessTokenLifespan(1000); - realmModel.addDefaultRole("foo"); - - session.getTransactionManager().commit(); - resetSession(); - - realmModel = realmManager.getRealm(realmModel.getId()); - assertNotNull(realmModel); - Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100); - Assert.assertEquals(600, realmModel.getAccessCodeLifespanUserAction()); - Assert.assertEquals(realmModel.getAccessTokenLifespan(), 1000); - Assert.assertEquals(realmModel.isEnabled(), true); - Assert.assertEquals(realmModel.getName(), "JUGGLER"); - Assert.assertArrayEquals(realmModel.getPrivateKey().getEncoded(), keyPair.getPrivate().getEncoded()); - Assert.assertArrayEquals(realmModel.getPublicKey().getEncoded(), keyPair.getPublic().getEncoded()); - Assert.assertEquals(3, realmModel.getDefaultRoles().size()); - Assert.assertTrue(realmModel.getDefaultRoles().contains("foo")); - } - - @Test - public void testRealmListing() throws Exception { - realmModel = realmManager.createRealm("JUGGLER"); - realmModel.setAccessCodeLifespan(100); - realmModel.setAccessCodeLifespanUserAction(600); - realmModel.setEnabled(true); - realmModel.setName("JUGGLER"); - KeyPair keyPair = generateKeypair(); - realmModel.setPrivateKey(keyPair.getPrivate()); - realmModel.setPublicKey(keyPair.getPublic()); - realmModel.setAccessTokenLifespan(1000); - realmModel.addDefaultRole("foo"); - - realmModel = realmManager.getRealm(realmModel.getId()); - assertNotNull(realmModel); - Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100); - Assert.assertEquals(600, realmModel.getAccessCodeLifespanUserAction()); - Assert.assertEquals(realmModel.getAccessTokenLifespan(), 1000); - Assert.assertEquals(realmModel.isEnabled(), true); - Assert.assertEquals(realmModel.getName(), "JUGGLER"); - Assert.assertArrayEquals(realmModel.getPrivateKey().getEncoded(), keyPair.getPrivate().getEncoded()); - Assert.assertArrayEquals(realmModel.getPublicKey().getEncoded(), keyPair.getPublic().getEncoded()); - Assert.assertEquals(3, realmModel.getDefaultRoles().size()); - Assert.assertTrue(realmModel.getDefaultRoles().contains("foo")); - - realmModel.getId(); - - commit(); - List realms = model.getRealms(); - Assert.assertEquals(realms.size(), 2); - } - - - @Test - public void test2RequiredCredential() throws Exception { - test1CreateRealm(); - realmModel.addRequiredCredential(CredentialRepresentation.PASSWORD); - List storedCreds = realmModel.getRequiredCredentials(); - Assert.assertEquals(1, storedCreds.size()); - - Set creds = new HashSet(); - creds.add(CredentialRepresentation.PASSWORD); - creds.add(CredentialRepresentation.TOTP); - realmModel.updateRequiredCredentials(creds); - storedCreds = realmModel.getRequiredCredentials(); - Assert.assertEquals(2, storedCreds.size()); - boolean totp = false; - boolean password = false; - for (RequiredCredentialModel cred : storedCreds) { - Assert.assertTrue(cred.isInput()); - if (cred.getType().equals(CredentialRepresentation.PASSWORD)) { - password = true; - Assert.assertTrue(cred.isSecret()); - } else if (cred.getType().equals(CredentialRepresentation.TOTP)) { - totp = true; - Assert.assertFalse(cred.isSecret()); - } - } - Assert.assertTrue(totp); - Assert.assertTrue(password); - } - - @Test - public void testCredentialValidation() throws Exception { - test1CreateRealm(); - UserProvider userProvider = realmManager.getSession().users(); - UserModel user = userProvider.addUser(realmModel, "bburke"); - UserCredentialModel cred = new UserCredentialModel(); - cred.setType(CredentialRepresentation.PASSWORD); - cred.setValue("geheim"); - realmManager.getSession().userCredentialManager().updateCredential(realmModel, user, cred); - Assert.assertTrue(realmManager.getSession().userCredentialManager().isValid(realmModel, user, UserCredentialModel.password("geheim"))); - List creds = realmManager.getSession().userCredentialManager().getStoredCredentialsByType(realmModel, user, CredentialModel.PASSWORD); - - Assert.assertEquals(creds.get(0).getHashIterations(), 20000); - realmModel.setPasswordPolicy(PasswordPolicy.parse(realmManager.getSession(), "hashIterations(200)")); - Assert.assertTrue(realmManager.getSession().userCredentialManager().isValid(realmModel, user, UserCredentialModel.password("geheim"))); - creds = realmManager.getSession().userCredentialManager().getStoredCredentialsByType(realmModel, user, CredentialModel.PASSWORD); - Assert.assertEquals(creds.get(0).getHashIterations(), 200); - realmModel.setPasswordPolicy(PasswordPolicy.parse(realmManager.getSession(), "hashIterations(1)")); - } - - @Test - public void testDeleteUser() throws Exception { - test1CreateRealm(); - - UserModel user = realmManager.getSession().users().addUser(realmModel, "bburke"); - user.setSingleAttribute("attr1", "val1"); - user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); - - RoleModel testRole = realmModel.addRole("test"); - user.grantRole(testRole); - - ClientModel app = realmModel.addClient("test-app"); - RoleModel appRole = app.addRole("test"); - user.grantRole(appRole); - - FederatedIdentityModel socialLink = new FederatedIdentityModel("google", "google1", user.getUsername()); - realmManager.getSession().users().addFederatedIdentity(realmModel, user, socialLink); - - UserCredentialModel cred = new UserCredentialModel(); - cred.setType(CredentialRepresentation.PASSWORD); - cred.setValue("password"); - realmManager.getSession().userCredentialManager().updateCredential(realmModel, user, cred); - commit(); - - realmModel = model.getRealm("JUGGLER"); - Assert.assertTrue(realmManager.getSession().users().removeUser(realmModel, user)); - assertNull(realmManager.getSession().users().getUserByUsername("bburke", realmModel)); - } - - @Test - public void testRemoveApplication() throws Exception { - test1CreateRealm(); - - UserModel user = realmManager.getSession().users().addUser(realmModel, "bburke"); - - ClientModel client = realmModel.addClient("client"); - - ClientModel app = realmModel.addClient("test-app"); - - RoleModel appRole = app.addRole("test"); - user.grantRole(appRole); - client.addScopeMapping(appRole); - - RoleModel realmRole = realmModel.addRole("test"); - app.addScopeMapping(realmRole); - - Assert.assertTrue(realmModel.removeClient(app.getId())); - Assert.assertFalse(realmModel.removeClient(app.getId())); - assertNull(realmModel.getClientById(app.getId())); - } - - - @Test - public void testRemoveRealm() throws Exception { - test1CreateRealm(); - - UserModel user = realmManager.getSession().users().addUser(realmModel, "bburke"); - - UserCredentialModel cred = new UserCredentialModel(); - cred.setType(CredentialRepresentation.PASSWORD); - cred.setValue("password"); - realmManager.getSession().userCredentialManager().updateCredential(realmModel, user, cred); - - ClientModel client = realmModel.addClient("client"); - - ClientModel app = realmModel.addClient("test-app"); - - RoleModel appRole = app.addRole("test"); - user.grantRole(appRole); - client.addScopeMapping(appRole); - - RoleModel realmRole = realmModel.addRole("test"); - RoleModel realmRole2 = realmModel.addRole("test2"); - realmRole.addCompositeRole(realmRole2); - realmRole.addCompositeRole(appRole); - - app.addScopeMapping(realmRole); - - commit(); - realmModel = model.getRealm("JUGGLER"); - - Assert.assertTrue(realmManager.removeRealm(realmModel)); - Assert.assertFalse(realmManager.removeRealm(realmModel)); - assertNull(realmManager.getRealm(realmModel.getId())); - } - - - @Test - public void testRemoveRole() throws Exception { - test1CreateRealm(); - - UserModel user = realmManager.getSession().users().addUser(realmModel, "bburke"); - - ClientModel client = realmModel.addClient("client"); - - ClientModel app = realmModel.addClient("test-app"); - - RoleModel appRole = app.addRole("test"); - user.grantRole(appRole); - client.addScopeMapping(appRole); - - RoleModel realmRole = realmModel.addRole("test"); - app.addScopeMapping(realmRole); - - commit(); - realmModel = model.getRealm("JUGGLER"); - app = realmModel.getClientByClientId("test-app"); - user = realmManager.getSession().users().getUserByUsername("bburke", realmModel); - - Assert.assertTrue(realmModel.removeRoleById(realmRole.getId())); - Assert.assertFalse(realmModel.removeRoleById(realmRole.getId())); - assertNull(realmModel.getRole(realmRole.getName())); - - Assert.assertTrue(realmModel.removeRoleById(appRole.getId())); - Assert.assertFalse(realmModel.removeRoleById(appRole.getId())); - assertNull(app.getRole(appRole.getName())); - - user = realmManager.getSession().users().getUserByUsername("bburke", realmModel); - - } - - @Test - public void testUserSearch() throws Exception { - test1CreateRealm(); - { - UserModel user = realmManager.getSession().users().addUser(realmModel, "bburke"); - user.setLastName("Burke"); - user.setFirstName("Bill"); - user.setEmail("bburke@redhat.com"); - - UserModel user2 = realmManager.getSession().users().addUser(realmModel, "doublefirst"); - user2.setFirstName("Knut Ole"); - user2.setLastName("Alver"); - user2.setEmail("knut@redhat.com"); - - UserModel user3 = realmManager.getSession().users().addUser(realmModel, "doublelast"); - user3.setFirstName("Ole"); - user3.setLastName("Alver Veland"); - user3.setEmail("knut2@redhat.com"); - } - - RealmManager adapter = realmManager; - - { - List userModels = adapter.searchUsers("total junk query", realmModel); - Assert.assertEquals(userModels.size(), 0); - } - - { - List userModels = adapter.searchUsers("Bill Burke", realmModel); - Assert.assertEquals(userModels.size(), 1); - UserModel bburke = userModels.get(0); - Assert.assertEquals(bburke.getFirstName(), "Bill"); - Assert.assertEquals(bburke.getLastName(), "Burke"); - Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com"); - } - - { - List userModels = adapter.searchUsers("bill burk", realmModel); - Assert.assertEquals(userModels.size(), 1); - UserModel bburke = userModels.get(0); - Assert.assertEquals(bburke.getFirstName(), "Bill"); - Assert.assertEquals(bburke.getLastName(), "Burke"); - Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com"); - } - - { - ArrayList users = new ArrayList(); - for (UserModel u : adapter.searchUsers("ole alver", realmModel)) { - users.add(u.getUsername()); - } - String[] usernames = users.toArray(new String[users.size()]); - Arrays.sort(usernames); - Assert.assertArrayEquals(new String[]{"doublefirst", "doublelast"}, usernames); - } - - { - List userModels = adapter.searchUsers("bburke@redhat.com", realmModel); - Assert.assertEquals(userModels.size(), 1); - UserModel bburke = userModels.get(0); - Assert.assertEquals(bburke.getFirstName(), "Bill"); - Assert.assertEquals(bburke.getLastName(), "Burke"); - Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com"); - } - - { - List userModels = adapter.searchUsers("rke@redhat.com", realmModel); - Assert.assertEquals(userModels.size(), 1); - UserModel bburke = userModels.get(0); - Assert.assertEquals(bburke.getFirstName(), "Bill"); - Assert.assertEquals(bburke.getLastName(), "Burke"); - Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com"); - } - - { - List userModels = adapter.searchUsers("bburke", realmModel); - Assert.assertEquals(userModels.size(), 1); - UserModel bburke = userModels.get(0); - Assert.assertEquals(bburke.getFirstName(), "Bill"); - Assert.assertEquals(bburke.getLastName(), "Burke"); - Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com"); - } - - { - List userModels = adapter.searchUsers("BurK", realmModel); - Assert.assertEquals(userModels.size(), 1); - UserModel bburke = userModels.get(0); - Assert.assertEquals(bburke.getFirstName(), "Bill"); - Assert.assertEquals(bburke.getLastName(), "Burke"); - Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com"); - } - - { - List userModels = adapter.searchUsers("Burke", realmModel); - Assert.assertEquals(userModels.size(), 1); - UserModel bburke = userModels.get(0); - Assert.assertEquals(bburke.getFirstName(), "Bill"); - Assert.assertEquals(bburke.getLastName(), "Burke"); - Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com"); - } - - { - UserModel user = realmManager.getSession().users().addUser(realmModel, "mburke"); - user.setLastName("Burke"); - user.setFirstName("Monica"); - user.setEmail("mburke@redhat.com"); - } - - { - UserModel user = realmManager.getSession().users().addUser(realmModel, "thor"); - user.setLastName("Thorgersen"); - user.setFirstName("Stian"); - user.setEmail("thor@redhat.com"); - } - - { - List userModels = adapter.searchUsers("Monica Burke", realmModel); - Assert.assertEquals(userModels.size(), 1); - UserModel bburke = userModels.get(0); - Assert.assertEquals(bburke.getFirstName(), "Monica"); - Assert.assertEquals(bburke.getLastName(), "Burke"); - Assert.assertEquals(bburke.getEmail(), "mburke@redhat.com"); - } - - - { - List userModels = adapter.searchUsers("mburke@redhat.com", realmModel); - Assert.assertEquals(userModels.size(), 1); - UserModel bburke = userModels.get(0); - Assert.assertEquals(bburke.getFirstName(), "Monica"); - Assert.assertEquals(bburke.getLastName(), "Burke"); - Assert.assertEquals(bburke.getEmail(), "mburke@redhat.com"); - } - - { - List userModels = adapter.searchUsers("mburke", realmModel); - Assert.assertEquals(userModels.size(), 1); - UserModel bburke = userModels.get(0); - Assert.assertEquals(bburke.getFirstName(), "Monica"); - Assert.assertEquals(bburke.getLastName(), "Burke"); - Assert.assertEquals(bburke.getEmail(), "mburke@redhat.com"); - } - - { - List userModels = adapter.searchUsers("Burke", realmModel); - Assert.assertEquals(userModels.size(), 2); - UserModel first = userModels.get(0); - UserModel second = userModels.get(1); - if (!first.getEmail().equals("bburke@redhat.com") && !second.getEmail().equals("bburke@redhat.com")) { - Assert.fail(); - } - if (!first.getEmail().equals("mburke@redhat.com") && !second.getEmail().equals("mburke@redhat.com")) { - Assert.fail(); - } - } - - RealmModel otherRealm = adapter.createRealm("other"); - realmManager.getSession().users().addUser(otherRealm, "bburke"); - - Assert.assertEquals(1, realmManager.getSession().users().getUsers(otherRealm, false).size()); - Assert.assertEquals(1, realmManager.getSession().users().searchForUser("bu", otherRealm).size()); - } - - - @Test - public void testRoles() throws Exception { - test1CreateRealm(); - realmModel.addRole("admin"); - realmModel.addRole("user"); - Set roles = realmModel.getRoles(); - Assert.assertEquals(5, roles.size()); - UserModel user = realmManager.getSession().users().addUser(realmModel, "bburke"); - RoleModel realmUserRole = realmModel.getRole("user"); - user.grantRole(realmUserRole); - Assert.assertTrue(user.hasRole(realmUserRole)); - RoleModel found = realmModel.getRoleById(realmUserRole.getId()); - assertNotNull(found); - assertRolesEquals(found, realmUserRole); - - // Test app roles - ClientModel application = realmModel.addClient("app1"); - application.addRole("user"); - application.addRole("bar"); - Set appRoles = application.getRoles(); - Assert.assertEquals(2, appRoles.size()); - RoleModel appBarRole = application.getRole("bar"); - assertNotNull(appBarRole); - - found = realmModel.getRoleById(appBarRole.getId()); - assertNotNull(found); - assertRolesEquals(found, appBarRole); - - user.grantRole(appBarRole); - user.grantRole(application.getRole("user")); - - roles = user.getRealmRoleMappings(); - Assert.assertEquals(4, roles.size()); - assertRolesContains(realmUserRole, roles); - Assert.assertTrue(user.hasRole(realmUserRole)); - // Role "foo" is default realm role - Assert.assertTrue(user.hasRole(realmModel.getRole("foo"))); - - roles = user.getClientRoleMappings(application); - Assert.assertEquals(roles.size(), 2); - assertRolesContains(application.getRole("user"), roles); - assertRolesContains(appBarRole, roles); - Assert.assertTrue(user.hasRole(appBarRole)); - - // Test that application role 'user' don't clash with realm role 'user' - Assert.assertNotEquals(realmModel.getRole("user").getId(), application.getRole("user").getId()); - - Assert.assertEquals(8, user.getRoleMappings().size()); - - // Revoke some roles - user.deleteRoleMapping(realmModel.getRole("foo")); - user.deleteRoleMapping(appBarRole); - roles = user.getRoleMappings(); - Assert.assertEquals(6, roles.size()); - assertRolesContains(realmUserRole, roles); - assertRolesContains(application.getRole("user"), roles); - Assert.assertFalse(user.hasRole(appBarRole)); - } - - @Test - public void testScopes() throws Exception { - test1CreateRealm(); - RoleModel realmRole = realmModel.addRole("realm"); - - ClientModel app1 = realmModel.addClient("app1"); - RoleModel appRole = app1.addRole("app"); - - ClientModel app2 = realmModel.addClient("app2"); - app2.addScopeMapping(realmRole); - app2.addScopeMapping(appRole); - - ClientModel client = realmModel.addClient("client"); - client.addScopeMapping(realmRole); - client.addScopeMapping(appRole); - - commit(); - - realmModel = model.getRealmByName("JUGGLER"); - app1 = realmModel.getClientByClientId("app1"); - app2 = realmModel.getClientByClientId("app2"); - client = realmModel.getClientByClientId("client"); - - Set scopeMappings = app2.getScopeMappings(); - Assert.assertEquals(2, scopeMappings.size()); - Assert.assertTrue(scopeMappings.contains(realmModel.getRole("realm"))); - Assert.assertTrue(scopeMappings.contains(app1.getRole("app"))); - - scopeMappings = client.getScopeMappings(); - Assert.assertEquals(2, scopeMappings.size()); - Assert.assertTrue(scopeMappings.contains(realmModel.getRole("realm"))); - Assert.assertTrue(scopeMappings.contains(app1.getRole("app"))); - } - - @Test - public void testRealmNameCollisions() throws Exception { - test1CreateRealm(); - - commit(); - - // Try to create realm with duplicate name - try { - test1CreateRealm(); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - commit(true); - - // Try to rename realm to duplicate name - realmManager.createRealm("JUGGLER2"); - commit(); - try { - realmManager.getRealmByName("JUGGLER2").setName("JUGGLER"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - - resetSession(); - } - - @Test - public void testAppNameCollisions() throws Exception { - realmManager.createRealm("JUGGLER1").addClient("app1"); - realmManager.createRealm("JUGGLER2").addClient("app1"); - - commit(); - - // Try to create app with duplicate name - try { - realmManager.getRealmByName("JUGGLER1").addClient("app1"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - commit(true); - - // Ty to rename app to duplicate name - realmManager.getRealmByName("JUGGLER1").addClient("app2"); - commit(); - try { - realmManager.getRealmByName("JUGGLER1").getClientByClientId("app2").setClientId("app1"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - - resetSession(); - } - - @Test - public void testClientNameCollisions() throws Exception { - realmManager.createRealm("JUGGLER1").addClient("client1"); - realmManager.createRealm("JUGGLER2").addClient("client1"); - - commit(); - - // Try to create app with duplicate name - try { - realmManager.getRealmByName("JUGGLER1").addClient("client1"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - commit(true); - - // Ty to rename app to duplicate name - realmManager.getRealmByName("JUGGLER1").addClient("client2"); - commit(); - try { - realmManager.getRealmByName("JUGGLER1").addClient("client2").setClientId("client1"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - - resetSession(); - } - - @Test - public void testUsernameCollisions() throws Exception { - RealmModel juggler1 = realmManager.createRealm("JUGGLER1"); - realmManager.getSession().users().addUser(juggler1, "user1"); - RealmModel juggler2 = realmManager.createRealm("JUGGLER2"); - realmManager.getSession().users().addUser(juggler2, "user1"); - commit(); - - // Try to create user with duplicate login name - try { - juggler1 = realmManager.getRealmByName("JUGGLER1"); - realmManager.getSession().users().addUser(juggler1, "user1"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - commit(true); - - // Ty to rename user to duplicate login name - juggler1 = realmManager.getRealmByName("JUGGLER1"); - realmManager.getSession().users().addUser(juggler1, "user2"); - commit(); - try { - juggler1 = realmManager.getRealmByName("JUGGLER1"); - realmManager.getSession().users().getUserByUsername("user2", juggler1).setUsername("user1"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - - resetSession(); - } - - @Test - public void testEmailCollisions() throws Exception { - RealmModel juggler1 = realmManager.createRealm("JUGGLER1"); - realmManager.getSession().users().addUser(juggler1, "user1").setEmail("email@example.com"); - RealmModel juggler2 = realmManager.createRealm("JUGGLER2"); - realmManager.getSession().users().addUser(juggler2, "user1").setEmail("email@example.com"); - commit(); - - // Try to create user with duplicate email - juggler1 = realmManager.getRealmByName("JUGGLER1"); - try { - realmManager.getSession().users().addUser(juggler1, "user2").setEmail("email@example.com"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - - resetSession(); - - // Ty to rename user to duplicate email - juggler1 = realmManager.getRealmByName("JUGGLER1"); - realmManager.getSession().users().addUser(juggler1, "user3").setEmail("email2@example.com"); - commit(); - try { - juggler1 = realmManager.getRealmByName("JUGGLER1"); - realmManager.getSession().users().getUserByUsername("user3", juggler1).setEmail("email@example.com"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - - resetSession(); - } - - @Test - public void testAppRoleCollisions() throws Exception { - realmManager.createRealm("JUGGLER1").addRole("role1"); - realmManager.getRealmByName("JUGGLER1").addClient("app1").addRole("role1"); - realmManager.getRealmByName("JUGGLER1").addClient("app2").addRole("role1"); - - commit(); - - // Try to add role with same name - try { - realmManager.getRealmByName("JUGGLER1").getClientByClientId("app1").addRole("role1"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - commit(true); - - // Ty to rename role to duplicate name - realmManager.getRealmByName("JUGGLER1").getClientByClientId("app1").addRole("role2"); - commit(); - try { - realmManager.getRealmByName("JUGGLER1").getClientByClientId("app1").getRole("role2").setName("role1"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - - resetSession(); - } - - @Test - public void testRealmRoleCollisions() throws Exception { - realmManager.createRealm("JUGGLER1").addRole("role1"); - realmManager.getRealmByName("JUGGLER1").addClient("app1").addRole("role1"); - realmManager.getRealmByName("JUGGLER1").addClient("app2").addRole("role1"); - - commit(); - - // Try to add role with same name - try { - realmManager.getRealmByName("JUGGLER1").addRole("role1"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - commit(true); - - // Ty to rename role to duplicate name - realmManager.getRealmByName("JUGGLER1").addRole("role2"); - commit(); - try { - realmManager.getRealmByName("JUGGLER1").getRole("role2").setName("role1"); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - - resetSession(); - } - - @Test - public void testUserFederationProviderDisplayNameCollisions() throws Exception { - RealmModel realm = realmManager.createRealm("JUGGLER1"); - Map cfg = Collections.emptyMap(); - realm.addUserFederationProvider("ldap", cfg, 1, "providerName1", -1, -1, 0); - realm.addUserFederationProvider("ldap", cfg, 1, "providerName2", -1, -1, 0); - - commit(); - - // Try to add federation provider with same display name - try { - realmManager.getRealmByName("JUGGLER1").addUserFederationProvider("ldap", cfg, 1, "providerName1", -1, -1, 0); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - commit(true); - - // Try to rename federation provider tu duplicate display name - try { - List fedProviders = realmManager.getRealmByName("JUGGLER1").getUserFederationProviders(); - for (UserFederationProviderModel fedProvider : fedProviders) { - if ("providerName1".equals(fedProvider.getDisplayName())) { - fedProvider.setDisplayName("providerName2"); - realm.updateUserFederationProvider(fedProvider); - break; - } - } - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - commit(true); - - // Try to rename federation provider tu duplicate display name - try { - List fedProviders = realmManager.getRealmByName("JUGGLER1").getUserFederationProviders(); - for (UserFederationProviderModel fedProvider : fedProviders) { - if ("providerName1".equals(fedProvider.getDisplayName())) { - fedProvider.setDisplayName("providerName2"); - break; - } - } - - realm.setUserFederationProviders(fedProviders); - commit(); - Assert.fail("Expected exception"); - } catch (ModelDuplicateException e) { - } - commit(true); - - } - - // KEYCLOAK-2026 - @Test - public void testMasterAdminClient() { - realmModel = realmManager.createRealm("foo-realm"); - ClientModel masterAdminClient = realmModel.getMasterAdminClient(); - Assert.assertEquals(Config.getAdminRealm(), masterAdminClient.getRealm().getId()); - - commit(); - - realmModel = realmManager.getRealmByName("foo-realm"); - masterAdminClient = realmModel.getMasterAdminClient(); - Assert.assertEquals(Config.getAdminRealm(), masterAdminClient.getRealm().getId()); - - realmManager.removeRealm(realmModel); - } - - @Test - public void testComponentModelCRUD() { - // Add - realmModel = realmManager.createRealm("foo-realm"); - UserStorageProviderModel model = new UserStorageProviderModel(); - model.setName("memory"); - model.setPriority(0); - model.setProviderId(UserMapStorageFactory.PROVIDER_ID); - model.setParentId(realmModel.getId()); - ComponentModel createdModel = realmModel.addComponentModel(model); - String id = createdModel.getId(); - Assert.assertNotNull(id); - - commit(); - - realmModel = realmManager.getRealmByName("foo-realm"); - ComponentModel foundModel = realmModel.getComponent(id); - assertComponentModel(foundModel, id, UserMapStorageFactory.PROVIDER_ID, realmModel.getId(), "memory"); - - List components = realmModel.getComponents(); - Assert.assertEquals(components.size(), 1); - assertComponentModel(components.get(0), id, UserMapStorageFactory.PROVIDER_ID, realmModel.getId(), "memory"); - - components = realmModel.getComponents(realmModel.getId(), UserStorageProvider.class.getName()); - Assert.assertEquals(components.size(), 1); - assertComponentModel(components.get(0), id, UserMapStorageFactory.PROVIDER_ID, realmModel.getId(), "memory"); - - // Update - foundModel.getConfig().putSingle("foo", "bar"); - realmModel.updateComponent(foundModel); - - commit(); - - realmModel = realmManager.getRealmByName("foo-realm"); - foundModel = realmModel.getComponent(id); - assertComponentModel(foundModel, id, UserMapStorageFactory.PROVIDER_ID, realmModel.getId(), "memory"); - Assert.assertEquals("bar", foundModel.getConfig().getFirst("foo")); - - // Remove - realmModel.removeComponent(foundModel); - - commit(); - - realmModel = realmManager.getRealmByName("foo-realm"); - foundModel = realmModel.getComponent(id); - Assert.assertNull(foundModel); - } - - private void assertComponentModel(ComponentModel componentModel, String expectedId, String expectedProviderId, String expectedParentId, String expectedName) { - Assert.assertEquals(expectedId, componentModel.getId()); - Assert.assertEquals(expectedProviderId, componentModel.getProviderId()); - Assert.assertEquals(expectedParentId, componentModel.getParentId()); - Assert.assertEquals(expectedName, componentModel.getName()); - } - - private KeyPair generateKeypair() throws NoSuchAlgorithmException { - return KeyPairGenerator.getInstance("RSA").generateKeyPair(); - } - -} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java deleted file mode 100755 index a34f460ebf..0000000000 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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.model; - -import org.junit.Assert; -import org.junit.Test; -import org.keycloak.common.enums.SslRequired; -import org.keycloak.models.PasswordPolicy; -import org.keycloak.models.RealmModel; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.models.utils.ModelToRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; - -public class ModelTest extends AbstractModelTest { - - @Test - public void importExportRealm() { - RealmModel realm = realmManager.createRealm("original"); - realm.setRegistrationAllowed(true); - realm.setRegistrationEmailAsUsername(true); - realm.setResetPasswordAllowed(true); - realm.setEditUsernameAllowed(true); - realm.setSslRequired(SslRequired.EXTERNAL); - realm.setVerifyEmail(true); - realm.setAccessTokenLifespan(1000); - realm.setPasswordPolicy(PasswordPolicy.parse(realmManager.getSession(), "length")); - realm.setAccessCodeLifespan(1001); - realm.setAccessCodeLifespanUserAction(1002); - KeycloakModelUtils.generateRealmKeys(realm); - realm.addDefaultRole("default-role"); - - HashMap smtp = new HashMap(); - smtp.put("from", "auto@keycloak"); - smtp.put("hostname", "localhost"); - realm.setSmtpConfig(smtp); - - realm.setDefaultLocale("en"); - realm.setAccessCodeLifespanLogin(100); - realm.setInternationalizationEnabled(true); - realm.setRegistrationEmailAsUsername(true); - realm.setSupportedLocales(new HashSet(Arrays.asList("en", "cz"))); - realm.setEventsListeners(new HashSet(Arrays.asList("jpa", "mongo", "foo"))); - realm.setEventsExpiration(200); - realm.setEventsEnabled(true); - - RealmModel persisted = realmManager.getRealm(realm.getId()); - assertEquals(realm, persisted); - - RealmModel copy = importExport(realm, "copy"); - assertEquals(realm, copy); - } - - public static void assertEquals(RealmModel expected, RealmModel actual) { - Assert.assertEquals(expected.isRegistrationAllowed(), actual.isRegistrationAllowed()); - Assert.assertEquals(expected.isRegistrationEmailAsUsername(), actual.isRegistrationEmailAsUsername()); - Assert.assertEquals(expected.isResetPasswordAllowed(), actual.isResetPasswordAllowed()); - Assert.assertEquals(expected.isEditUsernameAllowed(), actual.isEditUsernameAllowed()); - Assert.assertEquals(expected.getSslRequired(), actual.getSslRequired()); - Assert.assertEquals(expected.isVerifyEmail(), actual.isVerifyEmail()); - Assert.assertEquals(expected.getAccessTokenLifespan(), actual.getAccessTokenLifespan()); - - Assert.assertEquals(expected.getAccessCodeLifespan(), actual.getAccessCodeLifespan()); - Assert.assertEquals(expected.getAccessCodeLifespanUserAction(), actual.getAccessCodeLifespanUserAction()); - Assert.assertEquals(expected.getPublicKeyPem(), actual.getPublicKeyPem()); - Assert.assertEquals(expected.getPrivateKeyPem(), actual.getPrivateKeyPem()); - - Assert.assertEquals(new HashSet<>(expected.getDefaultRoles()), new HashSet<>(actual.getDefaultRoles())); - - Assert.assertEquals(expected.getSmtpConfig(), actual.getSmtpConfig()); - - Assert.assertEquals(expected.getDefaultLocale(), actual.getDefaultLocale()); - Assert.assertEquals(expected.getAccessCodeLifespanLogin(), actual.getAccessCodeLifespanLogin()); - Assert.assertEquals(expected.isInternationalizationEnabled(), actual.isInternationalizationEnabled()); - Assert.assertEquals(expected.isRegistrationEmailAsUsername(), actual.isRegistrationEmailAsUsername()); - Assert.assertEquals(expected.getSupportedLocales(), actual.getSupportedLocales()); - Assert.assertEquals(expected.getEventsListeners(), actual.getEventsListeners()); - Assert.assertEquals(expected.getEventsExpiration(), actual.getEventsExpiration()); - Assert.assertEquals(expected.isEventsEnabled(), actual.isEventsEnabled()); - } - - private RealmModel importExport(RealmModel src, String copyName) { - RealmRepresentation representation = ModelToRepresentation.toRepresentation(src, true); - representation.setRealm(copyName); - representation.setId(copyName); - RealmModel copy = realmManager.importRealm(representation); - return realmManager.getRealm(copy.getId()); - } - -} 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 e9f3449cb7..3abd719ccb 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 c3b25ecb27..cae3e36e0a 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 : 'RealmKeysDetailCtrl' + 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 : '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 1589ffdff0..8237749e00 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.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]; + } + } + } + + 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]; + } + } + } + }); + + $scope.viewKey = function(key) { + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/view-key.html', + controller: 'ViewKeyCtrl', + resolve: { + key: function () { + return key; + } + } + }) + } +}); + +module.controller('RealmKeysProvidersCtrl', function($scope, Realm, realm, $http, $route, $location, Dialog, Notifications, serverInfo, Components, $modal) { $scope.realm = angular.copy(realm); $scope.enableUpload = false; - $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; - } - } - }, true); + $scope.providers = serverInfo.componentTypes['org.keycloak.keys.KeyProvider']; - $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.'); + 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.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."); }); }); }; +}); - $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('GenericKeystoreCtrl', function($scope, $location, Notifications, $route, Dialog, realm, serverInfo, instance, providerId, Components) { + $scope.create = !instance.providerId; + $scope.realm = realm; - $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.certificateUpload = function($files){ - var reader = new FileReader(); - reader.onload = function(e) { - $scope.$apply(function() { - $scope.certificateUploadContent = e.target.result; - }); - }; - reader.readAsText($files[0]); - $scope.certificateUploadName = $files[0].name; - }; - - $scope.clearImport = function() { - $route.reload(); + 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 4b768633c7..7e481d3165 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 236ad44be4..47c2b04c5c 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 0000000000..5d53d3ff79 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/modal/view-key.html @@ -0,0 +1,18 @@ + + +
{{key}}
\ 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 0000000000..0f9857c695 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-generic.html @@ -0,0 +1,69 @@ + + +
+ + + + + + +
+
+
+ +
+ +
+
+
+ +
+ +
+ {{:: 'console-display-name.tooltip' | translate}} +
+ + + +
+ +
+
+ + +
+
+ +
+
+ + +
+
+
+
+ + \ 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 0000000000..ff47219bf8 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-list.html @@ -0,0 +1,58 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{:: 'status' | translate}}{{:: 'type' | translate}}{{:: 'kid' | translate}}{{:: 'priority' | translate}}{{:: 'provider' | translate}}{{:: 'publicKey' | translate}}{{:: 'certificate' | translate}}
{{key.status}}{{key.type}}{{key.kid}}{{key.providerPriority}}{{key.provider.name}}{{:: 'view' | translate}}{{:: 'view' | translate}}
+ +
+ + \ 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 0000000000..2036e52f1d --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-providers.html @@ -0,0 +1,65 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
{{:: 'name' | translate}}{{:: 'id' | translate}}{{:: 'provider' | translate}}{{:: 'priority' | translate}}{{:: 'actions' | translate}}
{{instance.name}}{{instance.id}}{{instance.providerId}}{{instance.config['priority'][0]}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
+ +
+ + + \ 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 7bebc3b650..cd9cbcdea4 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 @@
-
- - - -
-
- - -
- -
-
-
- - -
- -
-
-
- -
-
- -
-
- -
- Import keys - -
- -
-
- - -
- - {{privateKeyUploadName}} - -
-
- -
- -
-
- - -
- - {{publicKeyUploadName}} - -
-
- -
- -
-
- - -
- - {{certificateUploadName}} - -
-
- -
-
- - -
-
-
-
+ + + + + + + + + + + + + + + + + + + + + + +
{{:: 'type' | translate}}{{:: 'kid' | translate}}{{:: 'provider' | translate}}{{:: 'publicKey' | translate}}{{:: 'certificate' | translate}}
{{key.type}}{{key.kid}}{{key.provider.name}}{{:: 'view' | translate}}{{:: 'view' | translate}}
- \ 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 57bbc06de4..cd0df94252 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 @@
-
+
- +
-
+
+
+ + + +
+
+ +
{{config[option.name]}}
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 7f10767eb6..dfedb75539 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 @@