KEYCLOAK-905
Realm key rotation for OIDC
This commit is contained in:
parent
1e51952006
commit
d2cae0f8c3
161 changed files with 4905 additions and 2704 deletions
|
@ -214,7 +214,7 @@ public class PreAuthActionsHandler {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JWSInput input = new JWSInput(token);
|
JWSInput input = new JWSInput(token);
|
||||||
PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input, deployment);
|
PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input.getHeader().getKeyId(), deployment);
|
||||||
if (RSAProvider.verify(input, publicKey)) {
|
if (RSAProvider.verify(input, publicKey)) {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
|
||||||
|
|
||||||
PublicKey publicKey = pkLocator.getPublicKey(input, deployment);
|
PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
|
||||||
if (publicKey == null) {
|
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");
|
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 {
|
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException {
|
||||||
JWSInput input;
|
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(deployment.getRealmInfoUrl()).checkActive(checkActive).checkTokenType(checkTokenType);
|
||||||
try {
|
PublicKey publicKey = getPublicKey(verifier.getHeader().getKeyId(), deployment);
|
||||||
input = new JWSInput(tokenString);
|
return verifier.publicKey(publicKey).verify().getToken();
|
||||||
} 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ public class HardcodedPublicKeyLocator implements PublicKeyLocator {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) {
|
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,13 +46,7 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
|
||||||
private volatile int lastRequestTime = 0;
|
private volatile int lastRequestTime = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) {
|
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
||||||
String kid = input.getHeader().getKeyId();
|
|
||||||
return getPublicKey(kid, deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
|
||||||
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
|
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
|
||||||
|
|
||||||
// Check if key is in cache.
|
// Check if key is in cache.
|
||||||
|
|
|
@ -28,10 +28,10 @@ import java.security.PublicKey;
|
||||||
public interface PublicKeyLocator {
|
public interface PublicKeyLocator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param input
|
* @param kid
|
||||||
* @param deployment
|
* @param deployment
|
||||||
* @return publicKey, which should be used for verify signature on given "input"
|
* @return publicKey, which should be used for verify signature on given "input"
|
||||||
*/
|
*/
|
||||||
PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment);
|
PublicKey getPublicKey(String kid, KeycloakDeployment deployment);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,11 +140,13 @@ public class CertificateUtils {
|
||||||
*
|
*
|
||||||
* @throws Exception the exception
|
* @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 {
|
try {
|
||||||
X500Name subjectDN = new X500Name("CN=" + subject);
|
X500Name subjectDN = new X500Name("CN=" + subject);
|
||||||
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
|
|
||||||
Date validityStartDate = new Date(System.currentTimeMillis() - 100000);
|
Date validityStartDate = new Date(System.currentTimeMillis() - 100000);
|
||||||
Calendar calendar = Calendar.getInstance();
|
Calendar calendar = Calendar.getInstance();
|
||||||
calendar.add(Calendar.YEAR, 10);
|
calendar.add(Calendar.YEAR, 10);
|
||||||
|
|
76
common/src/main/java/org/keycloak/common/util/KeyUtils.java
Normal file
76
common/src/main/java/org/keycloak/common/util/KeyUtils.java
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.interfaces.RSAPrivateCrtKey;
|
||||||
|
import java.security.spec.RSAPublicKeySpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class KeyUtils {
|
||||||
|
|
||||||
|
private static final String DEFAULT_MESSAGE_DIGEST = "SHA-256";
|
||||||
|
|
||||||
|
private KeyUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyPair generateRsaKeyPair(int keysize) {
|
||||||
|
try {
|
||||||
|
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
|
||||||
|
generator.initialize(keysize);
|
||||||
|
KeyPair keyPair = generator.generateKeyPair();
|
||||||
|
return keyPair;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublicKey extractPublicKey(PrivateKey key) {
|
||||||
|
if (key == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
RSAPrivateCrtKey rsaPrivateCrtKey = (RSAPrivateCrtKey) key;
|
||||||
|
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(rsaPrivateCrtKey.getModulus(), rsaPrivateCrtKey.getPublicExponent());
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
return keyFactory.generatePublic(publicKeySpec);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String createKeyId(Key key) {
|
||||||
|
try {
|
||||||
|
return Base64Url.encode(MessageDigest.getInstance(DEFAULT_MESSAGE_DIGEST).digest(key.getEncoded()));
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class PemException extends RuntimeException {
|
||||||
|
|
||||||
|
public PemException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -18,12 +18,15 @@
|
||||||
package org.keycloak.common.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
|
|
||||||
|
import org.bouncycastle.openssl.PEMWriter;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.StringWriter;
|
||||||
|
import java.security.Key;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,6 +36,7 @@ import java.security.cert.X509Certificate;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public final class PemUtils {
|
public final class PemUtils {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
BouncyIntegration.init();
|
BouncyIntegration.init();
|
||||||
}
|
}
|
||||||
|
@ -40,73 +44,112 @@ public final class PemUtils {
|
||||||
private PemUtils() {
|
private PemUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static X509Certificate decodeCertificate(InputStream is) throws Exception {
|
/**
|
||||||
byte[] der = pemToDer(is);
|
* Decode a X509 Certificate from a PEM string
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(der);
|
*
|
||||||
return DerUtils.decodeCertificate(bis);
|
* @param cert
|
||||||
}
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static X509Certificate decodeCertificate(String cert) {
|
||||||
|
if (cert == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static X509Certificate decodeCertificate(String cert) throws Exception {
|
try {
|
||||||
byte[] der = pemToDer(cert);
|
byte[] der = pemToDer(cert);
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(der);
|
ByteArrayInputStream bis = new ByteArrayInputStream(der);
|
||||||
return DerUtils.decodeCertificate(bis);
|
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
|
* @param pem
|
||||||
* @return
|
* @return
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public static PublicKey decodePublicKey(String pem) throws Exception {
|
public static PublicKey decodePublicKey(String pem) {
|
||||||
byte[] der = pemToDer(pem);
|
if (pem == null) {
|
||||||
return DerUtils.decodePublicKey(der);
|
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
|
* @param pem
|
||||||
* @return
|
* @return
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public static PrivateKey decodePrivateKey(String pem) throws Exception {
|
public static PrivateKey decodePrivateKey(String pem) {
|
||||||
byte[] der = pemToDer(pem);
|
if (pem == null) {
|
||||||
return DerUtils.decodePrivateKey(der);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PrivateKey decodePrivateKey(InputStream is) throws Exception {
|
try {
|
||||||
String pem = pemFromStream(is);
|
byte[] der = pemToDer(pem);
|
||||||
return decodePrivateKey(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
|
* @return
|
||||||
* @throws java.io.IOException
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public static byte[] pemToDer(InputStream is) throws IOException {
|
public static String encodeKey(Key key) {
|
||||||
String pem = pemFromStream(is);
|
return encode(key);
|
||||||
return pemToDer(pem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode a PEM string to DER format
|
* Encode a X509 Certificate to a PEM string
|
||||||
*
|
*
|
||||||
* @param pem
|
* @param certificate
|
||||||
* @return
|
* @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);
|
pem = removeBeginEnd(pem);
|
||||||
return Base64.decode(pem);
|
return Base64.decode(pem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String removeBeginEnd(String pem) {
|
private static String removeBeginEnd(String pem) {
|
||||||
pem = pem.replaceAll("-----BEGIN (.*)-----", "");
|
pem = pem.replaceAll("-----BEGIN (.*)-----", "");
|
||||||
pem = pem.replaceAll("-----END (.*)----", "");
|
pem = pem.replaceAll("-----END (.*)----", "");
|
||||||
pem = pem.replaceAll("\r\n", "");
|
pem = pem.replaceAll("\r\n", "");
|
||||||
|
@ -114,12 +157,4 @@ public final class PemUtils {
|
||||||
return pem.trim();
|
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak;
|
package org.keycloak;
|
||||||
|
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
|
import org.keycloak.jose.jws.JWSHeader;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
import org.keycloak.jose.jws.JWSInputException;
|
||||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||||
|
@ -31,84 +32,123 @@ import java.security.PublicKey;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class RSATokenVerifier {
|
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 {
|
public static RSATokenVerifier create(String tokenString) {
|
||||||
AccessToken token = toAccessToken(tokenString, realmKey);
|
return new RSATokenVerifier(tokenString);
|
||||||
|
|
||||||
tokenVerifications(token, realmUrl, checkActive, checkTokenType);
|
|
||||||
|
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void tokenVerifications(AccessToken token, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
|
public static AccessToken verifyToken(String tokenString, PublicKey publicKey, String realmUrl) throws VerificationException {
|
||||||
String user = token.getSubject();
|
return RSATokenVerifier.create(tokenString).publicKey(publicKey).realmUrl(realmUrl).verify().getToken();
|
||||||
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, boolean checkActive, boolean checkTokenType) throws VerificationException {
|
||||||
|
return RSATokenVerifier.create(tokenString).publicKey(publicKey).realmUrl(realmUrl).checkActive(checkActive).checkTokenType(checkTokenType).verify().getToken();
|
||||||
|
}
|
||||||
|
|
||||||
if (checkTokenType) {
|
public RSATokenVerifier publicKey(PublicKey publicKey) {
|
||||||
String type = token.getType();
|
this.publicKey = publicKey;
|
||||||
if (type == null || !type.equalsIgnoreCase(TokenUtil.TOKEN_TYPE_BEARER)) {
|
return this;
|
||||||
throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + type + "'");
|
}
|
||||||
|
|
||||||
|
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()) {
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.jose.jwk;
|
package org.keycloak.jose.jwk;
|
||||||
|
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
|
import org.keycloak.common.util.KeyUtils;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
@ -53,7 +54,7 @@ public class JWKBuilder {
|
||||||
|
|
||||||
RSAPublicJWK k = new RSAPublicJWK();
|
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.setKeyId(kid);
|
||||||
k.setKeyType(RSAPublicJWK.RSA);
|
k.setKeyType(RSAPublicJWK.RSA);
|
||||||
k.setAlgorithm(RSAPublicJWK.RS256);
|
k.setAlgorithm(RSAPublicJWK.RS256);
|
||||||
|
@ -64,14 +65,6 @@ public class JWKBuilder {
|
||||||
return k;
|
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
|
* Copied from org.apache.commons.codec.binary.Base64
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -24,6 +24,8 @@ import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
*/
|
*/
|
||||||
public class ComponentRepresentation {
|
public class ComponentRepresentation {
|
||||||
|
|
||||||
|
public static final String SECRET_VALUE = "**********";
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
private String name;
|
private String name;
|
||||||
private String providerId;
|
private String providerId;
|
||||||
|
|
|
@ -27,6 +27,7 @@ public class ConfigPropertyRepresentation {
|
||||||
protected String helpText;
|
protected String helpText;
|
||||||
protected String type;
|
protected String type;
|
||||||
protected Object defaultValue;
|
protected Object defaultValue;
|
||||||
|
protected boolean secret;
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
|
@ -67,4 +68,12 @@ public class ConfigPropertyRepresentation {
|
||||||
public void setHelpText(String helpText) {
|
public void setHelpText(String helpText) {
|
||||||
this.helpText = helpText;
|
this.helpText = helpText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSecret() {
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecret(boolean secret) {
|
||||||
|
this.secret = secret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.representations.idm;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class KeysMetadataRepresentation {
|
||||||
|
|
||||||
|
private Map<String, String> active;
|
||||||
|
|
||||||
|
private List<KeyMetadataRepresentation> keys;
|
||||||
|
|
||||||
|
public Map<String, String> getActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActive(Map<String, String> active) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<KeyMetadataRepresentation> getKeys() {
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeys(List<KeyMetadataRepresentation> keys) {
|
||||||
|
this.keys = keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class KeyMetadataRepresentation {
|
||||||
|
private String providerId;
|
||||||
|
private long providerPriority;
|
||||||
|
|
||||||
|
private String kid;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
private String publicKey;
|
||||||
|
private String certificate;
|
||||||
|
|
||||||
|
public String getProviderId() {
|
||||||
|
return providerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProviderId(String providerId) {
|
||||||
|
this.providerId = providerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getProviderPriority() {
|
||||||
|
return providerPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProviderPriority(long providerPriority) {
|
||||||
|
this.providerPriority = providerPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKid() {
|
||||||
|
return kid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKid(String kid) {
|
||||||
|
this.kid = kid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPublicKey() {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPublicKey(String publicKey) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCertificate() {
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCertificate(String certificate) {
|
||||||
|
this.certificate = certificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,11 +19,8 @@ package org.keycloak.representations.idm;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import org.bouncycastle.openssl.PEMWriter;
|
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,17 +82,7 @@ public class PublishedRealmRepresentation {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public void setPublicKey(PublicKey publicKey) {
|
public void setPublicKey(PublicKey publicKey) {
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
StringWriter writer = new StringWriter();
|
this.publicKeyPem = PemUtils.encodeKey(publicKey);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTokenServiceUrl() {
|
public String getTokenServiceUrl() {
|
||||||
|
|
|
@ -72,9 +72,13 @@ public class RealmRepresentation {
|
||||||
protected Integer failureFactor;
|
protected Integer failureFactor;
|
||||||
//--- end brute force settings
|
//--- end brute force settings
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
protected String privateKey;
|
protected String privateKey;
|
||||||
|
@Deprecated
|
||||||
protected String publicKey;
|
protected String publicKey;
|
||||||
|
@Deprecated
|
||||||
protected String certificate;
|
protected String certificate;
|
||||||
|
@Deprecated
|
||||||
protected String codeSecret;
|
protected String codeSecret;
|
||||||
protected RolesRepresentation roles;
|
protected RolesRepresentation roles;
|
||||||
protected List<GroupRepresentation> groups;
|
protected List<GroupRepresentation> groups;
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.admin.client.resource;
|
||||||
|
|
||||||
|
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
||||||
|
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public interface KeyResource {
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
KeysMetadataRepresentation getKeyMetadata();
|
||||||
|
|
||||||
|
}
|
|
@ -207,4 +207,7 @@ public interface RealmResource {
|
||||||
@Path("components")
|
@Path("components")
|
||||||
ComponentsResource components();
|
ComponentsResource components();
|
||||||
|
|
||||||
|
@Path("keys")
|
||||||
|
KeyResource keys();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,10 +60,6 @@ public class RealmAdapter implements RealmModel {
|
||||||
protected RealmCacheSession cacheSession;
|
protected RealmCacheSession cacheSession;
|
||||||
protected RealmModel updated;
|
protected RealmModel updated;
|
||||||
protected RealmCache cache;
|
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) {
|
public RealmAdapter(CachedRealm cached, RealmCacheSession cacheSession) {
|
||||||
this.cached = cached;
|
this.cached = cached;
|
||||||
|
@ -423,123 +419,6 @@ public class RealmAdapter implements RealmModel {
|
||||||
updated.setAccessCodeLifespanLogin(seconds);
|
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
|
@Override
|
||||||
public List<RequiredCredentialModel> getRequiredCredentials() {
|
public List<RequiredCredentialModel> getRequiredCredentials() {
|
||||||
if (isUpdated()) return updated.getRequiredCredentials();
|
if (isUpdated()) return updated.getRequiredCredentials();
|
||||||
|
|
|
@ -89,15 +89,6 @@ public class CachedRealm extends AbstractRevisioned {
|
||||||
protected PasswordPolicy passwordPolicy;
|
protected PasswordPolicy passwordPolicy;
|
||||||
protected OTPPolicy otpPolicy;
|
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 loginTheme;
|
||||||
protected String accountTheme;
|
protected String accountTheme;
|
||||||
protected String adminTheme;
|
protected String adminTheme;
|
||||||
|
@ -191,15 +182,6 @@ public class CachedRealm extends AbstractRevisioned {
|
||||||
passwordPolicy = model.getPasswordPolicy();
|
passwordPolicy = model.getPasswordPolicy();
|
||||||
otpPolicy = model.getOTPPolicy();
|
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();
|
loginTheme = model.getLoginTheme();
|
||||||
accountTheme = model.getAccountTheme();
|
accountTheme = model.getAccountTheme();
|
||||||
adminTheme = model.getAdminTheme();
|
adminTheme = model.getAdminTheme();
|
||||||
|
@ -415,22 +397,6 @@ public class CachedRealm extends AbstractRevisioned {
|
||||||
return accessCodeLifespanLogin;
|
return accessCodeLifespanLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getKeyId() {
|
|
||||||
return keyId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPublicKeyPem() {
|
|
||||||
return publicKeyPem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPrivateKeyPem() {
|
|
||||||
return privateKeyPem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCodeSecret() {
|
|
||||||
return codeSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<RequiredCredentialModel> getRequiredCredentials() {
|
public List<RequiredCredentialModel> getRequiredCredentials() {
|
||||||
return requiredCredentials;
|
return requiredCredentials;
|
||||||
}
|
}
|
||||||
|
@ -507,10 +473,6 @@ public class CachedRealm extends AbstractRevisioned {
|
||||||
return userFederationMappers;
|
return userFederationMappers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCertificatePem() {
|
|
||||||
return certificatePem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IdentityProviderModel> getIdentityProviders() {
|
public List<IdentityProviderModel> getIdentityProviders() {
|
||||||
return identityProviders;
|
return identityProviders;
|
||||||
}
|
}
|
||||||
|
@ -591,18 +553,6 @@ public class CachedRealm extends AbstractRevisioned {
|
||||||
return clientTemplates;
|
return clientTemplates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PublicKey getPublicKey() {
|
|
||||||
return publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrivateKey getPrivateKey() {
|
|
||||||
return privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public X509Certificate getCertificate() {
|
|
||||||
return certificate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<UserFederationMapperModel> getUserFederationMapperSet() {
|
public Set<UserFederationMapperModel> getUserFederationMapperSet() {
|
||||||
return userFederationMapperSet;
|
return userFederationMapperSet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
tx.put(sessionCache, id, entity);
|
tx.put(sessionCache, id, entity);
|
||||||
|
|
||||||
ClientSessionAdapter wrap = wrap(realm, entity, false);
|
ClientSessionAdapter wrap = wrap(realm, entity, false);
|
||||||
wrap.setNote(ClientSessionModel.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
|
|
||||||
return wrap;
|
return wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.connections.jpa.updater.liquibase.custom;
|
||||||
|
|
||||||
|
import liquibase.exception.CustomChangeException;
|
||||||
|
import liquibase.statement.core.InsertStatement;
|
||||||
|
import liquibase.structure.core.Table;
|
||||||
|
import org.keycloak.keys.KeyProvider;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ExtractRealmKeysFromRealmTable extends CustomKeycloakTask {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generateStatementsImpl() throws CustomChangeException {
|
||||||
|
try {
|
||||||
|
PreparedStatement statement = jdbcConnection.prepareStatement("select ID, PRIVATE_KEY, CERTIFICATE from " + getTableName("REALM"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
ResultSet resultSet = statement.executeQuery();
|
||||||
|
try {
|
||||||
|
while (resultSet.next()) {
|
||||||
|
String realmId = resultSet.getString(1);
|
||||||
|
String privateKeyPem = resultSet.getString(2);
|
||||||
|
String certificatePem = resultSet.getString(3);
|
||||||
|
|
||||||
|
String componentId = KeycloakModelUtils.generateId();
|
||||||
|
|
||||||
|
InsertStatement insertComponent = new InsertStatement(null, null, database.correctObjectName("COMPONENT", Table.class))
|
||||||
|
.addColumnValue("ID", componentId)
|
||||||
|
.addColumnValue("REALM_ID", realmId)
|
||||||
|
.addColumnValue("PARENT_ID", realmId)
|
||||||
|
.addColumnValue("NAME", "rsa")
|
||||||
|
.addColumnValue("PROVIDER_ID", "rsa")
|
||||||
|
.addColumnValue("PROVIDER_TYPE", KeyProvider.class.getName());
|
||||||
|
|
||||||
|
statements.add(insertComponent);
|
||||||
|
|
||||||
|
statements.add(componentConfigStatement(componentId, "priority", "100"));
|
||||||
|
statements.add(componentConfigStatement(componentId, "privateKey", privateKeyPem));
|
||||||
|
statements.add(componentConfigStatement(componentId, "certificate", certificatePem));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
resultSet.close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
statement.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmationMessage.append("Updated " + statements.size() + " records in USER_FEDERATION_PROVIDER table");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new CustomChangeException(getTaskId() + ": Exception when updating data from previous version", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private InsertStatement componentConfigStatement(String componentId, String name, String value) {
|
||||||
|
return new InsertStatement(null, null, database.correctObjectName("COMPONENT_CONFIG", Table.class))
|
||||||
|
.addColumnValue("ID", KeycloakModelUtils.generateId())
|
||||||
|
.addColumnValue("COMPONENT_ID", componentId)
|
||||||
|
.addColumnValue("NAME", name)
|
||||||
|
.addColumnValue("VALUE", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getTaskId() {
|
||||||
|
return "Update 2.3.0.Final";
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ package org.keycloak.models.jpa;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
import org.keycloak.component.ComponentFactory;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.jose.jwk.JWKBuilder;
|
import org.keycloak.jose.jwk.JWKBuilder;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
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.RoleEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserFederationMapperEntity;
|
import org.keycloak.models.jpa.entities.UserFederationMapperEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserFederationProviderEntity;
|
import org.keycloak.models.jpa.entities.UserFederationProviderEntity;
|
||||||
|
import org.keycloak.models.utils.ComponentUtil;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
@ -88,10 +90,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
protected static final Logger logger = Logger.getLogger(RealmAdapter.class);
|
protected static final Logger logger = Logger.getLogger(RealmAdapter.class);
|
||||||
protected RealmEntity realm;
|
protected RealmEntity realm;
|
||||||
protected EntityManager em;
|
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;
|
protected KeycloakSession session;
|
||||||
private PasswordPolicy passwordPolicy;
|
private PasswordPolicy passwordPolicy;
|
||||||
private OTPPolicy otpPolicy;
|
private OTPPolicy otpPolicy;
|
||||||
|
@ -488,106 +486,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
em.flush();
|
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) {
|
protected RequiredCredentialModel initRequiredCredentialModel(String type) {
|
||||||
RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
|
RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
|
||||||
if (model == null) {
|
if (model == null) {
|
||||||
|
@ -2138,6 +2036,13 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComponentModel addComponentModel(ComponentModel model) {
|
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();
|
ComponentEntity c = new ComponentEntity();
|
||||||
if (model.getId() == null) {
|
if (model.getId() == null) {
|
||||||
c.setId(KeycloakModelUtils.generateId());
|
c.setId(KeycloakModelUtils.generateId());
|
||||||
|
@ -2171,6 +2076,8 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateComponent(ComponentModel component) {
|
public void updateComponent(ComponentModel component) {
|
||||||
|
ComponentUtil.getComponentFactory(session, component).validateConfiguration(session, component);
|
||||||
|
|
||||||
ComponentEntity c = em.find(ComponentEntity.class, component.getId());
|
ComponentEntity c = em.find(ComponentEntity.class, component.getId());
|
||||||
if (c == null) return;
|
if (c == null) return;
|
||||||
c.setName(component.getName());
|
c.setName(component.getName());
|
||||||
|
|
|
@ -117,15 +117,6 @@ public class RealmEntity {
|
||||||
@Column(name="NOT_BEFORE")
|
@Column(name="NOT_BEFORE")
|
||||||
protected int notBefore;
|
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")
|
@Column(name="LOGIN_THEME")
|
||||||
protected String loginTheme;
|
protected String loginTheme;
|
||||||
@Column(name="ACCOUNT_THEME")
|
@Column(name="ACCOUNT_THEME")
|
||||||
|
@ -384,30 +375,6 @@ public class RealmEntity {
|
||||||
this.accessCodeLifespanLogin = accessCodeLifespanLogin;
|
this.accessCodeLifespanLogin = accessCodeLifespanLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPublicKeyPem() {
|
|
||||||
return publicKeyPem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPublicKeyPem(String publicKeyPem) {
|
|
||||||
this.publicKeyPem = publicKeyPem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPrivateKeyPem() {
|
|
||||||
return privateKeyPem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrivateKeyPem(String privateKeyPem) {
|
|
||||||
this.privateKeyPem = privateKeyPem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCodeSecret() {
|
|
||||||
return codeSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCodeSecret(String codeSecret) {
|
|
||||||
this.codeSecret = codeSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<RequiredCredentialEntity> getRequiredCredentials() {
|
public Collection<RequiredCredentialEntity> getRequiredCredentials() {
|
||||||
return requiredCredentials;
|
return requiredCredentials;
|
||||||
}
|
}
|
||||||
|
@ -567,14 +534,6 @@ public class RealmEntity {
|
||||||
this.attributes = attributes;
|
this.attributes = attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCertificatePem() {
|
|
||||||
return certificatePem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCertificatePem(String certificatePem) {
|
|
||||||
this.certificatePem = certificatePem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IdentityProviderEntity> getIdentityProviders() {
|
public List<IdentityProviderEntity> getIdentityProviders() {
|
||||||
return this.identityProviders;
|
return this.identityProviders;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,13 @@
|
||||||
<addColumn tableName="IDENTITY_PROVIDER">
|
<addColumn tableName="IDENTITY_PROVIDER">
|
||||||
<column name="PROVIDER_DISPLAY_NAME" type="VARCHAR(255)"></column>
|
<column name="PROVIDER_DISPLAY_NAME" type="VARCHAR(255)"></column>
|
||||||
</addColumn>
|
</addColumn>
|
||||||
|
|
||||||
|
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.ExtractRealmKeysFromRealmTable"/>
|
||||||
|
<dropColumn tableName="REALM" columnName="CODE_SECRET" />
|
||||||
|
<dropColumn tableName="REALM" columnName="PRIVATE_KEY" />
|
||||||
|
<dropColumn tableName="REALM" columnName="PUBLIC_KEY" />
|
||||||
|
<dropColumn tableName="REALM" columnName="CERTIFICATE" />
|
||||||
|
|
||||||
</changeSet>
|
</changeSet>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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_7_0;
|
||||||
import org.keycloak.connections.mongo.updater.impl.updates.Update1_8_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.Update1_9_2;
|
||||||
|
import org.keycloak.connections.mongo.updater.impl.updates.Update2_3_0;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -57,7 +58,8 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
|
||||||
Update1_4_0.class,
|
Update1_4_0.class,
|
||||||
Update1_7_0.class,
|
Update1_7_0.class,
|
||||||
Update1_8_0.class,
|
Update1_8_0.class,
|
||||||
Update1_9_2.class
|
Update1_9_2.class,
|
||||||
|
Update2_3_0.class
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.connections.mongo.updater.impl.updates;
|
||||||
|
|
||||||
|
import com.mongodb.BasicDBList;
|
||||||
|
import com.mongodb.BasicDBObject;
|
||||||
|
import com.mongodb.DBCollection;
|
||||||
|
import com.mongodb.DBCursor;
|
||||||
|
import org.keycloak.keys.KeyProvider;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.mongo.keycloak.entities.ComponentEntity;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class Update2_3_0 extends Update {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "2.3.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(KeycloakSession session) {
|
||||||
|
|
||||||
|
DBCollection realms = db.getCollection("realms");
|
||||||
|
DBCursor cursor = realms.find();
|
||||||
|
while (cursor.hasNext()) {
|
||||||
|
BasicDBObject realm = (BasicDBObject) cursor.next();
|
||||||
|
|
||||||
|
String realmId = realm.getString("_id");
|
||||||
|
|
||||||
|
String privateKeyPem = realm.getString("privateKeyPem");
|
||||||
|
String certificatePem = realm.getString("certificatePem");
|
||||||
|
|
||||||
|
BasicDBList entities = (BasicDBList) realm.get("componentEntities");
|
||||||
|
|
||||||
|
BasicDBObject component = new BasicDBObject();
|
||||||
|
component.put("id", KeycloakModelUtils.generateId());
|
||||||
|
component.put("name", "rsa");
|
||||||
|
component.put("providerType", KeyProvider.class.getName());
|
||||||
|
component.put("providerId", "rsa");
|
||||||
|
component.put("parentId", realmId);
|
||||||
|
|
||||||
|
BasicDBObject config = new BasicDBObject();
|
||||||
|
config.put("priority", Collections.singletonList("100"));
|
||||||
|
config.put("privateKey", Collections.singletonList(privateKeyPem));
|
||||||
|
config.put("certificate", Collections.singletonList(certificatePem));
|
||||||
|
|
||||||
|
component.put("config", config);
|
||||||
|
|
||||||
|
entities.add(component);
|
||||||
|
|
||||||
|
realm.remove("privateKeyPem");
|
||||||
|
realm.remove("certificatePem");
|
||||||
|
realm.remove("publicKeyPem");
|
||||||
|
realm.remove("codeSecret");
|
||||||
|
|
||||||
|
realms.update(new BasicDBObject().append("_id", realmId), realm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.RequiredCredentialEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.UserFederationMapperEntity;
|
import org.keycloak.models.mongo.keycloak.entities.UserFederationMapperEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.UserFederationProviderEntity;
|
import org.keycloak.models.mongo.keycloak.entities.UserFederationProviderEntity;
|
||||||
|
import org.keycloak.models.utils.ComponentUtil;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
@ -85,11 +86,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
private final MongoRealmEntity realm;
|
private final MongoRealmEntity realm;
|
||||||
private final RealmProvider model;
|
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 OTPPolicy otpPolicy;
|
||||||
private volatile transient PasswordPolicy passwordPolicy;
|
private volatile transient PasswordPolicy passwordPolicy;
|
||||||
private volatile transient KeycloakSession session;
|
private volatile transient KeycloakSession session;
|
||||||
|
@ -455,110 +451,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
return realm.getAccessCodeLifespanLogin();
|
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
|
@Override
|
||||||
public String getLoginTheme() {
|
public String getLoginTheme() {
|
||||||
return realm.getLoginTheme();
|
return realm.getLoginTheme();
|
||||||
|
@ -2062,6 +1954,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComponentModel addComponentModel(ComponentModel model) {
|
public ComponentModel addComponentModel(ComponentModel model) {
|
||||||
|
ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, model);
|
||||||
|
|
||||||
ComponentEntity entity = new ComponentEntity();
|
ComponentEntity entity = new ComponentEntity();
|
||||||
if (model.getId() == null) {
|
if (model.getId() == null) {
|
||||||
entity.setId(KeycloakModelUtils.generateId());
|
entity.setId(KeycloakModelUtils.generateId());
|
||||||
|
@ -2082,6 +1976,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateComponent(ComponentModel model) {
|
public void updateComponent(ComponentModel model) {
|
||||||
|
ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, model);
|
||||||
|
|
||||||
for (ComponentEntity entity : realm.getComponentEntities()) {
|
for (ComponentEntity entity : realm.getComponentEntities()) {
|
||||||
if (entity.getId().equals(model.getId())) {
|
if (entity.getId().equals(model.getId())) {
|
||||||
entity.setConfig(model.getConfig());
|
entity.setConfig(model.getConfig());
|
||||||
|
|
|
@ -70,11 +70,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
|
||||||
private int accessCodeLifespanLogin;
|
private int accessCodeLifespanLogin;
|
||||||
private int notBefore;
|
private int notBefore;
|
||||||
|
|
||||||
private String publicKeyPem;
|
|
||||||
private String privateKeyPem;
|
|
||||||
private String certificatePem;
|
|
||||||
private String codeSecret;
|
|
||||||
|
|
||||||
private String loginTheme;
|
private String loginTheme;
|
||||||
private String accountTheme;
|
private String accountTheme;
|
||||||
private String adminTheme;
|
private String adminTheme;
|
||||||
|
@ -351,30 +346,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
|
||||||
this.notBefore = notBefore;
|
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() {
|
public String getLoginTheme() {
|
||||||
return loginTheme;
|
return loginTheme;
|
||||||
}
|
}
|
||||||
|
@ -527,14 +498,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
|
||||||
this.identityProviders = identityProviders;
|
this.identityProviders = identityProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCertificatePem() {
|
|
||||||
return certificatePem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCertificatePem(String certificatePem) {
|
|
||||||
this.certificatePem = certificatePem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInternationalizationEnabled() {
|
public boolean isInternationalizationEnabled() {
|
||||||
return internationalizationEnabled;
|
return internationalizationEnabled;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,6 @@ public interface ComponentFactory<CreatedType, ProviderType extends Provider> ex
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException;
|
void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.component;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stored configuration of a User Storage provider instance.
|
* Stored configuration of a User Storage provider instance.
|
||||||
|
@ -35,6 +36,7 @@ public class ComponentModel implements Serializable {
|
||||||
private String providerType;
|
private String providerType;
|
||||||
private String parentId;
|
private String parentId;
|
||||||
private MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
private MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
||||||
|
private transient ConcurrentHashMap<String, Object> notes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public ComponentModel() {}
|
public ComponentModel() {}
|
||||||
|
|
||||||
|
@ -71,6 +73,57 @@ public class ComponentModel implements Serializable {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean contains(String key) {
|
||||||
|
return config.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String key) {
|
||||||
|
return config.getFirst(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get(String key, int defaultValue) {
|
||||||
|
String s = config.getFirst(key);
|
||||||
|
return s != null ? Integer.parseInt(s) : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long get(String key, long defaultValue) {
|
||||||
|
String s = config.getFirst(key);
|
||||||
|
return s != null ? Long.parseLong(s) : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean get(String key, boolean defaultValue) {
|
||||||
|
String s = config.getFirst(key);
|
||||||
|
return s != null ? Boolean.parseBoolean(s) : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(String key, String value) {
|
||||||
|
config.putSingle(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(String key, int value) {
|
||||||
|
config.putSingle(key, Integer.toString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(String key, long value) {
|
||||||
|
config.putSingle(key, Long.toString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(String key, boolean value) {
|
||||||
|
config.putSingle(key, Boolean.toString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNote(String key) {
|
||||||
|
return notes.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getNote(String key) {
|
||||||
|
return (T) notes.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNote(String key, Object object) {
|
||||||
|
notes.put(key, object);
|
||||||
|
}
|
||||||
|
|
||||||
public String getProviderId() {
|
public String getProviderId() {
|
||||||
return providerId;
|
return providerId;
|
||||||
}
|
}
|
||||||
|
|
104
server-spi/src/main/java/org/keycloak/keys/KeyMetadata.java
Normal file
104
server-spi/src/main/java/org/keycloak/keys/KeyMetadata.java
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class KeyMetadata {
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
ACTIVE, PASSIVE, DISABLED
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
RSA
|
||||||
|
}
|
||||||
|
|
||||||
|
private String providerId;
|
||||||
|
private long providerPriority;
|
||||||
|
|
||||||
|
private String kid;
|
||||||
|
|
||||||
|
private Status status;
|
||||||
|
|
||||||
|
private Type type;
|
||||||
|
|
||||||
|
private PublicKey publicKey;
|
||||||
|
private Certificate certificate;
|
||||||
|
|
||||||
|
public String getProviderId() {
|
||||||
|
return providerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProviderId(String providerId) {
|
||||||
|
this.providerId = providerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getProviderPriority() {
|
||||||
|
return providerPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProviderPriority(long providerPriority) {
|
||||||
|
this.providerPriority = providerPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKid() {
|
||||||
|
return kid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKid(String kid) {
|
||||||
|
this.kid = kid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Status status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(Type type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicKey getPublicKey() {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPublicKey(PublicKey publicKey) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Certificate getCertificate() {
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCertificate(Certificate certificate) {
|
||||||
|
this.certificate = certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
69
server-spi/src/main/java/org/keycloak/keys/KeyProvider.java
Normal file
69
server-spi/src/main/java/org/keycloak/keys/KeyProvider.java
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public interface KeyProvider extends Provider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the KID for the active keypair, or <code>null</code> if no active key is available.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String getKid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the private key for the active keypair, or <code>null</code> if no active key is available.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
PrivateKey getPrivateKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the public key for the specified kid, or <code>null</code> if the kid is unknown.
|
||||||
|
*
|
||||||
|
* @param kid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
PublicKey getPublicKey(String kid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the certificate for the specified kid, or <code>null</code> if the kid is unknown.
|
||||||
|
*
|
||||||
|
* @param kid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
X509Certificate getCertificate(String kid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return metadata about all keypairs held by the provider
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<KeyMetadata> getKeyMetadata();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.keycloak.component.ComponentFactory;
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public interface KeyProviderFactory<T extends KeyProvider> extends ComponentFactory<T, KeyProvider> {
|
||||||
|
|
||||||
|
T create(KeycloakSession session, ComponentModel model);
|
||||||
|
|
||||||
|
}
|
|
@ -15,50 +15,33 @@
|
||||||
* limitations under the License.
|
* 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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class PasswordToken {
|
public class KeySpi implements Spi {
|
||||||
|
@Override
|
||||||
private String realm;
|
public boolean isInternal() {
|
||||||
private String user;
|
return true;
|
||||||
private int timestamp;
|
|
||||||
|
|
||||||
public PasswordToken() {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PasswordToken(String realm, String user) {
|
@Override
|
||||||
this.realm = realm;
|
public String getName() {
|
||||||
this.user = user;
|
return "keys";
|
||||||
this.timestamp = Time.currentTime();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRealm() {
|
@Override
|
||||||
return realm;
|
public Class<? extends Provider> getProviderClass() {
|
||||||
|
return KeyProvider.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRealm(String realm) {
|
@Override
|
||||||
this.realm = realm;
|
public Class<? extends ProviderFactory> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -24,7 +24,7 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public interface ClientSessionModel {
|
public interface ClientSessionModel {
|
||||||
public static final String ACTION_KEY = "action_key";
|
public static final String ACTION_SIGNATURE = "action_signature";
|
||||||
|
|
||||||
public String getId();
|
public String getId();
|
||||||
public RealmModel getRealm();
|
public RealmModel getRealm();
|
||||||
|
|
71
server-spi/src/main/java/org/keycloak/models/KeyManager.java
Normal file
71
server-spi/src/main/java/org/keycloak/models/KeyManager.java
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import org.keycloak.keys.KeyMetadata;
|
||||||
|
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public interface KeyManager {
|
||||||
|
|
||||||
|
ActiveKey getActiveKey(RealmModel realm);
|
||||||
|
|
||||||
|
PublicKey getPublicKey(RealmModel realm, String kid);
|
||||||
|
|
||||||
|
Certificate getCertificate(RealmModel realm, String kid);
|
||||||
|
|
||||||
|
List<KeyMetadata> getKeys(RealmModel realm, boolean includeDisabled);
|
||||||
|
|
||||||
|
class ActiveKey {
|
||||||
|
private final String kid;
|
||||||
|
private final PrivateKey privateKey;
|
||||||
|
private final PublicKey publicKey;
|
||||||
|
private final X509Certificate certificate;
|
||||||
|
|
||||||
|
public ActiveKey(String kid, PrivateKey privateKey, PublicKey publicKey, X509Certificate certificate) {
|
||||||
|
this.kid = kid;
|
||||||
|
this.privateKey = privateKey;
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.certificate = certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKid() {
|
||||||
|
return kid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivateKey getPrivateKey() {
|
||||||
|
return privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicKey getPublicKey() {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate getCertificate() {
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -145,6 +145,12 @@ public interface KeycloakSession {
|
||||||
*/
|
*/
|
||||||
UserFederatedStorageProvider userFederatedStorage();
|
UserFederatedStorageProvider userFederatedStorage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key manager
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
KeyManager keys();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keycloak scripting support.
|
* Keycloak scripting support.
|
||||||
|
|
|
@ -178,35 +178,6 @@ public interface RealmModel extends RoleContainerModel {
|
||||||
|
|
||||||
void setAccessCodeLifespanLogin(int seconds);
|
void setAccessCodeLifespanLogin(int seconds);
|
||||||
|
|
||||||
String getKeyId();
|
|
||||||
|
|
||||||
String getPublicKeyPem();
|
|
||||||
|
|
||||||
void setPublicKeyPem(String publicKeyPem);
|
|
||||||
|
|
||||||
String getPrivateKeyPem();
|
|
||||||
|
|
||||||
void setPrivateKeyPem(String privateKeyPem);
|
|
||||||
|
|
||||||
PublicKey getPublicKey();
|
|
||||||
|
|
||||||
void setPublicKey(PublicKey publicKey);
|
|
||||||
|
|
||||||
String getCodeSecret();
|
|
||||||
|
|
||||||
Key getCodeSecretKey();
|
|
||||||
|
|
||||||
void setCodeSecret(String codeSecret);
|
|
||||||
|
|
||||||
X509Certificate getCertificate();
|
|
||||||
void setCertificate(X509Certificate certificate);
|
|
||||||
String getCertificatePem();
|
|
||||||
void setCertificatePem(String certificate);
|
|
||||||
|
|
||||||
PrivateKey getPrivateKey();
|
|
||||||
|
|
||||||
void setPrivateKey(PrivateKey privateKey);
|
|
||||||
|
|
||||||
List<RequiredCredentialModel> getRequiredCredentials();
|
List<RequiredCredentialModel> getRequiredCredentials();
|
||||||
|
|
||||||
void addRequiredCredential(String cred);
|
void addRequiredCredential(String cred);
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
|
import org.keycloak.component.ComponentFactory;
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class ComponentUtil {
|
||||||
|
|
||||||
|
public static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, ComponentModel component) {
|
||||||
|
try {
|
||||||
|
List<ProviderConfigProperty> l = getComponentFactory(session, component).getConfigProperties();
|
||||||
|
Map<String, ProviderConfigProperty> properties = new HashMap<>();
|
||||||
|
for (ProviderConfigProperty p : l) {
|
||||||
|
properties.put(p.getName(), p);
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ComponentFactory getComponentFactory(KeycloakSession session, ComponentModel component) {
|
||||||
|
Class<? extends Provider> provider;
|
||||||
|
try {
|
||||||
|
provider = (Class<? extends Provider>) Class.forName(component.getProviderType());
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException("Invalid provider type '" + component.getProviderType() + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
ProviderFactory<? extends Provider> f = session.getKeycloakSessionFactory().getProviderFactory(provider, component.getProviderId());
|
||||||
|
if (f == null) {
|
||||||
|
throw new RuntimeException("No such provider '" + component.getProviderId() + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
ComponentFactory cf = (ComponentFactory) f;
|
||||||
|
return cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,15 +17,9 @@
|
||||||
|
|
||||||
package org.keycloak.models.utils;
|
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.OTPPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.representations.PasswordToken;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -33,29 +27,6 @@ import org.keycloak.representations.PasswordToken;
|
||||||
*/
|
*/
|
||||||
public class CredentialValidation {
|
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) {
|
public static boolean validOTP(RealmModel realm, String token, String secret) {
|
||||||
OTPPolicy policy = realm.getOTPPolicy();
|
OTPPolicy policy = realm.getOTPPolicy();
|
||||||
if (policy.getType().equals(UserCredentialModel.TOTP)) {
|
if (policy.getType().equals(UserCredentialModel.TOTP)) {
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.keys.KeyProvider;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class DefaultKeyProviders {
|
||||||
|
|
||||||
|
public static void createProviders(RealmModel realm) {
|
||||||
|
ComponentModel generated = new ComponentModel();
|
||||||
|
generated.setName("rsa-generated");
|
||||||
|
generated.setParentId(realm.getId());
|
||||||
|
generated.setProviderId("rsa-generated");
|
||||||
|
generated.setProviderType(KeyProvider.class.getName());
|
||||||
|
|
||||||
|
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
||||||
|
config.putSingle("priority", "100");
|
||||||
|
generated.setConfig(config);
|
||||||
|
|
||||||
|
realm.addComponentModel(generated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void createProviders(RealmModel realm, String privateKeyPem, String certificatePem) {
|
||||||
|
ComponentModel rsa = new ComponentModel();
|
||||||
|
rsa.setName("rsa");
|
||||||
|
rsa.setParentId(realm.getId());
|
||||||
|
rsa.setProviderId("rsa");
|
||||||
|
rsa.setProviderType(KeyProvider.class.getName());
|
||||||
|
|
||||||
|
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
||||||
|
config.putSingle("priority", "100");
|
||||||
|
config.putSingle("privateKey", privateKeyPem);
|
||||||
|
if (certificatePem != null) {
|
||||||
|
config.putSingle("certificate", certificatePem);
|
||||||
|
}
|
||||||
|
rsa.setConfig(config);
|
||||||
|
|
||||||
|
realm.addComponentModel(rsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,12 +17,15 @@
|
||||||
|
|
||||||
package org.keycloak.models.utils;
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
import org.bouncycastle.openssl.PEMWriter;
|
|
||||||
import org.keycloak.broker.social.SocialIdentityProvider;
|
import org.keycloak.broker.social.SocialIdentityProvider;
|
||||||
import org.keycloak.broker.social.SocialIdentityProviderFactory;
|
import org.keycloak.broker.social.SocialIdentityProviderFactory;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.common.util.CertificateUtils;
|
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.PemUtils;
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.keys.KeyProvider;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticationFlowModel;
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
@ -52,8 +55,6 @@ import javax.crypto.spec.SecretKeySpec;
|
||||||
import javax.transaction.InvalidTransactionException;
|
import javax.transaction.InvalidTransactionException;
|
||||||
import javax.transaction.SystemException;
|
import javax.transaction.SystemException;
|
||||||
import javax.transaction.Transaction;
|
import javax.transaction.Transaction;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
|
@ -135,82 +136,19 @@ public final class KeycloakModelUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getPemFromKey(Key key) {
|
public static String getPemFromKey(Key key) {
|
||||||
StringWriter writer = new StringWriter();
|
return PemUtils.encodeKey(key);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getPemFromCertificate(X509Certificate certificate) {
|
public static String getPemFromCertificate(X509Certificate certificate) {
|
||||||
StringWriter writer = new StringWriter();
|
return PemUtils.encodeCertificate(certificate);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CertificateRepresentation generateKeyPairCertificate(String subject) {
|
public static CertificateRepresentation generateKeyPairCertificate(String subject) {
|
||||||
KeyPair keyPair = null;
|
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
|
||||||
try {
|
X509Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, subject);
|
||||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
|
|
||||||
generator.initialize(2048);
|
String privateKeyPem = PemUtils.encodeKey(keyPair.getPrivate());
|
||||||
keyPair = generator.generateKeyPair();
|
String certPem = PemUtils.encodeCertificate(certificate);
|
||||||
} 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);
|
|
||||||
|
|
||||||
CertificateRepresentation rep = new CertificateRepresentation();
|
CertificateRepresentation rep = new CertificateRepresentation();
|
||||||
rep.setPrivateKey(privateKeyPem);
|
rep.setPrivateKey(privateKeyPem);
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.authorization.model.Scope;
|
||||||
import org.keycloak.authorization.store.PolicyStore;
|
import org.keycloak.authorization.store.PolicyStore;
|
||||||
import org.keycloak.authorization.store.ResourceStore;
|
import org.keycloak.authorization.store.ResourceStore;
|
||||||
import org.keycloak.authorization.store.StoreFactory;
|
import org.keycloak.authorization.store.StoreFactory;
|
||||||
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
@ -265,16 +266,6 @@ public class ModelToRepresentation {
|
||||||
rep.setEnabled(realm.isEnabled());
|
rep.setEnabled(realm.isEnabled());
|
||||||
rep.setNotBefore(realm.getNotBefore());
|
rep.setNotBefore(realm.getNotBefore());
|
||||||
rep.setSslRequired(realm.getSslRequired().name().toLowerCase());
|
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.setRegistrationAllowed(realm.isRegistrationAllowed());
|
||||||
rep.setRegistrationEmailAsUsername(realm.isRegistrationEmailAsUsername());
|
rep.setRegistrationEmailAsUsername(realm.isRegistrationEmailAsUsername());
|
||||||
rep.setRememberMe(realm.isRememberMe());
|
rep.setRememberMe(realm.isRememberMe());
|
||||||
|
@ -783,19 +774,38 @@ public class ModelToRepresentation {
|
||||||
propRep.setType(prop.getType());
|
propRep.setType(prop.getType());
|
||||||
propRep.setDefaultValue(prop.getDefaultValue());
|
propRep.setDefaultValue(prop.getDefaultValue());
|
||||||
propRep.setHelpText(prop.getHelpText());
|
propRep.setHelpText(prop.getHelpText());
|
||||||
|
propRep.setSecret(prop.isSecret());
|
||||||
propertiesRep.add(propRep);
|
propertiesRep.add(propRep);
|
||||||
}
|
}
|
||||||
return propertiesRep;
|
return propertiesRep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ComponentRepresentation toRepresentation(ComponentModel component) {
|
public static ComponentRepresentation toRepresentation(KeycloakSession session, ComponentModel component, boolean internal) {
|
||||||
ComponentRepresentation rep = new ComponentRepresentation();
|
ComponentRepresentation rep = new ComponentRepresentation();
|
||||||
rep.setId(component.getId());
|
rep.setId(component.getId());
|
||||||
rep.setName(component.getName());
|
rep.setName(component.getName());
|
||||||
rep.setProviderId(component.getProviderId());
|
rep.setProviderId(component.getProviderId());
|
||||||
rep.setProviderType(component.getProviderType());
|
rep.setProviderType(component.getProviderType());
|
||||||
rep.setParentId(component.getParentId());
|
rep.setParentId(component.getParentId());
|
||||||
rep.setConfig(component.getConfig());
|
if (internal) {
|
||||||
|
rep.setConfig(component.getConfig());
|
||||||
|
} else {
|
||||||
|
Map<String, ProviderConfigProperty> configProperties = ComponentUtil.getComponentConfigProperties(session, component);
|
||||||
|
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<String>> e : component.getConfig().entrySet()) {
|
||||||
|
ProviderConfigProperty configProperty = configProperties.get(e.getKey());
|
||||||
|
if (configProperty != null) {
|
||||||
|
if (configProperty.isSecret()) {
|
||||||
|
config.putSingle(e.getKey(), ComponentRepresentation.SECRET_VALUE);
|
||||||
|
} else {
|
||||||
|
config.put(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rep.setConfig(config);
|
||||||
|
}
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,14 @@ import org.keycloak.authorization.store.ScopeStore;
|
||||||
import org.keycloak.authorization.store.StoreFactory;
|
import org.keycloak.authorization.store.StoreFactory;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.common.util.Base64;
|
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.MultivaluedHashMap;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.common.util.UriUtils;
|
import org.keycloak.common.util.UriUtils;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
import org.keycloak.keys.KeyProvider;
|
||||||
import org.keycloak.migration.MigrationProvider;
|
import org.keycloak.migration.MigrationProvider;
|
||||||
import org.keycloak.migration.migrators.MigrationUtils;
|
import org.keycloak.migration.migrators.MigrationUtils;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
@ -63,6 +67,7 @@ import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserFederationMapperModel;
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.representations.idm.ApplicationRepresentation;
|
import org.keycloak.representations.idm.ApplicationRepresentation;
|
||||||
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
|
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
|
||||||
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
|
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
|
||||||
|
@ -99,12 +104,16 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import java.io.IOException;
|
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.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
@ -183,23 +192,6 @@ public class RepresentationToModel {
|
||||||
if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail());
|
if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail());
|
||||||
if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed());
|
if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed());
|
||||||
if (rep.isEditUsernameAllowed() != null) newRealm.setEditUsernameAllowed(rep.isEditUsernameAllowed());
|
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.getLoginTheme() != null) newRealm.setLoginTheme(rep.getLoginTheme());
|
||||||
if (rep.getAccountTheme() != null) newRealm.setAccountTheme(rep.getAccountTheme());
|
if (rep.getAccountTheme() != null) newRealm.setAccountTheme(rep.getAccountTheme());
|
||||||
if (rep.getAdminTheme() != null) newRealm.setAdminTheme(rep.getAdminTheme());
|
if (rep.getAdminTheme() != null) newRealm.setAdminTheme(rep.getAdminTheme());
|
||||||
|
@ -381,6 +373,13 @@ public class RepresentationToModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newRealm.getComponents(newRealm.getId(), KeyProvider.class.getName()).isEmpty()) {
|
||||||
|
if (rep.getPrivateKey() != null) {
|
||||||
|
DefaultKeyProviders.createProviders(newRealm, rep.getPrivateKey(), rep.getCertificate());
|
||||||
|
} else {
|
||||||
|
DefaultKeyProviders.createProviders(newRealm);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void importComponents(RealmModel newRealm, MultivaluedHashMap<String, ComponentExportRepresentation> components, String parentId) {
|
protected static void importComponents(RealmModel newRealm, MultivaluedHashMap<String, ComponentExportRepresentation> components, String parentId) {
|
||||||
|
@ -819,20 +818,6 @@ public class RepresentationToModel {
|
||||||
realm.setUserFederationProviders(providerModels);
|
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){
|
if(rep.isInternationalizationEnabled() != null){
|
||||||
realm.setInternationalizationEnabled(rep.isInternationalizationEnabled());
|
realm.setInternationalizationEnabled(rep.isInternationalizationEnabled());
|
||||||
}
|
}
|
||||||
|
@ -1692,17 +1677,82 @@ public class RepresentationToModel {
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ComponentModel toModel(KeycloakSession session, ComponentRepresentation rep) {
|
||||||
public static ComponentModel toModel(ComponentRepresentation rep) {
|
|
||||||
ComponentModel model = new ComponentModel();
|
ComponentModel model = new ComponentModel();
|
||||||
model.setParentId(rep.getParentId());
|
model.setParentId(rep.getParentId());
|
||||||
model.setProviderType(rep.getProviderType());
|
model.setProviderType(rep.getProviderType());
|
||||||
model.setProviderId(rep.getProviderId());
|
model.setProviderId(rep.getProviderId());
|
||||||
model.setConfig(rep.getConfig());
|
model.setConfig(new MultivaluedHashMap<>());
|
||||||
model.setName(rep.getName());
|
model.setName(rep.getName());
|
||||||
|
|
||||||
|
if (rep.getConfig() != null) {
|
||||||
|
Set<String> keys = new HashSet<>(rep.getConfig().keySet());
|
||||||
|
for (String k : keys) {
|
||||||
|
List<String> values = rep.getConfig().get(k);
|
||||||
|
if (values != null) {
|
||||||
|
ListIterator<String> itr = values.listIterator();
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
String v = itr.next();
|
||||||
|
if (v == null || v.trim().isEmpty()) {
|
||||||
|
itr.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!values.isEmpty()) {
|
||||||
|
model.getConfig().put(k, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void updateComponent(KeycloakSession session, ComponentRepresentation rep, ComponentModel component, boolean internal) {
|
||||||
|
if (rep.getParentId() != null) {
|
||||||
|
component.setParentId(rep.getParentId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rep.getProviderType() != null) {
|
||||||
|
component.setProviderType(rep.getProviderType());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rep.getProviderId() != null) {
|
||||||
|
component.setProviderId(rep.getProviderId());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, ProviderConfigProperty> providerConfiguration = null;
|
||||||
|
if (!internal) {
|
||||||
|
providerConfiguration = ComponentUtil.getComponentConfigProperties(session, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rep.getConfig() != null) {
|
||||||
|
Set<String> keys = new HashSet<>(rep.getConfig().keySet());
|
||||||
|
for (String k : keys) {
|
||||||
|
if (!internal && !providerConfiguration.containsKey(k)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> values = rep.getConfig().get(k);
|
||||||
|
if (values == null || values.isEmpty() || values.get(0) == null || values.get(0).trim().isEmpty()) {
|
||||||
|
component.getConfig().remove(k);
|
||||||
|
} else {
|
||||||
|
ListIterator<String> itr = values.listIterator();
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
String v = itr.next();
|
||||||
|
if (v == null || v.trim().isEmpty() || v.equals(ComponentRepresentation.SECRET_VALUE)) {
|
||||||
|
itr.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!values.isEmpty()) {
|
||||||
|
component.getConfig().put(k, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void importAuthorizationSettings(ClientRepresentation clientRepresentation, ClientModel client, KeycloakSession session) {
|
public static void importAuthorizationSettings(ClientRepresentation clientRepresentation, ClientModel client, KeycloakSession session) {
|
||||||
if (Boolean.TRUE.equals(clientRepresentation.getAuthorizationServicesEnabled())) {
|
if (Boolean.TRUE.equals(clientRepresentation.getAuthorizationServicesEnabled())) {
|
||||||
AuthorizationProviderFactory authorizationFactory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class);
|
AuthorizationProviderFactory authorizationFactory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class);
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.provider;
|
||||||
|
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.component.ComponentValidationException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class ConfigurationValidationHelper {
|
||||||
|
|
||||||
|
private ComponentModel model;
|
||||||
|
|
||||||
|
private ConfigurationValidationHelper(ComponentModel model) {
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConfigurationValidationHelper check(ComponentModel model) {
|
||||||
|
return new ConfigurationValidationHelper(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationValidationHelper checkInt(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
|
||||||
|
return checkInt(property.getName(), property.getLabel(), required);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationValidationHelper checkInt(String key, String label, boolean required) throws ComponentValidationException {
|
||||||
|
checkSingle(key, label, required);
|
||||||
|
|
||||||
|
String val = model.getConfig().getFirst(key);
|
||||||
|
if (val != null) {
|
||||||
|
try {
|
||||||
|
Integer.parseInt(val);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new ComponentValidationException(label + " should be a number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationValidationHelper checkLong(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
|
||||||
|
return checkLong(property.getName(), property.getLabel(), required);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationValidationHelper checkLong(String key, String label, boolean required) throws ComponentValidationException {
|
||||||
|
checkSingle(key, label, required);
|
||||||
|
|
||||||
|
String val = model.getConfig().getFirst(key);
|
||||||
|
if (val != null) {
|
||||||
|
try {
|
||||||
|
Long.parseLong(val);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new ComponentValidationException(label + " should be a number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationValidationHelper checkSingle(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
|
||||||
|
return checkSingle(property.getName(), property.getLabel(), required);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationValidationHelper checkSingle(String key, String label, boolean required) throws ComponentValidationException {
|
||||||
|
if (model.getConfig().containsKey(key) && model.getConfig().get(key).size() > 1) {
|
||||||
|
throw new ComponentValidationException(label + " should be a single entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (required) {
|
||||||
|
checkRequired(key, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationValidationHelper checkRequired(ProviderConfigProperty property) throws ComponentValidationException {
|
||||||
|
return checkRequired(property.getName(), property.getLabel());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationValidationHelper checkRequired(String key, String label) throws ComponentValidationException {
|
||||||
|
List<String> values = model.getConfig().get(key);
|
||||||
|
if (values == null) {
|
||||||
|
throw new ComponentValidationException(label + " is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationValidationHelper checkBoolean(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
|
||||||
|
return checkBoolean(property.getName(), property.getLabel(), required);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationValidationHelper checkBoolean(String key, String label, boolean required) {
|
||||||
|
checkSingle(key, label, required);
|
||||||
|
|
||||||
|
String val = model.getConfig().getFirst(key);
|
||||||
|
if (val != null && !(val.equals("true") || val.equals("false"))) {
|
||||||
|
throw new ComponentValidationException(label + " should be 'true' or 'false'");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ public class ProviderConfigProperty {
|
||||||
public static final String BOOLEAN_TYPE="boolean";
|
public static final String BOOLEAN_TYPE="boolean";
|
||||||
public static final String STRING_TYPE="String";
|
public static final String STRING_TYPE="String";
|
||||||
public static final String SCRIPT_TYPE="Script";
|
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 ROLE_TYPE="Role";
|
||||||
public static final String LIST_TYPE="List";
|
public static final String LIST_TYPE="List";
|
||||||
public static final String CLIENT_LIST_TYPE="ClientList";
|
public static final String CLIENT_LIST_TYPE="ClientList";
|
||||||
|
@ -35,6 +36,7 @@ public class ProviderConfigProperty {
|
||||||
protected String helpText;
|
protected String helpText;
|
||||||
protected String type;
|
protected String type;
|
||||||
protected Object defaultValue;
|
protected Object defaultValue;
|
||||||
|
protected boolean secret;
|
||||||
|
|
||||||
public ProviderConfigProperty() {
|
public ProviderConfigProperty() {
|
||||||
}
|
}
|
||||||
|
@ -47,6 +49,11 @@ public class ProviderConfigProperty {
|
||||||
this.defaultValue = defaultValue;
|
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() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -86,4 +93,13 @@ public class ProviderConfigProperty {
|
||||||
public void setHelpText(String helpText) {
|
public void setHelpText(String helpText) {
|
||||||
this.helpText = helpText;
|
this.helpText = helpText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSecret() {
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecret(boolean secret) {
|
||||||
|
this.secret = secret;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.provider;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class ProviderConfigurationBuilder {
|
||||||
|
|
||||||
|
private List<ProviderConfigProperty> properties = new LinkedList<>();
|
||||||
|
|
||||||
|
private ProviderConfigurationBuilder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProviderConfigurationBuilder create() {
|
||||||
|
return new ProviderConfigurationBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProviderConfigPropertyBuilder property() {
|
||||||
|
return new ProviderConfigPropertyBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProviderConfigurationBuilder property(ProviderConfigProperty property) {
|
||||||
|
properties.add(property);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProviderConfigurationBuilder property(String name, String label, String helpText, String type, Object defaultValue, boolean secret) {
|
||||||
|
ProviderConfigProperty property = new ProviderConfigProperty(name, label, helpText, type, defaultValue);
|
||||||
|
property.setSecret(secret);
|
||||||
|
properties.add(property);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public ProviderConfigurationBuilder property(String name, String label, String helpText, String type, Object defaultValue) {
|
||||||
|
properties.add(new ProviderConfigProperty(name, label, helpText, type, defaultValue));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ProviderConfigProperty> build() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProviderConfigPropertyBuilder {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String label;
|
||||||
|
private String helpText;
|
||||||
|
private String type;
|
||||||
|
private Object defaultValue;
|
||||||
|
private boolean secret;
|
||||||
|
|
||||||
|
public ProviderConfigPropertyBuilder name(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProviderConfigPropertyBuilder label(String label) {
|
||||||
|
this.label = label;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProviderConfigPropertyBuilder helpText(String helpText) {
|
||||||
|
this.helpText = helpText;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProviderConfigPropertyBuilder type(String type) {
|
||||||
|
this.type = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProviderConfigPropertyBuilder defaultValue(Object defaultValue) {
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProviderConfigPropertyBuilder secret(boolean secret) {
|
||||||
|
this.secret = secret;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProviderConfigurationBuilder add() {
|
||||||
|
ProviderConfigProperty property = new ProviderConfigProperty();
|
||||||
|
property.setName(name);
|
||||||
|
property.setLabel(label);
|
||||||
|
property.setHelpText(helpText);
|
||||||
|
property.setType(type);
|
||||||
|
property.setDefaultValue(defaultValue);
|
||||||
|
property.setSecret(secret);
|
||||||
|
ProviderConfigurationBuilder.this.properties.add(property);
|
||||||
|
return ProviderConfigurationBuilder.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,18 +17,23 @@
|
||||||
|
|
||||||
package org.keycloak.services.managers;
|
package org.keycloak.services.managers;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.common.util.Time;
|
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.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.ClientTemplateModel;
|
import org.keycloak.models.ClientTemplateModel;
|
||||||
|
import org.keycloak.models.KeyManager;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import java.security.PublicKey;
|
||||||
import java.security.Key;
|
import java.security.Signature;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -38,8 +43,11 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public class ClientSessionCode {
|
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 RealmModel realm;
|
||||||
private final ClientSessionModel clientSession;
|
private final ClientSessionModel clientSession;
|
||||||
|
|
||||||
|
@ -49,32 +57,12 @@ public class ClientSessionCode {
|
||||||
USER
|
USER
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClientSessionCode(RealmModel realm, ClientSessionModel clientSession) {
|
public ClientSessionCode(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
|
||||||
|
this.session = session;
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
this.clientSession = clientSession;
|
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 {
|
public static class ParseResult {
|
||||||
ClientSessionCode code;
|
ClientSessionCode code;
|
||||||
boolean clientSessionNotFound;
|
boolean clientSessionNotFound;
|
||||||
|
@ -114,13 +102,12 @@ public class ClientSessionCode {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
String hash = createHash(realm, result.clientSession);
|
if (!verifyCode(code, session, realm, result.clientSession)) {
|
||||||
if (!hash.equals(parts[0])) {
|
|
||||||
result.illegalHash = true;
|
result.illegalHash = true;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.code = new ClientSessionCode(realm, result.clientSession);
|
result.code = new ClientSessionCode(session, realm, result.clientSession);
|
||||||
return result;
|
return result;
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
result.illegalHash = true;
|
result.illegalHash = true;
|
||||||
|
@ -128,8 +115,6 @@ public class ClientSessionCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static ClientSessionCode parse(String code, KeycloakSession session, RealmModel realm) {
|
public static ClientSessionCode parse(String code, KeycloakSession session, RealmModel realm) {
|
||||||
try {
|
try {
|
||||||
String[] parts = code.split("\\.");
|
String[] parts = code.split("\\.");
|
||||||
|
@ -140,12 +125,11 @@ public class ClientSessionCode {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String hash = createHash(realm, clientSession);
|
if (!verifyCode(code, session, realm, clientSession)) {
|
||||||
if (!hash.equals(parts[0])) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ClientSessionCode(realm, clientSession);
|
return new ClientSessionCode(session, realm, clientSession);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -194,7 +178,7 @@ public class ClientSessionCode {
|
||||||
|
|
||||||
|
|
||||||
public Set<RoleModel> getRequestedRoles() {
|
public Set<RoleModel> getRequestedRoles() {
|
||||||
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
|
Set<RoleModel> requestedRoles = new HashSet<>();
|
||||||
for (String roleId : clientSession.getRoles()) {
|
for (String roleId : clientSession.getRoles()) {
|
||||||
RoleModel role = realm.getRoleById(roleId);
|
RoleModel role = realm.getRoleById(roleId);
|
||||||
if (role != null) {
|
if (role != null) {
|
||||||
|
@ -205,7 +189,7 @@ public class ClientSessionCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<ProtocolMapperModel> getRequestedProtocolMappers() {
|
public Set<ProtocolMapperModel> getRequestedProtocolMappers() {
|
||||||
Set<ProtocolMapperModel> requestedProtocolMappers = new HashSet<ProtocolMapperModel>();
|
Set<ProtocolMapperModel> requestedProtocolMappers = new HashSet<>();
|
||||||
Set<String> protocolMappers = clientSession.getProtocolMappers();
|
Set<String> protocolMappers = clientSession.getProtocolMappers();
|
||||||
ClientModel client = clientSession.getClient();
|
ClientModel client = clientSession.getClient();
|
||||||
ClientTemplateModel template = client.getClientTemplate();
|
ClientTemplateModel template = client.getClientTemplate();
|
||||||
|
@ -229,32 +213,67 @@ public class ClientSessionCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCode() {
|
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) {
|
private static String generateCode(KeycloakSession session, 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) {
|
|
||||||
try {
|
try {
|
||||||
Key codeSecretKey = realm.getCodeSecretKey();
|
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||||
Mac mac = Mac.getInstance(codeSecretKey.getAlgorithm());
|
|
||||||
mac.init(codeSecretKey);
|
String secret = KeycloakModelUtils.generateSecret();
|
||||||
mac.update(clientSession.getId().getBytes());
|
|
||||||
mac.update(HASH_SEPERATOR);
|
StringBuilder sb = new StringBuilder();
|
||||||
mac.update(clientSession.getNote(ClientSessionModel.ACTION_KEY).getBytes());
|
sb.append(secret);
|
||||||
return Base64Url.encode(mac.doFinal());
|
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) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,3 +65,4 @@ org.keycloak.transaction.TransactionManagerLookupSpi
|
||||||
org.keycloak.credential.hash.PasswordHashSpi
|
org.keycloak.credential.hash.PasswordHashSpi
|
||||||
org.keycloak.credential.CredentialSpi
|
org.keycloak.credential.CredentialSpi
|
||||||
org.keycloak.keys.PublicKeyStorageSpi
|
org.keycloak.keys.PublicKeyStorageSpi
|
||||||
|
org.keycloak.keys.KeySpi
|
|
@ -212,7 +212,7 @@ public class AuthenticationProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generateCode() {
|
public String generateCode() {
|
||||||
ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
|
ClientSessionCode accessCode = new ClientSessionCode(session, getRealm(), getClientSession());
|
||||||
clientSession.setTimestamp(Time.currentTime());
|
clientSession.setTimestamp(Time.currentTime());
|
||||||
return accessCode.getCode();
|
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
|
// 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());
|
accessCode.setAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name());
|
||||||
clientSession.setTimestamp(Time.currentTime());
|
clientSession.setTimestamp(Time.currentTime());
|
||||||
|
|
||||||
|
@ -764,7 +764,7 @@ public class AuthenticationProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkClientSession() {
|
public void checkClientSession() {
|
||||||
ClientSessionCode code = new ClientSessionCode(realm, clientSession);
|
ClientSessionCode code = new ClientSessionCode(session, realm, clientSession);
|
||||||
String action = ClientSessionModel.Action.AUTHENTICATE.name();
|
String action = ClientSessionModel.Action.AUTHENTICATE.name();
|
||||||
if (!code.isValidAction(action)) {
|
if (!code.isValidAction(action)) {
|
||||||
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_SESSION);
|
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_SESSION);
|
||||||
|
@ -862,7 +862,7 @@ public class AuthenticationProcessor {
|
||||||
protected Response authenticationComplete() {
|
protected Response authenticationComplete() {
|
||||||
attachSession();
|
attachSession();
|
||||||
if (isActionRequired()) {
|
if (isActionRequired()) {
|
||||||
return redirectToRequiredActions(realm, clientSession, uriInfo);
|
return redirectToRequiredActions(session, realm, clientSession, uriInfo);
|
||||||
} else {
|
} 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
|
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);
|
return AuthenticationManager.finishedRequiredActions(session, userSession, clientSession, connection, request, uriInfo, event);
|
||||||
|
|
|
@ -148,7 +148,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateCode() {
|
public String generateCode() {
|
||||||
ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
|
ClientSessionCode accessCode = new ClientSessionCode(session, getRealm(), getClientSession());
|
||||||
clientSession.setTimestamp(Time.currentTime());
|
clientSession.setTimestamp(Time.currentTime());
|
||||||
return accessCode.getCode();
|
return accessCode.getCode();
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,7 +256,6 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
|
||||||
ctx.setLastName(getLastName());
|
ctx.setLastName(getLastName());
|
||||||
ctx.setBrokerSessionId(getBrokerSessionId());
|
ctx.setBrokerSessionId(getBrokerSessionId());
|
||||||
ctx.setBrokerUserId(getBrokerUserId());
|
ctx.setBrokerUserId(getBrokerUserId());
|
||||||
ctx.setCode(getCode());
|
|
||||||
ctx.setToken(getToken());
|
ctx.setToken(getToken());
|
||||||
|
|
||||||
RealmModel realm = clientSession.getRealm();
|
RealmModel realm = clientSession.getRealm();
|
||||||
|
@ -297,7 +296,6 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
|
||||||
ctx.setLastName(context.getLastName());
|
ctx.setLastName(context.getLastName());
|
||||||
ctx.setBrokerSessionId(context.getBrokerSessionId());
|
ctx.setBrokerSessionId(context.getBrokerSessionId());
|
||||||
ctx.setBrokerUserId(context.getBrokerUserId());
|
ctx.setBrokerUserId(context.getBrokerUserId());
|
||||||
ctx.setCode(context.getCode());
|
|
||||||
ctx.setToken(context.getToken());
|
ctx.setToken(context.getToken());
|
||||||
ctx.setIdentityProviderId(context.getIdpConfig().getAlias());
|
ctx.setIdentityProviderId(context.getIdpConfig().getAlias());
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class IdentityProviderAuthenticator implements Authenticator {
|
||||||
List<IdentityProviderModel> identityProviders = context.getRealm().getIdentityProviders();
|
List<IdentityProviderModel> identityProviders = context.getRealm().getIdentityProviders();
|
||||||
for (IdentityProviderModel identityProvider : identityProviders) {
|
for (IdentityProviderModel identityProvider : identityProviders) {
|
||||||
if (identityProvider.isEnabled() && providerId.equals(identityProvider.getAlias())) {
|
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(
|
Response response = Response.seeOther(
|
||||||
Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode))
|
Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode))
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.authorization.util.Permissions;
|
||||||
import org.keycloak.authorization.util.Tokens;
|
import org.keycloak.authorization.util.Tokens;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
import org.keycloak.jose.jws.JWSInputException;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
@ -77,6 +78,9 @@ public class AuthorizationTokenService {
|
||||||
@Context
|
@Context
|
||||||
private HttpRequest httpRequest;
|
private HttpRequest httpRequest;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private KeycloakSession session;
|
||||||
|
|
||||||
public AuthorizationTokenService(AuthorizationProvider authorization) {
|
public AuthorizationTokenService(AuthorizationProvider authorization) {
|
||||||
this.authorization = authorization;
|
this.authorization = authorization;
|
||||||
}
|
}
|
||||||
|
@ -180,7 +184,7 @@ public class AuthorizationTokenService {
|
||||||
String rpt = request.getRpt();
|
String rpt = request.getRpt();
|
||||||
|
|
||||||
if (rpt != null && !"".equals(rpt)) {
|
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);
|
throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,13 +256,13 @@ public class AuthorizationTokenService {
|
||||||
authorization.setPermissions(permissions);
|
authorization.setPermissions(permissions);
|
||||||
accessToken.setAuthorization(authorization);
|
accessToken.setAuthorization(authorization);
|
||||||
|
|
||||||
return new TokenManager().encodeToken(getRealm(), accessToken);
|
return new TokenManager().encodeToken(session, getRealm(), accessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PermissionTicket verifyPermissionTicket(AuthorizationRequest request) {
|
private PermissionTicket verifyPermissionTicket(AuthorizationRequest request) {
|
||||||
String ticketString = request.getTicket();
|
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);
|
throw new ErrorResponseException("invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.authorization.config;
|
package org.keycloak.authorization.config;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
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(),
|
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, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()),
|
||||||
URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "token").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
|
@Override
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
import org.keycloak.jose.jws.JWSInputException;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakContext;
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
@ -81,6 +82,9 @@ public class EntitlementService {
|
||||||
@Context
|
@Context
|
||||||
private HttpRequest request;
|
private HttpRequest request;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private KeycloakSession session;
|
||||||
|
|
||||||
public EntitlementService(AuthorizationProvider authorization) {
|
public EntitlementService(AuthorizationProvider authorization) {
|
||||||
this.authorization = authorization;
|
this.authorization = authorization;
|
||||||
}
|
}
|
||||||
|
@ -200,7 +204,7 @@ public class EntitlementService {
|
||||||
authorization.setPermissions(permissions);
|
authorization.setPermissions(permissions);
|
||||||
accessToken.setAuthorization(authorization);
|
accessToken.setAuthorization(authorization);
|
||||||
|
|
||||||
return new TokenManager().encodeToken(realm, accessToken);
|
return new TokenManager().encodeToken(this.authorization.getKeycloakSession(), realm, accessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ResourcePermission> createPermissions(EntitlementRequest entitlementRequest, ResourceServer resourceServer, AuthorizationProvider authorization) {
|
private List<ResourcePermission> createPermissions(EntitlementRequest entitlementRequest, ResourceServer resourceServer, AuthorizationProvider authorization) {
|
||||||
|
@ -252,7 +256,7 @@ public class EntitlementService {
|
||||||
|
|
||||||
if (rpt != null && !"".equals(rpt)) {
|
if (rpt != null && !"".equals(rpt)) {
|
||||||
KeycloakContext context = authorization.getKeycloakSession().getContext();
|
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);
|
throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.authorization.protection.permission.representation.Permissio
|
||||||
import org.keycloak.authorization.protection.permission.representation.PermissionResponse;
|
import org.keycloak.authorization.protection.permission.representation.PermissionResponse;
|
||||||
import org.keycloak.authorization.store.StoreFactory;
|
import org.keycloak.authorization.store.StoreFactory;
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
import org.keycloak.jose.jws.JWSBuilder;
|
||||||
|
import org.keycloak.models.KeyManager;
|
||||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
|
@ -130,7 +131,8 @@ public class AbstractPermissionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createPermissionTicket(List<ResourceRepresentation> resources) {
|
private String createPermissionTicket(List<ResourceRepresentation> resources) {
|
||||||
return new JWSBuilder().jsonContent(new PermissionTicket(resources, this.resourceServer.getId(), this.identity.getAccessToken()))
|
KeyManager.ActiveKey keys = this.authorization.getKeycloakSession().keys().getActiveKey(this.authorization.getRealm());
|
||||||
.rsa256(this.authorization.getKeycloakSession().getContext().getRealm().getPrivateKey());
|
return new JWSBuilder().kid(keys.getKid()).jsonContent(new PermissionTicket(resources, this.resourceServer.getId(), this.identity.getAccessToken()))
|
||||||
|
.rsa256(keys.getPrivateKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||||
import org.keycloak.models.KeycloakContext;
|
import org.keycloak.models.KeycloakContext;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.managers.AppAuthManager;
|
import org.keycloak.services.managers.AppAuthManager;
|
||||||
|
@ -53,10 +54,10 @@ public class Tokens {
|
||||||
return authManager.extractAuthorizationHeaderToken(keycloakSession.getContext().getRequestHeaders());
|
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 {
|
try {
|
||||||
JWSInput jws = new JWSInput(token);
|
JWSInput jws = new JWSInput(token);
|
||||||
|
PublicKey publicKey = keycloakSession.keys().getPublicKey(realm, jws.getHeader().getKeyId());
|
||||||
return RSAProvider.verify(jws, publicKey);
|
return RSAProvider.verify(jws, publicKey);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ErrorResponseException("invalid_signature", "Unexpected error while validating signature.", Status.INTERNAL_SERVER_ERROR);
|
throw new ErrorResponseException("invalid_signature", "Unexpected error while validating signature.", Status.INTERNAL_SERVER_ERROR);
|
||||||
|
|
|
@ -233,9 +233,9 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||||
federatedIdentity.setToken(response);
|
federatedIdentity.setToken(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
federatedIdentity.setCode(state);
|
|
||||||
federatedIdentity.setIdpConfig(getConfig());
|
federatedIdentity.setIdpConfig(getConfig());
|
||||||
federatedIdentity.setIdp(AbstractOAuth2IdentityProvider.this);
|
federatedIdentity.setIdp(AbstractOAuth2IdentityProvider.this);
|
||||||
|
federatedIdentity.setCode(state);
|
||||||
|
|
||||||
return callback.authenticated(federatedIdentity);
|
return callback.authenticated(federatedIdentity);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.KeyManager;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
@ -265,7 +266,8 @@ public class SAMLEndpoint {
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
||||||
.relayState(relayState);
|
.relayState(relayState);
|
||||||
if (config.isWantAuthnRequestsSigned()) {
|
if (config.isWantAuthnRequestsSigned()) {
|
||||||
binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||||
|
binding.signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
|
||||||
.signatureAlgorithm(provider.getSignatureAlgorithm())
|
.signatureAlgorithm(provider.getSignatureAlgorithm())
|
||||||
.signDocument();
|
.signDocument();
|
||||||
}
|
}
|
||||||
|
@ -291,13 +293,13 @@ public class SAMLEndpoint {
|
||||||
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
|
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AssertionType assertion = AssertionUtil.getAssertion(responseType, realm.getPrivateKey());
|
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||||
|
AssertionType assertion = AssertionUtil.getAssertion(responseType, keys.getPrivateKey());
|
||||||
SubjectType subject = assertion.getSubject();
|
SubjectType subject = assertion.getSubject();
|
||||||
SubjectType.STSubType subType = subject.getSubType();
|
SubjectType.STSubType subType = subject.getSubType();
|
||||||
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
||||||
//Map<String, String> notes = new HashMap<>();
|
//Map<String, String> notes = new HashMap<>();
|
||||||
BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue());
|
BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue());
|
||||||
identity.setCode(relayState);
|
|
||||||
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
|
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
|
||||||
identity.getContextData().put(SAML_ASSERTION, assertion);
|
identity.getContextData().put(SAML_ASSERTION, assertion);
|
||||||
|
|
||||||
|
@ -340,6 +342,7 @@ public class SAMLEndpoint {
|
||||||
if (authn != null && authn.getSessionIndex() != null) {
|
if (authn != null && authn.getSessionIndex() != null) {
|
||||||
identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
|
identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
|
||||||
}
|
}
|
||||||
|
identity.setCode(relayState);
|
||||||
|
|
||||||
|
|
||||||
return callback.authenticated(identity);
|
return callback.authenticated(identity);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
|
import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
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.AssertionType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
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.events.EventBuilder;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
|
import org.keycloak.models.KeyManager;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
@ -97,18 +99,9 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
.relayState(request.getState());
|
.relayState(request.getState());
|
||||||
|
|
||||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||||
PrivateKey privateKey = realm.getPrivateKey();
|
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||||
PublicKey publicKey = realm.getPublicKey();
|
|
||||||
|
|
||||||
if (privateKey == null) {
|
KeyPair keypair = new KeyPair(keys.getPublicKey(), keys.getPrivateKey());
|
||||||
throw new IdentityBrokerException("Identity Provider [" + getConfig().getAlias() + "] wants a signed authentication request. But the Realm [" + realm.getName() + "] does not have a private key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (publicKey == null) {
|
|
||||||
throw new IdentityBrokerException("Identity Provider [" + getConfig().getAlias() + "] wants a signed authentication request. But the Realm [" + realm.getName() + "] does not have a public key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyPair keypair = new KeyPair(publicKey, privateKey);
|
|
||||||
|
|
||||||
binding.signWith(keypair);
|
binding.signWith(keypair);
|
||||||
binding.signatureAlgorithm(getSignatureAlgorithm());
|
binding.signatureAlgorithm(getSignatureAlgorithm());
|
||||||
|
@ -155,7 +148,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
|
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
|
||||||
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
|
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
||||||
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm);
|
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm);
|
||||||
try {
|
try {
|
||||||
int status = SimpleHttp.doPost(singleLogoutServiceUrl)
|
int status = SimpleHttp.doPost(singleLogoutServiceUrl)
|
||||||
.param(GeneralConstants.SAML_REQUEST_KEY, binding.postBinding(logoutBuilder.buildDocument()).encoded())
|
.param(GeneralConstants.SAML_REQUEST_KEY, binding.postBinding(logoutBuilder.buildDocument()).encoded())
|
||||||
|
@ -181,7 +174,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
||||||
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm);
|
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm);
|
||||||
return binding.postBinding(logoutBuilder.buildDocument()).request(singleLogoutServiceUrl);
|
return binding.postBinding(logoutBuilder.buildDocument()).request(singleLogoutServiceUrl);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
@ -200,11 +193,12 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
return logoutBuilder;
|
return logoutBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JaxrsSAML2BindingBuilder buildLogoutBinding(UserSessionModel userSession, RealmModel realm) {
|
private JaxrsSAML2BindingBuilder buildLogoutBinding(KeycloakSession session, UserSessionModel userSession, RealmModel realm) {
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
||||||
.relayState(userSession.getId());
|
.relayState(userSession.getId());
|
||||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||||
binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||||
|
binding.signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
|
||||||
.signatureAlgorithm(getSignatureAlgorithm())
|
.signatureAlgorithm(getSignatureAlgorithm())
|
||||||
.signDocument();
|
.signDocument();
|
||||||
}
|
}
|
||||||
|
@ -231,7 +225,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
|
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
|
||||||
String entityId = getEntityId(uriInfo, realm);
|
String entityId = getEntityId(uriInfo, realm);
|
||||||
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
|
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
|
||||||
String certificatePem = realm.getCertificatePem();
|
String certificatePem = PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate());
|
||||||
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, certificatePem);
|
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, certificatePem);
|
||||||
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
|
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractRsaKeyProvider implements KeyProvider {
|
||||||
|
|
||||||
|
private final boolean enabled;
|
||||||
|
|
||||||
|
private final boolean active;
|
||||||
|
|
||||||
|
private final ComponentModel model;
|
||||||
|
|
||||||
|
private final Keys keys;
|
||||||
|
|
||||||
|
public AbstractRsaKeyProvider(RealmModel realm, ComponentModel model) {
|
||||||
|
this.model = model;
|
||||||
|
|
||||||
|
this.enabled = model.get(Attributes.ENABLED_KEY, true);
|
||||||
|
this.active = model.get(Attributes.ACTIVE_KEY, true);
|
||||||
|
|
||||||
|
if (model.hasNote(Keys.class.getName())) {
|
||||||
|
keys = model.getNote(Keys.class.getName());
|
||||||
|
} else {
|
||||||
|
keys = loadKeys(realm, model);
|
||||||
|
model.setNote(Keys.class.getName(), keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Keys loadKeys(RealmModel realm, ComponentModel model);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String getKid() {
|
||||||
|
return isActive() ? keys.getKid() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final PrivateKey getPrivateKey() {
|
||||||
|
return isActive() ? keys.getKeyPair().getPrivate() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final PublicKey getPublicKey(String kid) {
|
||||||
|
return isEnabled() && kid.equals(keys.getKid()) ? keys.getKeyPair().getPublic() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate getCertificate(String kid) {
|
||||||
|
return isEnabled() && kid.equals(keys.getKid()) ? keys.getCertificate() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final List<KeyMetadata> getKeyMetadata() {
|
||||||
|
String kid = keys.getKid();
|
||||||
|
PublicKey publicKey = keys.getKeyPair().getPublic();
|
||||||
|
if (kid != null && publicKey != null) {
|
||||||
|
KeyMetadata k = new KeyMetadata();
|
||||||
|
k.setProviderId(model.getId());
|
||||||
|
k.setProviderPriority(model.get(Attributes.PRIORITY_KEY, 0l));
|
||||||
|
k.setKid(kid);
|
||||||
|
if (isActive()) {
|
||||||
|
k.setStatus(KeyMetadata.Status.ACTIVE);
|
||||||
|
} else if (isEnabled()) {
|
||||||
|
k.setStatus(KeyMetadata.Status.PASSIVE);
|
||||||
|
} else {
|
||||||
|
k.setStatus(KeyMetadata.Status.DISABLED);
|
||||||
|
}
|
||||||
|
k.setType(KeyMetadata.Type.RSA);
|
||||||
|
k.setPublicKey(publicKey);
|
||||||
|
k.setCertificate(keys.getCertificate());
|
||||||
|
return Collections.singletonList(k);
|
||||||
|
} else {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEnabled() {
|
||||||
|
return keys != null && enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isActive() {
|
||||||
|
return isEnabled() && active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Keys {
|
||||||
|
private String kid;
|
||||||
|
private KeyPair keyPair;
|
||||||
|
private X509Certificate certificate;
|
||||||
|
|
||||||
|
public Keys(String kid, KeyPair keyPair, X509Certificate certificate) {
|
||||||
|
this.kid = kid;
|
||||||
|
this.keyPair = keyPair;
|
||||||
|
this.certificate = certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKid() {
|
||||||
|
return kid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyPair getKeyPair() {
|
||||||
|
return keyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate getCertificate() {
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.component.ComponentValidationException;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.provider.ConfigurationValidationHelper;
|
||||||
|
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractRsaKeyProviderFactory implements KeyProviderFactory {
|
||||||
|
|
||||||
|
public final static ProviderConfigurationBuilder configurationBuilder() {
|
||||||
|
return ProviderConfigurationBuilder.create()
|
||||||
|
.property(Attributes.PRIORITY_PROPERTY)
|
||||||
|
.property(Attributes.ENABLED_PROPERTY)
|
||||||
|
.property(Attributes.ACTIVE_PROPERTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
|
||||||
|
ConfigurationValidationHelper.check(model)
|
||||||
|
.checkLong(Attributes.PRIORITY_PROPERTY, false)
|
||||||
|
.checkBoolean(Attributes.ENABLED_PROPERTY, false)
|
||||||
|
.checkBoolean(Attributes.ACTIVE_PROPERTY, false);
|
||||||
|
}
|
||||||
|
}
|
49
services/src/main/java/org/keycloak/keys/Attributes.java
Normal file
49
services/src/main/java/org/keycloak/keys/Attributes.java
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import static org.keycloak.provider.ProviderConfigProperty.BOOLEAN_TYPE;
|
||||||
|
import static org.keycloak.provider.ProviderConfigProperty.FILE_TYPE;
|
||||||
|
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public interface Attributes {
|
||||||
|
|
||||||
|
String PRIORITY_KEY = "priority";
|
||||||
|
ProviderConfigProperty PRIORITY_PROPERTY = new ProviderConfigProperty(PRIORITY_KEY, "Priority", "Priority for the provider", STRING_TYPE, "0");
|
||||||
|
|
||||||
|
String ENABLED_KEY = "enabled";
|
||||||
|
ProviderConfigProperty ENABLED_PROPERTY = new ProviderConfigProperty(ENABLED_KEY, "Enabled", "Set if the keys are enabled", BOOLEAN_TYPE, "true");
|
||||||
|
|
||||||
|
String ACTIVE_KEY = "active";
|
||||||
|
ProviderConfigProperty ACTIVE_PROPERTY = new ProviderConfigProperty(ACTIVE_KEY, "Active", "Set if the keys can be used for signing", BOOLEAN_TYPE, "true");
|
||||||
|
|
||||||
|
String PRIVATE_KEY_KEY = "privateKey";
|
||||||
|
ProviderConfigProperty PRIVATE_KEY_PROPERTY = new ProviderConfigProperty(PRIVATE_KEY_KEY, "Private RSA Key", "Private RSA Key encoded in PEM format", FILE_TYPE, null, true);
|
||||||
|
|
||||||
|
String CERTIFICATE_KEY = "certificate";
|
||||||
|
ProviderConfigProperty CERTIFICATE_PROPERTY = new ProviderConfigProperty(CERTIFICATE_KEY, "X509 Certificate", "X509 Certificate encoded in PEM format", FILE_TYPE, null);
|
||||||
|
|
||||||
|
String KEY_SIZE_KEY = "keySize";
|
||||||
|
ProviderConfigProperty KEY_SIZE_PROPERTY = new ProviderConfigProperty(KEY_SIZE_KEY, "Keysize", "Size for the generated keys (1024, 2048 or 4096)", STRING_TYPE, null);
|
||||||
|
|
||||||
|
}
|
162
services/src/main/java/org/keycloak/keys/DefaultKeyManager.java
Normal file
162
services/src/main/java/org/keycloak/keys/DefaultKeyManager.java
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.models.KeyManager;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class DefaultKeyManager implements KeyManager {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(DefaultKeyManager.class);
|
||||||
|
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final Map<String, List<KeyProvider>> providersMap = new HashMap<>();
|
||||||
|
|
||||||
|
public DefaultKeyManager(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActiveKey getActiveKey(RealmModel realm) {
|
||||||
|
for (KeyProvider p : getProviders(realm)) {
|
||||||
|
if (p.getKid() != null && p.getPrivateKey() != null) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracev("Active key realm={0} kid={1}", realm.getName(), p.getKid());
|
||||||
|
}
|
||||||
|
String kid = p.getKid();
|
||||||
|
return new ActiveKey(kid, p.getPrivateKey(), p.getPublicKey(kid), p.getCertificate(kid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Failed to get keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PublicKey getPublicKey(RealmModel realm, String kid) {
|
||||||
|
if (kid == null) {
|
||||||
|
logger.warnv("KID is null, can't find public key", realm.getName(), kid);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (KeyProvider p : getProviders(realm)) {
|
||||||
|
PublicKey publicKey = p.getPublicKey(kid);
|
||||||
|
if (publicKey != null) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracev("Found public key realm={0} kid={1}", realm.getName(), kid);
|
||||||
|
}
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracev("Failed to find public key realm={0} kid={1}", realm.getName(), kid);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate getCertificate(RealmModel realm, String kid) {
|
||||||
|
if (kid == null) {
|
||||||
|
logger.warnv("KID is null, can't find public key", realm.getName(), kid);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (KeyProvider p : getProviders(realm)) {
|
||||||
|
Certificate certificate = p.getCertificate(kid);
|
||||||
|
if (certificate != null) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracev("Found certificate realm={0} kid={1}", realm.getName(), kid);
|
||||||
|
}
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracev("Failed to find certificate realm={0} kid={1}", realm.getName(), kid);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<KeyMetadata> getKeys(RealmModel realm, boolean includeDisabled) {
|
||||||
|
List<KeyMetadata> keys = new LinkedList<>();
|
||||||
|
for (KeyProvider p : getProviders(realm)) {
|
||||||
|
if (includeDisabled) {
|
||||||
|
keys.addAll(p.getKeyMetadata());
|
||||||
|
} else {
|
||||||
|
p.getKeyMetadata().stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<KeyProvider> getProviders(RealmModel realm) {
|
||||||
|
boolean active = false;
|
||||||
|
List<KeyProvider> providers = providersMap.get(realm.getId());
|
||||||
|
if (providers == null) {
|
||||||
|
providers = new LinkedList<>();
|
||||||
|
|
||||||
|
List<ComponentModel> components = new LinkedList<>(realm.getComponents(realm.getId(), KeyProvider.class.getName()));
|
||||||
|
components.sort(new ProviderComparator());
|
||||||
|
|
||||||
|
for (ComponentModel c : components) {
|
||||||
|
try {
|
||||||
|
ProviderFactory<KeyProvider> f = session.getKeycloakSessionFactory().getProviderFactory(KeyProvider.class, c.getProviderId());
|
||||||
|
KeyProviderFactory factory = (KeyProviderFactory) f;
|
||||||
|
KeyProvider provider = factory.create(session, c);
|
||||||
|
session.enlistForClose(provider);
|
||||||
|
providers.add(provider);
|
||||||
|
if (!active && provider.getKid() != null && provider.getPrivateKey() != null) {
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.errorv(t, "Failed to load provider {0}", c.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!active) {
|
||||||
|
providers.add(new FailsafeRsaKeyProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
providersMap.put(realm.getId(), providers);
|
||||||
|
}
|
||||||
|
return providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProviderComparator implements Comparator<ComponentModel> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(ComponentModel o1, ComponentModel o2) {
|
||||||
|
int i = Long.compare(o2.get("priority", 0l), o1.get("priority", 0l));
|
||||||
|
return i != 0 ? i : o1.getId().compareTo(o2.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.KeyUtils;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class FailsafeRsaKeyProvider implements KeyProvider {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FailsafeRsaKeyProvider.class);
|
||||||
|
|
||||||
|
private static String KID;
|
||||||
|
|
||||||
|
private static KeyPair KEY_PAIR;
|
||||||
|
|
||||||
|
private static long EXPIRES;
|
||||||
|
|
||||||
|
private KeyPair keyPair;
|
||||||
|
|
||||||
|
private String kid;
|
||||||
|
|
||||||
|
public FailsafeRsaKeyProvider() {
|
||||||
|
logger.errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
|
||||||
|
|
||||||
|
synchronized (FailsafeRsaKeyProvider.class) {
|
||||||
|
if (EXPIRES < Time.currentTime()) {
|
||||||
|
KEY_PAIR = KeyUtils.generateRsaKeyPair(2048);
|
||||||
|
KID = KeyUtils.createKeyId(KEY_PAIR.getPublic());
|
||||||
|
EXPIRES = Time.currentTime() + 60 * 10;
|
||||||
|
|
||||||
|
if (EXPIRES > 0) {
|
||||||
|
logger.warnv("Keys expired, re-generated kid={0}", KID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kid = KID;
|
||||||
|
keyPair = KEY_PAIR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKid() {
|
||||||
|
return kid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrivateKey getPrivateKey() {
|
||||||
|
return keyPair.getPrivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PublicKey getPublicKey(String kid) {
|
||||||
|
return kid.equals(this.kid) ? keyPair.getPublic() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate getCertificate(String kid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<KeyMetadata> getKeyMetadata() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.common.util.CertificateUtils;
|
||||||
|
import org.keycloak.common.util.KeyUtils;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.component.ComponentValidationException;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.provider.ConfigurationValidationHelper;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactory {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(GeneratedRsaKeyProviderFactory.class);
|
||||||
|
|
||||||
|
public static final String ID = "rsa-generated";
|
||||||
|
|
||||||
|
private static final String HELP_TEXT = "Generates RSA keys and creates a self-signed certificate";
|
||||||
|
|
||||||
|
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder()
|
||||||
|
.property(Attributes.KEY_SIZE_PROPERTY)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyProvider create(KeycloakSession session, ComponentModel model) {
|
||||||
|
return new RsaKeyProvider(session.getContext().getRealm(), model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
|
||||||
|
super.validateConfiguration(session, model);
|
||||||
|
|
||||||
|
ConfigurationValidationHelper.check(model)
|
||||||
|
.checkInt(Attributes.KEY_SIZE_PROPERTY, false);
|
||||||
|
|
||||||
|
int size;
|
||||||
|
if (!model.contains(Attributes.KEY_SIZE_KEY)) {
|
||||||
|
size = 2048;
|
||||||
|
model.put(Attributes.KEY_SIZE_KEY, size);
|
||||||
|
} else {
|
||||||
|
size = model.get(Attributes.KEY_SIZE_KEY, 2048);
|
||||||
|
if (size != 1024 && size != 2048 && size != 4096) {
|
||||||
|
throw new ComponentValidationException("Keysize should be 1024, 2048 or 4096");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(model.contains(Attributes.PRIVATE_KEY_KEY) && model.contains(Attributes.CERTIFICATE_KEY))) {
|
||||||
|
RealmModel realm = session.realms().getRealm(model.getParentId());
|
||||||
|
generateKeys(realm, model, size);
|
||||||
|
|
||||||
|
logger.debugv("Generated keys for {0}", realm.getName());
|
||||||
|
} else {
|
||||||
|
PrivateKey privateKey = PemUtils.decodePrivateKey(model.get(Attributes.PRIVATE_KEY_KEY));
|
||||||
|
int currentSize = ((RSAPrivateKey) privateKey).getModulus().bitLength();
|
||||||
|
if (currentSize != size) {
|
||||||
|
RealmModel realm = session.realms().getRealm(model.getParentId());
|
||||||
|
generateKeys(realm, model, size);
|
||||||
|
|
||||||
|
logger.debugv("Key size changed, generating new keys for {0}", realm.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateKeys(RealmModel realm, ComponentModel model, int size) {
|
||||||
|
KeyPair keyPair;
|
||||||
|
try {
|
||||||
|
keyPair = KeyUtils.generateRsaKeyPair(size);
|
||||||
|
model.put(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new ComponentValidationException("Failed to generate keys", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateCertificate(realm, model, keyPair);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateCertificate(RealmModel realm, ComponentModel model, KeyPair keyPair) {
|
||||||
|
try {
|
||||||
|
Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
|
||||||
|
model.put(Attributes.CERTIFICATE_KEY, PemUtils.encodeCertificate(certificate));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new ComponentValidationException("Failed to generate certificate", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return HELP_TEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return CONFIG_PROPERTIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.CertificateUtils;
|
||||||
|
import org.keycloak.common.util.KeyUtils;
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(JavaKeystoreKeyProvider.class);
|
||||||
|
|
||||||
|
public JavaKeystoreKeyProvider(RealmModel realm, ComponentModel model) {
|
||||||
|
super(realm, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Keys loadKeys(RealmModel realm, ComponentModel model) {
|
||||||
|
try {
|
||||||
|
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||||
|
keyStore.load(new FileInputStream(model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_KEY)), model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_PASSWORD_KEY).toCharArray());
|
||||||
|
|
||||||
|
PrivateKey privateKey = (PrivateKey) keyStore.getKey(model.get(JavaKeystoreKeyProviderFactory.KEY_ALIAS_KEY), model.get(JavaKeystoreKeyProviderFactory.KEY_PASSWORD_KEY).toCharArray());
|
||||||
|
PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
|
||||||
|
|
||||||
|
KeyPair keyPair = new KeyPair(publicKey, privateKey);
|
||||||
|
|
||||||
|
X509Certificate certificate;
|
||||||
|
if (model.contains(JavaKeystoreKeyProviderFactory.CERTIFICATE_ALIAS_KEY)) {
|
||||||
|
certificate = (X509Certificate) keyStore.getCertificate(model.get(JavaKeystoreKeyProviderFactory.CERTIFICATE_ALIAS_KEY));
|
||||||
|
} else {
|
||||||
|
certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
String kid = KeyUtils.createKeyId(keyPair.getPublic());
|
||||||
|
|
||||||
|
return new Keys(kid, keyPair, certificate);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to load keys", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.component.ComponentValidationException;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.provider.ConfigurationValidationHelper;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactory {
|
||||||
|
|
||||||
|
public static final String ID = "java-keystore";
|
||||||
|
|
||||||
|
public static String KEYSTORE_KEY = "keystore";
|
||||||
|
public static ProviderConfigProperty KEYSTORE_PROPERTY = new ProviderConfigProperty(KEYSTORE_KEY, "Keystore", "Path to keys file", STRING_TYPE, null);
|
||||||
|
|
||||||
|
public static String KEYSTORE_PASSWORD_KEY = "keystorePassword";
|
||||||
|
public static ProviderConfigProperty KEYSTORE_PASSWORD_PROPERTY = new ProviderConfigProperty(KEYSTORE_PASSWORD_KEY, "Keystore Password", "Password for the keys", STRING_TYPE, null, true);
|
||||||
|
|
||||||
|
public static String KEY_ALIAS_KEY = "keyAlias";
|
||||||
|
public static ProviderConfigProperty KEY_ALIAS_PROPERTY = new ProviderConfigProperty(KEY_ALIAS_KEY, "Private Key Alias", "Alias for the private key", STRING_TYPE, null);
|
||||||
|
|
||||||
|
public static String KEY_PASSWORD_KEY = "keyPassword";
|
||||||
|
public static ProviderConfigProperty KEY_PASSWORD_PROPERTY = new ProviderConfigProperty(KEY_PASSWORD_KEY, "Private Key password", "Password for the private key", STRING_TYPE, null, true);
|
||||||
|
|
||||||
|
public static String CERTIFICATE_ALIAS_KEY = "certificateAlias";
|
||||||
|
public static ProviderConfigProperty CERTIFICATE_ALIAS_PROPERTY = new ProviderConfigProperty(CERTIFICATE_ALIAS_KEY, "Certificate Alias", "Alias for the certificate", STRING_TYPE, null);
|
||||||
|
|
||||||
|
private static final String HELP_TEXT = "Loads keys from a Java keys file";
|
||||||
|
|
||||||
|
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder()
|
||||||
|
.property(KEYSTORE_PROPERTY)
|
||||||
|
.property(KEYSTORE_PASSWORD_PROPERTY)
|
||||||
|
.property(KEY_ALIAS_PROPERTY)
|
||||||
|
.property(KEY_PASSWORD_PROPERTY)
|
||||||
|
.property(CERTIFICATE_ALIAS_PROPERTY)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyProvider create(KeycloakSession session, ComponentModel model) {
|
||||||
|
return new JavaKeystoreKeyProvider(session.getContext().getRealm(), model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
|
||||||
|
super.validateConfiguration(session, model);
|
||||||
|
|
||||||
|
ConfigurationValidationHelper.check(model)
|
||||||
|
.checkSingle(KEYSTORE_PROPERTY, true)
|
||||||
|
.checkSingle(KEYSTORE_PASSWORD_PROPERTY, true)
|
||||||
|
.checkSingle(KEY_ALIAS_PROPERTY, true)
|
||||||
|
.checkSingle(KEY_PASSWORD_PROPERTY, true)
|
||||||
|
.checkSingle(CERTIFICATE_ALIAS_PROPERTY, false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
new JavaKeystoreKeyProvider(session.getContext().getRealm(), model)
|
||||||
|
.loadKeys(session.getContext().getRealm(), model);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new ComponentValidationException("Failed to load keys", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return HELP_TEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return CONFIG_PROPERTIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
56
services/src/main/java/org/keycloak/keys/RsaKeyProvider.java
Normal file
56
services/src/main/java/org/keycloak/keys/RsaKeyProvider.java
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.KeyUtils;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class RsaKeyProvider extends AbstractRsaKeyProvider {
|
||||||
|
|
||||||
|
public RsaKeyProvider(RealmModel realm, ComponentModel model) {
|
||||||
|
super(realm, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Keys loadKeys(RealmModel realm, ComponentModel model) {
|
||||||
|
String privateRsaKeyPem = model.getConfig().getFirst(Attributes.PRIVATE_KEY_KEY);
|
||||||
|
String certificatePem = model.getConfig().getFirst(Attributes.CERTIFICATE_KEY);
|
||||||
|
|
||||||
|
PrivateKey privateKey = PemUtils.decodePrivateKey(privateRsaKeyPem);
|
||||||
|
PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
|
||||||
|
|
||||||
|
KeyPair keyPair = new KeyPair(publicKey, privateKey);
|
||||||
|
X509Certificate certificate = PemUtils.decodeCertificate(certificatePem);
|
||||||
|
|
||||||
|
String kid = KeyUtils.createKeyId(keyPair.getPublic());
|
||||||
|
|
||||||
|
return new Keys(kid, keyPair, certificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.common.util.CertificateUtils;
|
||||||
|
import org.keycloak.common.util.KeyUtils;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.component.ComponentValidationException;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.provider.ConfigurationValidationHelper;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class RsaKeyProviderFactory extends AbstractRsaKeyProviderFactory {
|
||||||
|
|
||||||
|
public static final String ID = "rsa";
|
||||||
|
|
||||||
|
private static final String HELP_TEXT = "RSA key provider that can optionally generated a self-signed certificate";
|
||||||
|
|
||||||
|
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder()
|
||||||
|
.property(Attributes.PRIVATE_KEY_PROPERTY)
|
||||||
|
.property(Attributes.CERTIFICATE_PROPERTY)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyProvider create(KeycloakSession session, ComponentModel model) {
|
||||||
|
return new RsaKeyProvider(session.getContext().getRealm(), model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
|
||||||
|
super.validateConfiguration(session, model);
|
||||||
|
|
||||||
|
ConfigurationValidationHelper.check(model)
|
||||||
|
.checkSingle(Attributes.PRIVATE_KEY_PROPERTY, true)
|
||||||
|
.checkSingle(Attributes.CERTIFICATE_PROPERTY, false);
|
||||||
|
|
||||||
|
KeyPair keyPair;
|
||||||
|
try {
|
||||||
|
PrivateKey privateKey = PemUtils.decodePrivateKey(model.get(Attributes.PRIVATE_KEY_KEY));
|
||||||
|
PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
|
||||||
|
keyPair = new KeyPair(publicKey, privateKey);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new ComponentValidationException("Failed to decode private key", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.contains(Attributes.CERTIFICATE_KEY)) {
|
||||||
|
Certificate certificate = null;
|
||||||
|
try {
|
||||||
|
certificate = PemUtils.decodeCertificate(model.get(Attributes.CERTIFICATE_KEY));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new ComponentValidationException("Failed to decode certificate", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (certificate == null) {
|
||||||
|
throw new ComponentValidationException("Failed to decode certificate");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!certificate.getPublicKey().equals(keyPair.getPublic())) {
|
||||||
|
throw new ComponentValidationException("Certificate does not match private key");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
RealmModel realm = session.realms().getRealm(model.getParentId());
|
||||||
|
Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
|
||||||
|
model.put(Attributes.CERTIFICATE_KEY, PemUtils.encodeCertificate(certificate));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new ComponentValidationException("Failed to generate self-signed certificate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return HELP_TEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return CONFIG_PROPERTIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,9 +23,9 @@ import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
||||||
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||||
import org.keycloak.jose.jwk.JWK;
|
import org.keycloak.jose.jwk.JWK;
|
||||||
import org.keycloak.jose.jwk.JWKBuilder;
|
|
||||||
import org.keycloak.keys.PublicKeyLoader;
|
import org.keycloak.keys.PublicKeyLoader;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -69,7 +69,7 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
|
||||||
PublicKey publicKey = getSignatureValidationKey(certInfo);
|
PublicKey publicKey = getSignatureValidationKey(certInfo);
|
||||||
|
|
||||||
// Check if we have kid in DB, generate otherwise
|
// 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);
|
return Collections.singletonMap(kid, publicKey);
|
||||||
} catch (ModelException me) {
|
} catch (ModelException me) {
|
||||||
logger.warnf(me, "Unable to retrieve publicKey for verify signature of client '%s' . Error details: %s", client.getClientId(), me.getMessage());
|
logger.warnf(me, "Unable to retrieve publicKey for verify signature of client '%s' . Error details: %s", client.getClientId(), me.getMessage());
|
||||||
|
|
|
@ -22,10 +22,10 @@ import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
|
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
|
||||||
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||||
import org.keycloak.jose.jwk.JWK;
|
import org.keycloak.jose.jwk.JWK;
|
||||||
import org.keycloak.jose.jwk.JWKBuilder;
|
|
||||||
import org.keycloak.keys.PublicKeyLoader;
|
import org.keycloak.keys.PublicKeyLoader;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.protocol.oidc.utils.JWKSHttpUtils;
|
import org.keycloak.protocol.oidc.utils.JWKSHttpUtils;
|
||||||
|
@ -60,7 +60,7 @@ public class OIDCIdentityProviderPublicKeyLoader implements PublicKeyLoader {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
String kid = JWKBuilder.createKeyId(publicKey);
|
String kid = KeyUtils.createKeyId(publicKey);
|
||||||
return Collections.singletonMap(kid, publicKey);
|
return Collections.singletonMap(kid, publicKey);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warnf(e, "Unable to retrieve publicKey for verify signature of identityProvider '%s' . Error details: %s", config.getAlias(), e.getMessage());
|
logger.warnf(e, "Unable to retrieve publicKey for verify signature of identityProvider '%s' . Error details: %s", config.getAlias(), e.getMessage());
|
||||||
|
|
|
@ -118,7 +118,7 @@ public abstract class AuthorizationEndpointBase {
|
||||||
return processor.finishAuthentication(protocol);
|
return processor.finishAuthentication(protocol);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
|
RestartLoginCookie.setRestartCookie(session, realm, clientConnection, uriInfo, clientSession);
|
||||||
if (redirectToAuthentication) {
|
if (redirectToAuthentication) {
|
||||||
return processor.redirectToFlow();
|
return processor.redirectToFlow();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,10 @@ import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
import org.keycloak.jose.jws.JWSBuilder;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.crypto.HMACProvider;
|
import org.keycloak.jose.jws.crypto.HMACProvider;
|
||||||
|
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.KeyManager;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
|
@ -33,6 +35,7 @@ import org.keycloak.services.util.CookieHelper;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.ws.rs.core.Cookie;
|
import javax.ws.rs.core.Cookie;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.security.PublicKey;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -112,11 +115,12 @@ public class RestartLoginCookie {
|
||||||
this.action = action;
|
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();
|
JWSBuilder builder = new JWSBuilder();
|
||||||
return builder.jsonContent(this)
|
return builder.kid(keys.getKid()).jsonContent(this)
|
||||||
.hmac256((SecretKey)realm.getCodeSecretKey());
|
.rsa256(keys.getPrivateKey());
|
||||||
//.rsa256(realm.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);
|
RestartLoginCookie restart = new RestartLoginCookie(clientSession);
|
||||||
String encoded = restart.encode(realm);
|
String encoded = restart.encode(session, realm);
|
||||||
int keySize = realm.getCodeSecret().length();
|
|
||||||
int size = encoded.length();
|
|
||||||
String path = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
|
String path = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
|
||||||
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
||||||
CookieHelper.addCookie(KC_RESTART, encoded, path, null, null, -1, secureOnly, true);
|
CookieHelper.addCookie(KC_RESTART, encoded, path, null, null, -1, secureOnly, true);
|
||||||
|
@ -157,13 +159,8 @@ public class RestartLoginCookie {
|
||||||
}
|
}
|
||||||
String encodedCookie = cook.getValue();
|
String encodedCookie = cook.getValue();
|
||||||
JWSInput input = new JWSInput(encodedCookie);
|
JWSInput input = new JWSInput(encodedCookie);
|
||||||
/*
|
PublicKey publicKey = session.keys().getPublicKey(realm, input.getHeader().getKeyId());
|
||||||
if (!RSAProvider.verify(input, realm.getPublicKey())) {
|
if (!RSAProvider.verify(input, publicKey)) {
|
||||||
logger.debug("Failed to verify encoded RestartLoginCookie");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if (!HMACProvider.verify(input, (SecretKey)realm.getCodeSecretKey())) {
|
|
||||||
logger.debug("Failed to verify encoded RestartLoginCookie");
|
logger.debug("Failed to verify encoded RestartLoginCookie");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,12 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
|
import org.keycloak.services.Urls;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
@ -46,13 +48,33 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
|
||||||
|
|
||||||
public Response introspect(String token) {
|
public Response introspect(String token) {
|
||||||
try {
|
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();
|
RealmModel realm = this.session.getContext().getRealm();
|
||||||
ObjectNode tokenMetadata;
|
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 = JsonSerialization.createObjectNode(toIntrospect);
|
||||||
tokenMetadata.put("client_id", toIntrospect.getIssuedFor());
|
tokenMetadata.put("client_id", toIntrospect.getIssuedFor());
|
||||||
tokenMetadata.put("username", toIntrospect.getPreferredUsername());
|
tokenMetadata.put("username", toIntrospect.getPreferredUsername());
|
||||||
|
@ -60,7 +82,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
|
||||||
tokenMetadata = JsonSerialization.createObjectNode();
|
tokenMetadata = JsonSerialization.createObjectNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenMetadata.put("active", active);
|
tokenMetadata.put("active", valid);
|
||||||
|
|
||||||
return Response.ok(JsonSerialization.writeValueAsBytes(tokenMetadata)).type(MediaType.APPLICATION_JSON_TYPE).build();
|
return Response.ok(JsonSerialization.writeValueAsBytes(tokenMetadata)).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -70,7 +92,13 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
|
||||||
|
|
||||||
protected AccessToken toAccessToken(String token) {
|
protected AccessToken toAccessToken(String token) {
|
||||||
try {
|
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) {
|
} catch (VerificationException e) {
|
||||||
throw new ErrorResponseException("invalid_request", "Invalid token.", Response.Status.UNAUTHORIZED);
|
throw new ErrorResponseException("invalid_request", "Invalid token.", Response.Status.UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,10 @@ package org.keycloak.protocol.oidc;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.events.EventBuilder;
|
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.JWK;
|
||||||
import org.keycloak.jose.jwk.JWKBuilder;
|
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.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint;
|
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.LogoutEndpoint;
|
||||||
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
|
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
|
||||||
import org.keycloak.protocol.oidc.endpoints.UserInfoEndpoint;
|
import org.keycloak.protocol.oidc.endpoints.UserInfoEndpoint;
|
||||||
|
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
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.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resource class for the oauth/openid connect token service
|
* Resource class for the oauth/openid connect token service
|
||||||
|
@ -174,8 +176,16 @@ public class OIDCLoginProtocolService {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@NoCache
|
@NoCache
|
||||||
public JSONWebKeySet certs() {
|
public JSONWebKeySet certs() {
|
||||||
|
List<KeyMetadata> publicKeys = session.keys().getKeys(realm, false);
|
||||||
|
JWK[] keys = new JWK[publicKeys.size()];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (KeyMetadata k : publicKeys) {
|
||||||
|
keys[i++] = JWKBuilder.create().kid(k.getKid()).rs256(k.getPublicKey());
|
||||||
|
}
|
||||||
|
|
||||||
JSONWebKeySet keySet = new JSONWebKeySet();
|
JSONWebKeySet keySet = new JSONWebKeySet();
|
||||||
keySet.setKeys(new JWK[]{JWKBuilder.create().rs256(realm.getPublicKey())});
|
keySet.setKeys(keys);
|
||||||
return keySet;
|
return keySet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
|
|
||||||
package org.keycloak.protocol.oidc;
|
package org.keycloak.protocol.oidc;
|
||||||
|
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.OAuthErrorException;
|
|
||||||
import org.keycloak.cluster.ClusterProvider;
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
import org.keycloak.common.ClientConnection;
|
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.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
|
@ -35,6 +35,7 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.ClientTemplateModel;
|
import org.keycloak.models.ClientTemplateModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.KeyManager;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
|
@ -55,11 +56,11 @@ import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
import org.keycloak.representations.RefreshToken;
|
import org.keycloak.representations.RefreshToken;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.ServicesLogger;
|
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
import org.keycloak.services.managers.UserSessionManager;
|
import org.keycloak.services.managers.UserSessionManager;
|
||||||
import org.keycloak.util.TokenUtil;
|
import org.keycloak.util.TokenUtil;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -80,7 +81,8 @@ import java.util.Set;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class TokenManager {
|
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
|
// Harcoded for now
|
||||||
Algorithm jwsAlgorithm = Algorithm.RS256;
|
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 {
|
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())
|
event.user(refreshToken.getSubject()).session(refreshToken.getSessionState())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
.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()));
|
return new RefreshResult(res, TokenUtil.TOKEN_TYPE_OFFLINE.equals(refreshToken.getType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken) throws OAuthErrorException {
|
public RefreshToken verifyRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken) throws OAuthErrorException {
|
||||||
return verifyRefreshToken(realm, encodedRefreshToken, true);
|
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 {
|
try {
|
||||||
RefreshToken refreshToken = toRefreshToken(realm, encodedRefreshToken);
|
RefreshToken refreshToken = toRefreshToken(session, realm, encodedRefreshToken);
|
||||||
|
|
||||||
if (checkExpiration) {
|
if (checkExpiration) {
|
||||||
if (refreshToken.getExpiration() != 0 && refreshToken.isExpired()) {
|
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);
|
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");
|
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
|
||||||
}
|
}
|
||||||
|
|
||||||
return jws.readJsonContent(RefreshToken.class);
|
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 {
|
try {
|
||||||
JWSInput jws = new JWSInput(encodedIDToken);
|
JWSInput jws = new JWSInput(encodedIDToken);
|
||||||
IDToken idToken;
|
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");
|
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken");
|
||||||
}
|
}
|
||||||
idToken = jws.readJsonContent(IDToken.class);
|
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,
|
public AccessToken transformAccessToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user,
|
||||||
UserSessionModel userSession, ClientSessionModel clientSession) {
|
UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
Set<ProtocolMapperModel> mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers();
|
Set<ProtocolMapperModel> mappings = new ClientSessionCode(session, realm, clientSession).getRequestedProtocolMappers();
|
||||||
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||||
for (ProtocolMapperModel mapping : mappings) {
|
for (ProtocolMapperModel mapping : mappings) {
|
||||||
|
|
||||||
|
@ -513,7 +515,7 @@ public class TokenManager {
|
||||||
|
|
||||||
public AccessToken transformUserInfoAccessToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user,
|
public AccessToken transformUserInfoAccessToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user,
|
||||||
UserSessionModel userSession, ClientSessionModel clientSession) {
|
UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
Set<ProtocolMapperModel> mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers();
|
Set<ProtocolMapperModel> mappings = new ClientSessionCode(session, realm, clientSession).getRequestedProtocolMappers();
|
||||||
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||||
for (ProtocolMapperModel mapping : mappings) {
|
for (ProtocolMapperModel mapping : mappings) {
|
||||||
|
|
||||||
|
@ -533,7 +535,7 @@ public class TokenManager {
|
||||||
|
|
||||||
public void transformIDToken(KeycloakSession session, IDToken token, RealmModel realm, ClientModel client, UserModel user,
|
public void transformIDToken(KeycloakSession session, IDToken token, RealmModel realm, ClientModel client, UserModel user,
|
||||||
UserSessionModel userSession, ClientSessionModel clientSession) {
|
UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
Set<ProtocolMapperModel> mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers();
|
Set<ProtocolMapperModel> mappings = new ClientSessionCode(session, realm, clientSession).getRequestedProtocolMappers();
|
||||||
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||||
for (ProtocolMapperModel mapping : mappings) {
|
for (ProtocolMapperModel mapping : mappings) {
|
||||||
|
|
||||||
|
@ -618,13 +620,9 @@ public class TokenManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encodeToken(RealmModel realm, Object token) {
|
public String encodeToken(KeycloakSession session, RealmModel realm, Object token) {
|
||||||
String encodedToken = new JWSBuilder()
|
KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
|
||||||
.type(OAuth2Constants.JWT)
|
return new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(token).sign(jwsAlgorithm, activeKey.getPrivateKey());
|
||||||
.kid(realm.getKeyId())
|
|
||||||
.jsonContent(token)
|
|
||||||
.sign(jwsAlgorithm, realm.getPrivateKey());
|
|
||||||
return encodedToken;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client, EventBuilder event, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
|
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() {
|
public AccessTokenResponse build() {
|
||||||
|
KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
|
||||||
|
|
||||||
if (accessToken != null) {
|
if (accessToken != null) {
|
||||||
event.detail(Details.TOKEN_ID, accessToken.getId());
|
event.detail(Details.TOKEN_ID, accessToken.getId());
|
||||||
}
|
}
|
||||||
|
@ -746,7 +746,7 @@ public class TokenManager {
|
||||||
|
|
||||||
AccessTokenResponse res = new AccessTokenResponse();
|
AccessTokenResponse res = new AccessTokenResponse();
|
||||||
if (accessToken != null) {
|
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.setToken(encodedToken);
|
||||||
res.setTokenType("bearer");
|
res.setTokenType("bearer");
|
||||||
res.setSessionState(accessToken.getSessionState());
|
res.setSessionState(accessToken.getSessionState());
|
||||||
|
@ -764,11 +764,11 @@ public class TokenManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idToken != null) {
|
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);
|
res.setIdToken(encodedToken);
|
||||||
}
|
}
|
||||||
if (refreshToken != null) {
|
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);
|
res.setRefreshToken(encodedToken);
|
||||||
if (refreshToken.getExpiration() != 0) {
|
if (refreshToken.getExpiration() != 0) {
|
||||||
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());
|
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());
|
||||||
|
|
|
@ -116,7 +116,7 @@ public class LogoutEndpoint {
|
||||||
boolean error = false;
|
boolean error = false;
|
||||||
if (encodedIdToken != null) {
|
if (encodedIdToken != null) {
|
||||||
try {
|
try {
|
||||||
IDToken idToken = tokenManager.verifyIDToken(realm, encodedIdToken);
|
IDToken idToken = tokenManager.verifyIDToken(session, realm, encodedIdToken);
|
||||||
userSession = session.sessions().getUserSession(realm, idToken.getSessionState());
|
userSession = session.sessions().getUserSession(realm, idToken.getSessionState());
|
||||||
if (userSession == null) {
|
if (userSession == null) {
|
||||||
error = true;
|
error = true;
|
||||||
|
@ -187,7 +187,7 @@ public class LogoutEndpoint {
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
RefreshToken token = tokenManager.verifyRefreshToken(realm, refreshToken, false);
|
RefreshToken token = tokenManager.verifyRefreshToken(session, realm, refreshToken, false);
|
||||||
UserSessionModel userSessionModel = session.sessions().getUserSession(realm, token.getSessionState());
|
UserSessionModel userSessionModel = session.sessions().getUserSession(realm, token.getSessionState());
|
||||||
if (userSessionModel != null) {
|
if (userSessionModel != null) {
|
||||||
logout(userSessionModel);
|
logout(userSessionModel);
|
||||||
|
|
|
@ -68,9 +68,6 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class TokenEndpoint {
|
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 static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
|
||||||
private MultivaluedMap<String, String> formParams;
|
private MultivaluedMap<String, String> formParams;
|
||||||
private ClientModel client;
|
private ClientModel client;
|
||||||
|
@ -203,34 +200,29 @@ public class TokenEndpoint {
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
|
ClientSessionCode.ParseResult parseResult = ClientSessionCode.parseResult(code, session, realm);
|
||||||
if (accessCode == null) {
|
if (parseResult.isClientSessionNotFound() || parseResult.isIllegalHash()) {
|
||||||
String[] parts = code.split("\\.");
|
String[] parts = code.split("\\.");
|
||||||
if (parts.length == 2) {
|
if (parts.length == 2) {
|
||||||
event.detail(Details.CODE_ID, parts[1]);
|
event.detail(Details.CODE_ID, parts[1]);
|
||||||
}
|
}
|
||||||
event.error(Errors.INVALID_CODE);
|
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());
|
event.detail(Details.CODE_ID, clientSession.getId());
|
||||||
|
|
||||||
String codeExchanged = clientSession.getNote(CODE_EXCHANGED);
|
if (!parseResult.getCode().isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) {
|
||||||
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)) {
|
|
||||||
event.error(Errors.INVALID_CODE);
|
event.error(Errors.INVALID_CODE);
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
accessCode.setAction(null);
|
parseResult.getCode().setAction(null);
|
||||||
clientSession.setNote(CODE_EXCHANGED, "true");
|
|
||||||
UserSessionModel userSession = clientSession.getUserSession();
|
UserSessionModel userSession = clientSession.getUserSession();
|
||||||
|
|
||||||
if (userSession == null) {
|
if (userSession == null) {
|
||||||
|
@ -275,7 +267,7 @@ public class TokenEndpoint {
|
||||||
updateClientSession(clientSession);
|
updateClientSession(clientSession);
|
||||||
updateUserSessionFromClientAuth(userSession);
|
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)
|
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
|
||||||
.accessToken(token)
|
.accessToken(token)
|
||||||
|
|
|
@ -129,7 +129,11 @@ public class UserInfoEndpoint {
|
||||||
|
|
||||||
AccessToken token = null;
|
AccessToken token = null;
|
||||||
try {
|
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) {
|
} catch (VerificationException e) {
|
||||||
event.error(Errors.INVALID_TOKEN);
|
event.error(Errors.INVALID_TOKEN);
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Token invalid: " + e.getMessage(), Response.Status.UNAUTHORIZED);
|
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Token invalid: " + e.getMessage(), Response.Status.UNAUTHORIZED);
|
||||||
|
@ -190,7 +194,7 @@ public class UserInfoEndpoint {
|
||||||
claims.put("aud", audience);
|
claims.put("aud", audience);
|
||||||
|
|
||||||
Algorithm signatureAlg = cfg.getUserInfoSignedResponseAlg();
|
Algorithm signatureAlg = cfg.getUserInfoSignedResponseAlg();
|
||||||
PrivateKey privateKey = realm.getPrivateKey();
|
PrivateKey privateKey = session.keys().getActiveKey(realm).getPrivateKey();
|
||||||
|
|
||||||
String signedUserInfo = new JWSBuilder()
|
String signedUserInfo = new JWSBuilder()
|
||||||
.jsonContent(claims)
|
.jsonContent(claims)
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.KeyManager;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -393,19 +394,22 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder();
|
JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder();
|
||||||
bindingBuilder.relayState(relayState);
|
bindingBuilder.relayState(relayState);
|
||||||
|
|
||||||
|
KeyManager keyManager = session.keys();
|
||||||
|
KeyManager.ActiveKey keys = keyManager.getActiveKey(realm);
|
||||||
|
|
||||||
if (samlClient.requiresRealmSignature()) {
|
if (samlClient.requiresRealmSignature()) {
|
||||||
String canonicalization = samlClient.getCanonicalizationMethod();
|
String canonicalization = samlClient.getCanonicalizationMethod();
|
||||||
if (canonicalization != null) {
|
if (canonicalization != null) {
|
||||||
bindingBuilder.canonicalizationMethod(canonicalization);
|
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()) {
|
if (samlClient.requiresAssertionSignature()) {
|
||||||
String canonicalization = samlClient.getCanonicalizationMethod();
|
String canonicalization = samlClient.getCanonicalizationMethod();
|
||||||
if (canonicalization != null) {
|
if (canonicalization != null) {
|
||||||
bindingBuilder.canonicalizationMethod(canonicalization);
|
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()) {
|
if (samlClient.requiresEncryption()) {
|
||||||
PublicKey publicKey = null;
|
PublicKey publicKey = null;
|
||||||
|
@ -536,7 +540,8 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
if (canonicalization != null) {
|
if (canonicalization != null) {
|
||||||
binding.canonicalizationMethod(canonicalization);
|
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 {
|
try {
|
||||||
|
@ -633,7 +638,8 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) {
|
private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) {
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
|
||||||
if (samlClient.requiresRealmSignature()) {
|
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;
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.common.util.StreamUtil;
|
import org.keycloak.common.util.StreamUtil;
|
||||||
import org.keycloak.dom.saml.v2.SAML2Object;
|
import org.keycloak.dom.saml.v2.SAML2Object;
|
||||||
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
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.events.EventType;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.KeyManager;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.AuthorizationEndpointBase;
|
import org.keycloak.protocol.AuthorizationEndpointBase;
|
||||||
|
@ -59,6 +62,7 @@ import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
@ -77,6 +81,9 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
|
|
||||||
protected static final Logger logger = Logger.getLogger(SamlService.class);
|
protected static final Logger logger = Logger.getLogger(SamlService.class);
|
||||||
|
|
||||||
|
@Context
|
||||||
|
protected KeycloakSession session;
|
||||||
|
|
||||||
public SamlService(RealmModel realm, EventBuilder event) {
|
public SamlService(RealmModel realm, EventBuilder event) {
|
||||||
super(realm, event);
|
super(realm, event);
|
||||||
}
|
}
|
||||||
|
@ -374,7 +381,8 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState);
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState);
|
||||||
if (samlClient.requiresRealmSignature()) {
|
if (samlClient.requiresRealmSignature()) {
|
||||||
SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm();
|
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 {
|
try {
|
||||||
|
@ -508,18 +516,18 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
@Produces(MediaType.APPLICATION_XML)
|
@Produces(MediaType.APPLICATION_XML)
|
||||||
@NoCache
|
@NoCache
|
||||||
public String getDescriptor() throws Exception {
|
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");
|
InputStream is = SamlService.class.getResourceAsStream("/idp-metadata-template.xml");
|
||||||
String template = StreamUtil.readString(is);
|
String template = StreamUtil.readString(is);
|
||||||
template = template.replace("${idp.entityID}", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
|
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-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.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.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;
|
return template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.protocol.saml.installation;
|
package org.keycloak.protocol.saml.installation;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
@ -43,12 +44,12 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
|
||||||
SamlClient samlClient = new SamlClient(client);
|
SamlClient samlClient = new SamlClient(client);
|
||||||
StringBuffer buffer = new StringBuffer();
|
StringBuffer buffer = new StringBuffer();
|
||||||
buffer.append("<keycloak-saml-adapter>\n");
|
buffer.append("<keycloak-saml-adapter>\n");
|
||||||
baseXml(realm, client, baseUri, samlClient, buffer);
|
baseXml(session, realm, client, baseUri, samlClient, buffer);
|
||||||
buffer.append("</keycloak-saml-adapter>\n");
|
buffer.append("</keycloak-saml-adapter>\n");
|
||||||
return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
|
return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void baseXml(RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuffer buffer) {
|
public static void baseXml(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuffer buffer) {
|
||||||
buffer.append(" <SP entityID=\"").append(client.getClientId()).append("\"\n");
|
buffer.append(" <SP entityID=\"").append(client.getClientId()).append("\"\n");
|
||||||
buffer.append(" sslPolicy=\"").append(realm.getSslRequired().name()).append("\"\n");
|
buffer.append(" sslPolicy=\"").append(realm.getSslRequired().name()).append("\"\n");
|
||||||
buffer.append(" logoutPage=\"SPECIFY YOUR LOGOUT PAGE!\">\n");
|
buffer.append(" logoutPage=\"SPECIFY YOUR LOGOUT PAGE!\">\n");
|
||||||
|
@ -116,7 +117,7 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
|
||||||
buffer.append(" <Keys>\n");
|
buffer.append(" <Keys>\n");
|
||||||
buffer.append(" <Key signing=\"true\">\n");
|
buffer.append(" <Key signing=\"true\">\n");
|
||||||
buffer.append(" <CertificatePem>\n");
|
buffer.append(" <CertificatePem>\n");
|
||||||
buffer.append(" ").append(realm.getCertificatePem()).append("\n");
|
buffer.append(" ").append(PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate())).append("\n");
|
||||||
buffer.append(" </CertificatePem>\n");
|
buffer.append(" </CertificatePem>\n");
|
||||||
buffer.append(" </Key>\n");
|
buffer.append(" </Key>\n");
|
||||||
buffer.append(" </Keys>\n");
|
buffer.append(" </Keys>\n");
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class KeycloakSamlSubsystemInstallation implements ClientInstallationProv
|
||||||
SamlClient samlClient = new SamlClient(client);
|
SamlClient samlClient = new SamlClient(client);
|
||||||
StringBuffer buffer = new StringBuffer();
|
StringBuffer buffer = new StringBuffer();
|
||||||
buffer.append("<secure-deployment name=\"YOUR-WAR.war\">\n");
|
buffer.append("<secure-deployment name=\"YOUR-WAR.war\">\n");
|
||||||
KeycloakSamlClientInstallation.baseXml(realm, client, baseUri, samlClient, buffer);
|
KeycloakSamlClientInstallation.baseXml(session, realm, client, baseUri, samlClient, buffer);
|
||||||
buffer.append("</secure-deployment>\n");
|
buffer.append("</secure-deployment>\n");
|
||||||
return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
|
return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class ModAuthMellonClientInstallation implements ClientInstallationProvid
|
||||||
SamlClient samlClient = new SamlClient(client);
|
SamlClient samlClient = new SamlClient(client);
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
ZipOutputStream zip = new ZipOutputStream(baos);
|
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 spDescriptor = SamlSPDescriptorClientInstallation.getSPDescriptorForClient(client);
|
||||||
String clientDirName = client.getClientId()
|
String clientDirName = client.getClientId()
|
||||||
.replace('/', '_')
|
.replace('/', '_')
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.protocol.saml.installation;
|
package org.keycloak.protocol.saml.installation;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
@ -37,7 +38,7 @@ import java.net.URI;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class SamlIDPDescriptorClientInstallation implements ClientInstallationProvider {
|
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);
|
SamlClient samlClient = new SamlClient(client);
|
||||||
String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString();
|
String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString();
|
||||||
String idp = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
String idp = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
||||||
|
@ -75,7 +76,7 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
|
||||||
" <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
|
" <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
|
||||||
" <dsig:X509Data>\n" +
|
" <dsig:X509Data>\n" +
|
||||||
" <dsig:X509Certificate>\n" +
|
" <dsig:X509Certificate>\n" +
|
||||||
" " + realm.getCertificatePem() + "\n" +
|
" " + PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()) + "\n" +
|
||||||
" </dsig:X509Certificate>\n" +
|
" </dsig:X509Certificate>\n" +
|
||||||
" </dsig:X509Data>\n" +
|
" </dsig:X509Data>\n" +
|
||||||
" </dsig:KeyInfo>\n" +
|
" </dsig:KeyInfo>\n" +
|
||||||
|
@ -87,7 +88,7 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
|
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();
|
return Response.ok(descriptor, MediaType.TEXT_PLAIN_TYPE).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,7 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
|
||||||
List<SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper>> roleNameMappers = new LinkedList<>();
|
List<SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper>> roleNameMappers = new LinkedList<>();
|
||||||
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||||
AttributeType singleAttributeType = null;
|
AttributeType singleAttributeType = null;
|
||||||
Set<ProtocolMapperModel> requestedProtocolMappers = new ClientSessionCode(clientSession.getRealm(), clientSession).getRequestedProtocolMappers();
|
Set<ProtocolMapperModel> requestedProtocolMappers = new ClientSessionCode(session, clientSession.getRealm(), clientSession).getRequestedProtocolMappers();
|
||||||
for (ProtocolMapperModel mapping : requestedProtocolMappers) {
|
for (ProtocolMapperModel mapping : requestedProtocolMappers) {
|
||||||
|
|
||||||
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
|
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
|
||||||
|
|
|
@ -17,10 +17,12 @@
|
||||||
package org.keycloak.services;
|
package org.keycloak.services;
|
||||||
|
|
||||||
import org.keycloak.credential.UserCredentialStoreManager;
|
import org.keycloak.credential.UserCredentialStoreManager;
|
||||||
|
import org.keycloak.keys.DefaultKeyManager;
|
||||||
import org.keycloak.models.KeycloakContext;
|
import org.keycloak.models.KeycloakContext;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.KeycloakTransactionManager;
|
import org.keycloak.models.KeycloakTransactionManager;
|
||||||
|
import org.keycloak.models.KeyManager;
|
||||||
import org.keycloak.models.RealmProvider;
|
import org.keycloak.models.RealmProvider;
|
||||||
import org.keycloak.models.UserCredentialManager;
|
import org.keycloak.models.UserCredentialManager;
|
||||||
import org.keycloak.models.UserFederationManager;
|
import org.keycloak.models.UserFederationManager;
|
||||||
|
@ -60,6 +62,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
private UserFederationManager federationManager;
|
private UserFederationManager federationManager;
|
||||||
private UserFederatedStorageProvider userFederatedStorageProvider;
|
private UserFederatedStorageProvider userFederatedStorageProvider;
|
||||||
private KeycloakContext context;
|
private KeycloakContext context;
|
||||||
|
private KeyManager keyManager;
|
||||||
|
|
||||||
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
|
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
|
@ -221,6 +224,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
return sessionProvider;
|
return sessionProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyManager keys() {
|
||||||
|
if (keyManager == null) {
|
||||||
|
keyManager = new DefaultKeyManager(this);
|
||||||
|
}
|
||||||
|
return keyManager;
|
||||||
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
for (Provider p : providers.values()) {
|
for (Provider p : providers.values()) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -79,7 +79,7 @@ public class ClientRegistrationAuth {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientRegistrationTokenUtils.TokenVerification tokenVerification = ClientRegistrationTokenUtils.verifyToken(realm, uri, split[1]);
|
ClientRegistrationTokenUtils.TokenVerification tokenVerification = ClientRegistrationTokenUtils.verifyToken(session, realm, uri, split[1]);
|
||||||
if (tokenVerification.getError() != null) {
|
if (tokenVerification.getError() != null) {
|
||||||
throw unauthorized(tokenVerification.getError().getMessage());
|
throw unauthorized(tokenVerification.getError().getMessage());
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.jose.jws.JWSInputException;
|
||||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||||
import org.keycloak.models.ClientInitialAccessModel;
|
import org.keycloak.models.ClientInitialAccessModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeyManager;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
@ -32,6 +33,7 @@ import org.keycloak.services.Urls;
|
||||||
import org.keycloak.util.TokenUtil;
|
import org.keycloak.util.TokenUtil;
|
||||||
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -42,21 +44,21 @@ public class ClientRegistrationTokenUtils {
|
||||||
public static final String TYPE_REGISTRATION_ACCESS_TOKEN = "RegistrationAccessToken";
|
public static final String TYPE_REGISTRATION_ACCESS_TOKEN = "RegistrationAccessToken";
|
||||||
|
|
||||||
public static String updateRegistrationAccessToken(KeycloakSession session, ClientModel client) {
|
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();
|
String id = KeycloakModelUtils.generateId();
|
||||||
client.setRegistrationToken(id);
|
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;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String createInitialAccessToken(RealmModel realm, UriInfo uri, ClientInitialAccessModel model) {
|
public static String createInitialAccessToken(KeycloakSession session, RealmModel realm, UriInfo uri, ClientInitialAccessModel model) {
|
||||||
return createToken(realm, uri, model.getId(), TYPE_INITIAL_ACCESS_TOKEN, model.getExpiration() > 0 ? model.getTimestamp() + model.getExpiration() : 0);
|
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) {
|
if (token == null) {
|
||||||
return TokenVerification.error(new RuntimeException("Missing token"));
|
return TokenVerification.error(new RuntimeException("Missing token"));
|
||||||
}
|
}
|
||||||
|
@ -68,7 +70,9 @@ public class ClientRegistrationTokenUtils {
|
||||||
return TokenVerification.error(new RuntimeException("Invalid token", e));
|
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"));
|
return TokenVerification.error(new RuntimeException("Failed verify token"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +100,7 @@ public class ClientRegistrationTokenUtils {
|
||||||
return TokenVerification.success(jwt);
|
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();
|
JsonWebToken jwt = new JsonWebToken();
|
||||||
|
|
||||||
String issuer = getIssuer(realm, uri);
|
String issuer = getIssuer(realm, uri);
|
||||||
|
@ -108,7 +112,9 @@ public class ClientRegistrationTokenUtils {
|
||||||
jwt.issuer(issuer);
|
jwt.issuer(issuer);
|
||||||
jwt.audience(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;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.DefaultKeyProviders;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
|
@ -83,7 +84,9 @@ public class ApplianceBootstrap {
|
||||||
realm.setSslRequired(SslRequired.EXTERNAL);
|
realm.setSslRequired(SslRequired.EXTERNAL);
|
||||||
realm.setRegistrationAllowed(false);
|
realm.setRegistrationAllowed(false);
|
||||||
realm.setRegistrationEmailAsUsername(false);
|
realm.setRegistrationEmailAsUsername(false);
|
||||||
KeycloakModelUtils.generateRealmKeys(realm);
|
|
||||||
|
session.getContext().setRealm(realm);
|
||||||
|
DefaultKeyProviders.createProviders(realm);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services.managers;
|
package org.keycloak.services.managers;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
|
@ -35,6 +36,7 @@ import org.keycloak.forms.login.LoginFormsProvider;
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
import org.keycloak.jose.jws.JWSBuilder;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.KeyManager;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
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.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.security.PublicKey;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -81,7 +84,8 @@ public class AuthenticationManager {
|
||||||
// clientSession note with flag that clientSession was authenticated through SSO cookie
|
// clientSession note with flag that clientSession was authenticated through SSO cookie
|
||||||
public static final String SSO_AUTH = "SSO_AUTH";
|
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";
|
public static final String FORM_USERNAME = "username";
|
||||||
// used for auth login
|
// used for auth login
|
||||||
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
|
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
|
||||||
|
@ -107,7 +111,13 @@ public class AuthenticationManager {
|
||||||
Cookie cookie = headers.getCookies().get(KEYCLOAK_IDENTITY_COOKIE);
|
Cookie cookie = headers.getCookies().get(KEYCLOAK_IDENTITY_COOKIE);
|
||||||
if (cookie == null) return;
|
if (cookie == null) return;
|
||||||
String tokenString = cookie.getValue();
|
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());
|
UserSessionModel cookieSession = session.sessions().getUserSession(realm, token.getSessionState());
|
||||||
if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return;
|
if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return;
|
||||||
expireIdentityCookie(realm, uriInfo, connection);
|
expireIdentityCookie(realm, uriInfo, connection);
|
||||||
|
@ -214,7 +224,7 @@ public class AuthenticationManager {
|
||||||
protocol.backchannelLogout(userSession, clientSession);
|
protocol.backchannelLogout(userSession, clientSession);
|
||||||
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
|
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.failedToLogoutClient(e);
|
ServicesLogger.ROOT_LOGGER.failedToLogoutClient(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,7 +245,7 @@ public class AuthenticationManager {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.failedToLogoutClient(e);
|
ServicesLogger.ROOT_LOGGER.failedToLogoutClient(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -284,7 +294,7 @@ public class AuthenticationManager {
|
||||||
String cookiePath = getIdentityCookiePath(realm, uriInfo);
|
String cookiePath = getIdentityCookiePath(realm, uriInfo);
|
||||||
String issuer = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName());
|
String issuer = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName());
|
||||||
AccessToken identityToken = createIdentityToken(realm, user, session, issuer);
|
AccessToken identityToken = createIdentityToken(realm, user, session, issuer);
|
||||||
String encoded = encodeToken(realm, identityToken);
|
String encoded = encodeToken(keycloakSession, realm, identityToken);
|
||||||
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
||||||
int maxAge = NewCookie.DEFAULT_MAX_AGE;
|
int maxAge = NewCookie.DEFAULT_MAX_AGE;
|
||||||
if (session.isRememberMe()) {
|
if (session.isRememberMe()) {
|
||||||
|
@ -326,10 +336,15 @@ public class AuthenticationManager {
|
||||||
return null;
|
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()
|
String encodedToken = new JWSBuilder()
|
||||||
|
.kid(activeKey.getKid())
|
||||||
.jsonContent(token)
|
.jsonContent(token)
|
||||||
.rsa256(realm.getPrivateKey());
|
.rsa256(activeKey.getPrivateKey());
|
||||||
return encodedToken;
|
return encodedToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,7 +445,7 @@ public class AuthenticationManager {
|
||||||
userSession.setNote(AUTH_TIME, String.valueOf(authTime));
|
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());
|
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()) {
|
for (RoleModel r : accessCode.getRequestedRoles()) {
|
||||||
|
|
||||||
// Consent already granted by user
|
// Consent already granted by user
|
||||||
|
@ -535,7 +550,7 @@ public class AuthenticationManager {
|
||||||
|
|
||||||
List<RoleModel> realmRoles = new LinkedList<>();
|
List<RoleModel> realmRoles = new LinkedList<>();
|
||||||
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<>();
|
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<>();
|
||||||
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
|
ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
|
||||||
for (RoleModel r : accessCode.getRequestedRoles()) {
|
for (RoleModel r : accessCode.getRequestedRoles()) {
|
||||||
|
|
||||||
// Consent already granted by user
|
// 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,
|
protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType,
|
||||||
String tokenString, HttpHeaders headers) {
|
String tokenString, HttpHeaders headers) {
|
||||||
try {
|
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 (checkActive) {
|
||||||
if (!token.isActive() || token.getIssuedAt() < realm.getNotBefore()) {
|
if (!token.isActive() || token.getIssuedAt() < realm.getNotBefore()) {
|
||||||
logger.debug("identity cookie expired");
|
logger.debug("Identity cookie expired");
|
||||||
return null;
|
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);
|
return new AuthResult(user, userSession, token);
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
logger.debug("Failed to verify identity token", e);
|
logger.debugf("Failed to verify identity token: %s", e.getMessage());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -244,7 +244,7 @@ public class ResourceAdminManager {
|
||||||
|
|
||||||
protected boolean sendLogoutRequest(RealmModel realm, ClientModel resource, List<String> adapterSessionIds, List<String> userSessions, int notBefore, String managementUrl) {
|
protected boolean sendLogoutRequest(RealmModel realm, ClientModel resource, List<String> adapterSessionIds, List<String> userSessions, int notBefore, String managementUrl) {
|
||||||
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), adapterSessionIds, notBefore, userSessions);
|
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);
|
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();
|
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build();
|
||||||
try {
|
try {
|
||||||
|
@ -295,7 +295,7 @@ public class ResourceAdminManager {
|
||||||
|
|
||||||
protected boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) {
|
protected boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) {
|
||||||
PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), notBefore);
|
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);
|
logger.debugv("pushRevocation resource: {0} url: {1}", resource.getClientId(), managementUrl);
|
||||||
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build();
|
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build();
|
||||||
try {
|
try {
|
||||||
|
@ -333,7 +333,7 @@ public class ResourceAdminManager {
|
||||||
|
|
||||||
protected boolean sendTestNodeAvailabilityRequest(RealmModel realm, ClientModel client, String managementUrl) {
|
protected boolean sendTestNodeAvailabilityRequest(RealmModel realm, ClientModel client, String managementUrl) {
|
||||||
TestAvailabilityAction adminAction = new TestAvailabilityAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, client.getClientId());
|
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);
|
logger.debugv("testNodes availability resource: {0} url: {1}", client.getClientId(), managementUrl);
|
||||||
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_TEST_AVAILABLE).build();
|
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_TEST_AVAILABLE).build();
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -714,7 +714,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ClientSessionModel clientSession = auth.getClientSession();
|
ClientSessionModel clientSession = auth.getClientSession();
|
||||||
ClientSessionCode clientSessionCode = new ClientSessionCode(realm, clientSession);
|
ClientSessionCode clientSessionCode = new ClientSessionCode(session, realm, clientSession);
|
||||||
clientSessionCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
clientSessionCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||||
clientSession.setRedirectUri(redirectUri);
|
clientSession.setRedirectUri(redirectUri);
|
||||||
clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());
|
clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());
|
||||||
|
|
|
@ -320,7 +320,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||||
ctx.saveToClientSession(clientSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
|
ctx.saveToClientSession(clientSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
|
||||||
|
|
||||||
URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo)
|
URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo)
|
||||||
.queryParam(OAuth2Constants.CODE, context.getCode())
|
.queryParam(OAuth2Constants.CODE, clientCode.getCode())
|
||||||
.build(realmModel.getName());
|
.build(realmModel.getName());
|
||||||
return Response.status(302).location(redirect).build();
|
return Response.status(302).location(redirect).build();
|
||||||
|
|
||||||
|
@ -333,7 +333,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||||
updateFederatedIdentity(context, federatedUser);
|
updateFederatedIdentity(context, federatedUser);
|
||||||
clientSession.setAuthenticatedUser(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) {
|
if (parsedCode.response != null) {
|
||||||
return parsedCode.response;
|
return parsedCode.response;
|
||||||
}
|
}
|
||||||
ClientSessionModel clientSession = parsedCode.clientSessionCode.getClientSession();
|
return afterFirstBrokerLogin(parsedCode.clientSessionCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response afterFirstBrokerLogin(ClientSessionCode clientSessionCode) {
|
||||||
|
ClientSessionModel clientSession = clientSessionCode.getClientSession();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.event.detail(Details.CODE_ID, clientSession.getId())
|
this.event.detail(Details.CODE_ID, clientSession.getId())
|
||||||
|
@ -435,7 +439,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||||
updateFederatedIdentity(context, federatedUser);
|
updateFederatedIdentity(context, federatedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
return finishOrRedirectToPostBrokerLogin(clientSession, context, true);
|
return finishOrRedirectToPostBrokerLogin(clientSession, context, true, clientSessionCode);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, 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();
|
String postBrokerLoginFlowId = context.getIdpConfig().getPostBrokerLoginFlowId();
|
||||||
if (postBrokerLoginFlowId == null) {
|
if (postBrokerLoginFlowId == null) {
|
||||||
|
|
||||||
logger.debugf("Skip redirect to postBrokerLogin flow. PostBrokerLogin flow not set for identityProvider '%s'.", context.getIdpConfig().getAlias());
|
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 {
|
} else {
|
||||||
|
|
||||||
logger.debugf("Redirect to postBrokerLogin flow after authentication with identityProvider '%s'.", context.getIdpConfig().getAlias());
|
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));
|
clientSession.setNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN, String.valueOf(wasFirstBrokerLogin));
|
||||||
|
|
||||||
URI redirect = LoginActionsService.postBrokerLoginProcessor(uriInfo)
|
URI redirect = LoginActionsService.postBrokerLoginProcessor(uriInfo)
|
||||||
.queryParam(OAuth2Constants.CODE, context.getCode())
|
.queryParam(OAuth2Constants.CODE, clientSessionCode.getCode())
|
||||||
.build(realmModel.getName());
|
.build(realmModel.getName());
|
||||||
return Response.status(302).location(redirect).build();
|
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_BROKERED_IDENTITY_CONTEXT);
|
||||||
clientSession.removeNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN);
|
clientSession.removeNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN);
|
||||||
|
|
||||||
return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin);
|
return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin, parsedCode.clientSessionCode);
|
||||||
} catch (IdentityBrokerException e) {
|
} catch (IdentityBrokerException e) {
|
||||||
return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, 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();
|
String providerId = context.getIdpConfig().getAlias();
|
||||||
UserModel federatedUser = clientSession.getAuthenticatedUser();
|
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 redirectToErrorPage(Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, federatedUser.getUsername(), linkingUser.getUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
return afterFirstBrokerLogin(context.getCode());
|
return afterFirstBrokerLogin(clientSessionCode);
|
||||||
} else {
|
} else {
|
||||||
return finishBrokerAuthentication(context, federatedUser, clientSession, providerId);
|
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);
|
logger.debugf("Performing local authentication for user [%s].", federatedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AuthenticationProcessor.redirectToRequiredActions(realmModel, clientSession, uriInfo);
|
return AuthenticationProcessor.redirectToRequiredActions(session, realmModel, clientSession, uriInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -619,7 +619,7 @@ public class LoginActionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response redirectToAfterBrokerLoginEndpoint(ClientSessionModel clientSession, boolean firstBrokerLogin) {
|
private Response redirectToAfterBrokerLoginEndpoint(ClientSessionModel clientSession, boolean firstBrokerLogin) {
|
||||||
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
|
ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
|
||||||
clientSession.setTimestamp(Time.currentTime());
|
clientSession.setTimestamp(Time.currentTime());
|
||||||
|
|
||||||
URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) :
|
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);
|
event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN);
|
||||||
|
|
||||||
return AuthenticationProcessor.redirectToRequiredActions(realm, clientSession, uriInfo);
|
return AuthenticationProcessor.redirectToRequiredActions(session, realm, clientSession, uriInfo);
|
||||||
} else {
|
} else {
|
||||||
Checks checks = new Checks();
|
Checks checks = new Checks();
|
||||||
if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
|
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.getUserSession().getUser().setEmailVerified(true);
|
||||||
clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
|
clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
|
||||||
clientSession.setNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), "true");
|
clientSession.setNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), "true");
|
||||||
return AuthenticationProcessor.redirectToRequiredActions(realm, clientSession, uriInfo);
|
return AuthenticationProcessor.redirectToRequiredActions(session, realm, clientSession, uriInfo);
|
||||||
} else {
|
} else {
|
||||||
event.error(Errors.INVALID_CODE);
|
event.error(Errors.INVALID_CODE);
|
||||||
return ErrorPage.error(session, Messages.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)) {
|
if (AuthenticationManager.isActionRequired(session, userSession, clientSession, clientConnection, request, uriInfo, event)) {
|
||||||
// redirect to a generic code URI so that browser refresh will work
|
// redirect to a generic code URI so that browser refresh will work
|
||||||
return redirectToRequiredActions(code);
|
return redirectToRequiredActions(checks.clientCode.getCode());
|
||||||
} else {
|
} else {
|
||||||
return AuthenticationManager.finishedRequiredActions(session, userSession, clientSession, clientConnection, request, uriInfo, event);
|
return AuthenticationManager.finishedRequiredActions(session, userSession, clientSession, clientConnection, request, uriInfo, event);
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.keycloak.services.resources;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.jboss.resteasy.spi.HttpResponse;
|
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.models.RealmModel;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||||
import org.keycloak.representations.idm.PublishedRealmRepresentation;
|
import org.keycloak.representations.idm.PublishedRealmRepresentation;
|
||||||
|
@ -52,6 +54,9 @@ public class PublicRealmResource {
|
||||||
@Context
|
@Context
|
||||||
protected HttpResponse response;
|
protected HttpResponse response;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
protected KeycloakSession session;
|
||||||
|
|
||||||
protected RealmModel realm;
|
protected RealmModel realm;
|
||||||
|
|
||||||
public PublicRealmResource(RealmModel realm) {
|
public PublicRealmResource(RealmModel realm) {
|
||||||
|
@ -79,16 +84,16 @@ public class PublicRealmResource {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public PublishedRealmRepresentation getRealm() {
|
public PublishedRealmRepresentation getRealm() {
|
||||||
Cors.add(request).allowedOrigins(Cors.ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD).auth().build(response);
|
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();
|
PublishedRealmRepresentation rep = new PublishedRealmRepresentation();
|
||||||
rep.setRealm(realm.getName());
|
rep.setRealm(realm.getName());
|
||||||
rep.setTokenServiceUrl(OIDCLoginProtocolService.tokenServiceBaseUrl(uriInfo).build(realm.getName()).toString());
|
rep.setTokenServiceUrl(OIDCLoginProtocolService.tokenServiceBaseUrl(uriInfo).build(realm.getName()).toString());
|
||||||
rep.setAccountServiceUrl(AccountService.accountServiceBaseUrl(uriInfo).build(realm.getName()).toString());
|
rep.setAccountServiceUrl(AccountService.accountServiceBaseUrl(uriInfo).build(realm.getName()).toString());
|
||||||
rep.setAdminApiUrl(uriInfo.getBaseUriBuilder().path(AdminRoot.class).build().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());
|
rep.setNotBefore(realm.getNotBefore());
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,6 +125,7 @@ public class AdminRoot {
|
||||||
if (realm == null) {
|
if (realm == null) {
|
||||||
throw new NotFoundException("Realm not found. Did you type in a bad URL?");
|
throw new NotFoundException("Realm not found. Did you type in a bad URL?");
|
||||||
}
|
}
|
||||||
|
session.getContext().setRealm(realm);
|
||||||
return realm;
|
return realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,6 +171,7 @@ public class AdminRoot {
|
||||||
if (realm == null) {
|
if (realm == null) {
|
||||||
throw new UnauthorizedException("Unknown realm in token");
|
throw new UnauthorizedException("Unknown realm in token");
|
||||||
}
|
}
|
||||||
|
session.getContext().setRealm(realm);
|
||||||
AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, headers);
|
AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, headers);
|
||||||
if (authResult == null) {
|
if (authResult == null) {
|
||||||
logger.debug("Token not valid");
|
logger.debug("Token not valid");
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue