keycloak-uncached
Changes
integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java 21(+18 -3)
integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java 14(+13 -1)
integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakSubsystemParser.java 81(+76 -5)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java 14(+13 -1)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java 81(+76 -5)
integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java 38(+37 -1)
integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml 4(+3 -1)
integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java 14(+13 -1)
integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java 81(+76 -5)
integration/wildfly/wf9-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java 37(+36 -1)
Details
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java
index 6503e67..d68c7cb 100644
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java
@@ -51,7 +51,7 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
}
String clientKeystoreType = (String) cfg.get("client-keystore-type");
- KeystoreUtil.KeystoreFormat clientKeystoreFormat = clientKeystoreType==null ? KeystoreUtil.KeystoreFormat.JKS : Enum.valueOf(KeystoreUtil.KeystoreFormat.class, clientKeystoreType);
+ KeystoreUtil.KeystoreFormat clientKeystoreFormat = clientKeystoreType==null ? KeystoreUtil.KeystoreFormat.JKS : Enum.valueOf(KeystoreUtil.KeystoreFormat.class, clientKeystoreType.toUpperCase());
String clientKeystorePassword = (String) cfg.get("client-keystore-password");
if (clientKeystorePassword == null) {
@@ -69,8 +69,23 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
}
this.privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
- Integer tokenExp = (Integer) cfg.get("token-timeout");
- this.tokenTimeout = (tokenExp==null) ? 10 : tokenExp;
+ this.tokenTimeout = asInt(cfg, "token-timeout", 10);
+ }
+
+ // TODO: Generic method for this?
+ private Integer asInt(Map<String, Object> cfg, String cfgKey, int defaultValue) {
+ Object cfgObj = cfg.get(cfgKey);
+ if (cfgObj == null) {
+ return defaultValue;
+ }
+
+ if (cfgObj instanceof String) {
+ return Integer.parseInt(cfgObj.toString());
+ } else if (cfgObj instanceof Number) {
+ return ((Number) cfgObj).intValue();
+ } else {
+ throw new IllegalArgumentException("Can't parse " + cfgKey + " from the config. Value is " + cfgObj);
+ }
}
@Override
diff --git a/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java b/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java
index af9e74f..845da8e 100755
--- a/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java
+++ b/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java
@@ -85,7 +85,19 @@ public final class KeycloakAdapterConfigService {
}
String credentialName = credentialNameFromOp(operation);
- credentials.get(credentialName).set(model.get("value").asString());
+ if (!credentialName.contains(".")) {
+ credentials.get(credentialName).set(model.get("value").asString());
+ } else {
+ String[] parts = credentialName.split("\\.");
+ String provider = parts[0];
+ String property = parts[1];
+ ModelNode credential = credentials.get(provider);
+ if (!credential.isDefined()) {
+ credential = new ModelNode();
+ }
+ credential.get(property).set(model.get("value").asString());
+ credentials.set(provider, credential);
+ }
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(CREDENTIALS_JSON_NAME).set(credentials);
diff --git a/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakSubsystemParser.java b/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakSubsystemParser.java
index 5c61e55..42b3996 100755
--- a/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakSubsystemParser.java
+++ b/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakSubsystemParser.java
@@ -34,7 +34,10 @@ import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
/**
* The subsystem parser, which uses stax to read and write to and from xml
@@ -125,12 +128,48 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
String name = readNameAttribute(reader);
+
+ Map<String, String> values = new HashMap<>();
+ String textValue = null;
+ while (reader.hasNext()) {
+ int next = reader.next();
+ if (next == CHARACTERS) {
+ // text value of credential element (like for "secret" )
+ String text = reader.getText();
+ if (text == null || text.trim().isEmpty()) {
+ continue;
+ }
+ textValue = text;
+ } else if (next == START_ELEMENT) {
+ String key = reader.getLocalName();
+ reader.next();
+ String value = reader.getText();
+ reader.next();
+
+ values.put(key, value);
+ } else if (next == END_ELEMENT) {
+ break;
+ }
+ }
+
+ if (textValue != null) {
+ ModelNode addCredential = getCredentialToAdd(parent, name, textValue);
+ credentialsToAdd.add(addCredential);
+ } else {
+ for (Map.Entry<String, String> entry : values.entrySet()) {
+ ModelNode addCredential = getCredentialToAdd(parent, name + "." + entry.getKey(), entry.getValue());
+ credentialsToAdd.add(addCredential);
+ }
+ }
+ }
+
+ private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
ModelNode addCredential = new ModelNode();
addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name));
addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
- addCredential.get(CredentialDefinition.VALUE.getName()).set(reader.getElementText());
- credentialsToAdd.add(addCredential);
+ addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
+ return addCredential;
}
// expects that the current tag will have one single attribute called "name"
@@ -199,11 +238,43 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
}
private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException {
+ Map<String, Object> parsed = new LinkedHashMap<>();
for (Property credential : credentials.asPropertyList()) {
+ String credName = credential.getName();
+ String credValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
+
+ if (credName.contains(".")) {
+ String[] parts = credName.split("\\.");
+ String provider = parts[0];
+ String propKey = parts[1];
+
+ Map<String, String> currentProviderMap = (Map<String, String>) parsed.get(provider);
+ if (currentProviderMap == null) {
+ currentProviderMap = new LinkedHashMap<>();
+ parsed.put(provider, currentProviderMap);
+ }
+ currentProviderMap.put(propKey, credValue);
+ } else {
+ parsed.put(credName, credValue);
+ }
+ }
+
+ for (Map.Entry<String, Object> entry : parsed.entrySet()) {
writer.writeStartElement(CredentialDefinition.TAG_NAME);
- writer.writeAttribute("name", credential.getName());
- String credentialValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
- writeCharacters(writer, credentialValue);
+ writer.writeAttribute("name", entry.getKey());
+
+ Object value = entry.getValue();
+ if (value instanceof String) {
+ writeCharacters(writer, (String) value);
+ } else {
+ Map<String, String> credentialProps = (Map<String, String>) value;
+ for (Map.Entry<String, String> prop : credentialProps.entrySet()) {
+ writer.writeStartElement(prop.getKey());
+ writeCharacters(writer, prop.getValue());
+ writer.writeEndElement();
+ }
+ }
+
writer.writeEndElement();
}
}
diff --git a/integration/as7-eap6/as7-subsystem/src/main/resources/schema/keycloak_1_1.xsd b/integration/as7-eap6/as7-subsystem/src/main/resources/schema/keycloak_1_1.xsd
index 269b323..75de38a 100755
--- a/integration/as7-eap6/as7-subsystem/src/main/resources/schema/keycloak_1_1.xsd
+++ b/integration/as7-eap6/as7-subsystem/src/main/resources/schema/keycloak_1_1.xsd
@@ -94,12 +94,11 @@
</xs:annotation>
</xs:attribute>
</xs:complexType>
-
- <xs:complexType name="credential-type">
- <xs:simpleContent>
- <xs:extension base="xs:string">
- <xs:attribute name="name" type="xs:string" />
- </xs:extension>
- </xs:simpleContent>
+
+ <xs:complexType name="credential-type" mixed="true">
+ <xs:sequence maxOccurs="unbounded" minOccurs="0">
+ <xs:any processContents="lax"></xs:any>
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
</xs:schema>
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java
index 4843534..2a0b93c 100755
--- a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java
@@ -84,7 +84,19 @@ public final class KeycloakAdapterConfigService {
}
String credentialName = credentialNameFromOp(operation);
- credentials.get(credentialName).set(model.get("value").asString());
+ if (!credentialName.contains(".")) {
+ credentials.get(credentialName).set(model.get("value").asString());
+ } else {
+ String[] parts = credentialName.split("\\.");
+ String provider = parts[0];
+ String property = parts[1];
+ ModelNode credential = credentials.get(provider);
+ if (!credential.isDefined()) {
+ credential = new ModelNode();
+ }
+ credential.get(property).set(model.get("value").asString());
+ credentials.set(provider, credential);
+ }
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(CREDENTIALS_JSON_NAME).set(credentials);
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java
index efa260b..a261bc4 100755
--- a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java
@@ -35,7 +35,10 @@ import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
/**
* The subsystem parser, which uses stax to read and write to and from xml
@@ -126,12 +129,48 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
String name = readNameAttribute(reader);
+
+ Map<String, String> values = new HashMap<>();
+ String textValue = null;
+ while (reader.hasNext()) {
+ int next = reader.next();
+ if (next == CHARACTERS) {
+ // text value of credential element (like for "secret" )
+ String text = reader.getText();
+ if (text == null || text.trim().isEmpty()) {
+ continue;
+ }
+ textValue = text;
+ } else if (next == START_ELEMENT) {
+ String key = reader.getLocalName();
+ reader.next();
+ String value = reader.getText();
+ reader.next();
+
+ values.put(key, value);
+ } else if (next == END_ELEMENT) {
+ break;
+ }
+ }
+
+ if (textValue != null) {
+ ModelNode addCredential = getCredentialToAdd(parent, name, textValue);
+ credentialsToAdd.add(addCredential);
+ } else {
+ for (Map.Entry<String, String> entry : values.entrySet()) {
+ ModelNode addCredential = getCredentialToAdd(parent, name + "." + entry.getKey(), entry.getValue());
+ credentialsToAdd.add(addCredential);
+ }
+ }
+ }
+
+ private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
ModelNode addCredential = new ModelNode();
addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name));
addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
- addCredential.get(CredentialDefinition.VALUE.getName()).set(reader.getElementText());
- credentialsToAdd.add(addCredential);
+ addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
+ return addCredential;
}
// expects that the current tag will have one single attribute called "name"
@@ -200,11 +239,43 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
}
private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException {
+ Map<String, Object> parsed = new LinkedHashMap<>();
for (Property credential : credentials.asPropertyList()) {
+ String credName = credential.getName();
+ String credValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
+
+ if (credName.contains(".")) {
+ String[] parts = credName.split("\\.");
+ String provider = parts[0];
+ String propKey = parts[1];
+
+ Map<String, String> currentProviderMap = (Map<String, String>) parsed.get(provider);
+ if (currentProviderMap == null) {
+ currentProviderMap = new LinkedHashMap<>();
+ parsed.put(provider, currentProviderMap);
+ }
+ currentProviderMap.put(propKey, credValue);
+ } else {
+ parsed.put(credName, credValue);
+ }
+ }
+
+ for (Map.Entry<String, Object> entry : parsed.entrySet()) {
writer.writeStartElement(CredentialDefinition.TAG_NAME);
- writer.writeAttribute("name", credential.getName());
- String credentialValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
- writeCharacters(writer, credentialValue);
+ writer.writeAttribute("name", entry.getKey());
+
+ Object value = entry.getValue();
+ if (value instanceof String) {
+ writeCharacters(writer, (String) value);
+ } else {
+ Map<String, String> credentialProps = (Map<String, String>) value;
+ for (Map.Entry<String, String> prop : credentialProps.entrySet()) {
+ writer.writeStartElement(prop.getKey());
+ writeCharacters(writer, prop.getValue());
+ writer.writeEndElement();
+ }
+ }
+
writer.writeEndElement();
}
}
diff --git a/integration/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/integration/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
index 269b323..75de38a 100755
--- a/integration/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
+++ b/integration/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
@@ -94,12 +94,11 @@
</xs:annotation>
</xs:attribute>
</xs:complexType>
-
- <xs:complexType name="credential-type">
- <xs:simpleContent>
- <xs:extension base="xs:string">
- <xs:attribute name="name" type="xs:string" />
- </xs:extension>
- </xs:simpleContent>
+
+ <xs:complexType name="credential-type" mixed="true">
+ <xs:sequence maxOccurs="unbounded" minOccurs="0">
+ <xs:any processContents="lax"></xs:any>
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
</xs:schema>
diff --git a/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java b/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java
index 93e9a59..6089293 100755
--- a/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java
+++ b/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java
@@ -16,6 +16,9 @@
*/
package org.keycloak.subsystem.wf8.extension;
+import org.jboss.as.controller.PathAddress;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest;
import org.jboss.dmr.ModelNode;
import org.junit.Test;
@@ -49,13 +52,46 @@ public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest {
node.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes");
node.get("ssl-required").set("external");
node.get("expose-token").set(true);
+
+ ModelNode jwtCredential = new ModelNode();
+ jwtCredential.get("client-keystore-file").set("/tmp/keystore.jks");
+ jwtCredential.get("client-keystore-password").set("changeit");
ModelNode credential = new ModelNode();
- credential.get("password").set("password");
+ credential.get("jwt").set(jwtCredential);
node.get("credentials").set(credential);
System.out.println("json=" + node.toJSONString(false));
}
+ @Test
+ public void testJsonFromSignedJWTCredentials() {
+ KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
+
+ PathAddress addr = PathAddress.pathAddress(PathElement.pathElement("subsystem", "keycloak"), PathElement.pathElement("secure-deployment", "foo"));
+ ModelNode deploymentOp = new ModelNode();
+ deploymentOp.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+ ModelNode deployment = new ModelNode();
+ deployment.get("realm").set("demo");
+ deployment.get("resource").set("customer-portal");
+ service.addSecureDeployment(deploymentOp, deployment);
+
+ addCredential(addr, service, "secret", "secret1");
+ addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks");
+ addCredential(addr, service, "jwt.token-timeout", "10");
+
+ System.out.println("Deployment: " + service.getJSON("foo"));
+ }
+
+ private void addCredential(PathAddress parent, KeycloakAdapterConfigService service, String key, String value) {
+ PathAddress credAddr = PathAddress.pathAddress(parent, PathElement.pathElement("credential", key));
+ ModelNode credOp = new ModelNode();
+ credOp.get(ModelDescriptionConstants.OP_ADDR).set(credAddr.toModelNode());
+ ModelNode credential = new ModelNode();
+ credential.get("value").set(value);
+ service.addCredential(credOp, credential);
+ }
+
+
@Override
protected String getSubsystemXml() throws IOException {
return readResource("keycloak-1.1.xml");
diff --git a/integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml b/integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
index 2d12d88..e512f0e 100644
--- a/integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
+++ b/integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
@@ -19,6 +19,8 @@
</realm-public-key>
<auth-server-url>http://localhost:8080/auth</auth-server-url>
<ssl-required>EXTERNAL</ssl-required>
- <credential name="secret">2769a4a2-5be0-454f-838f-f33b7755b667</credential>
+ <credential name="jwt">
+ <client-keystore-file>/tmp/keystore.jks</client-keystore-file>
+ </credential>
</secure-deployment>
</subsystem>
\ No newline at end of file
diff --git a/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java b/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
index c6f616a..8eb4b9e 100755
--- a/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
+++ b/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
@@ -84,7 +84,19 @@ public final class KeycloakAdapterConfigService {
}
String credentialName = credentialNameFromOp(operation);
- credentials.get(credentialName).set(model.get("value").asString());
+ if (!credentialName.contains(".")) {
+ credentials.get(credentialName).set(model.get("value").asString());
+ } else {
+ String[] parts = credentialName.split("\\.");
+ String provider = parts[0];
+ String property = parts[1];
+ ModelNode credential = credentials.get(provider);
+ if (!credential.isDefined()) {
+ credential = new ModelNode();
+ }
+ credential.get(property).set(model.get("value").asString());
+ credentials.set(provider, credential);
+ }
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(CREDENTIALS_JSON_NAME).set(credentials);
diff --git a/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java b/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java
index 3c63f7e..9a9e667 100755
--- a/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java
+++ b/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java
@@ -35,7 +35,10 @@ import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
/**
* The subsystem parser, which uses stax to read and write to and from xml
@@ -126,12 +129,48 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
String name = readNameAttribute(reader);
+
+ Map<String, String> values = new HashMap<>();
+ String textValue = null;
+ while (reader.hasNext()) {
+ int next = reader.next();
+ if (next == CHARACTERS) {
+ // text value of credential element (like for "secret" )
+ String text = reader.getText();
+ if (text == null || text.trim().isEmpty()) {
+ continue;
+ }
+ textValue = text;
+ } else if (next == START_ELEMENT) {
+ String key = reader.getLocalName();
+ reader.next();
+ String value = reader.getText();
+ reader.next();
+
+ values.put(key, value);
+ } else if (next == END_ELEMENT) {
+ break;
+ }
+ }
+
+ if (textValue != null) {
+ ModelNode addCredential = getCredentialToAdd(parent, name, textValue);
+ credentialsToAdd.add(addCredential);
+ } else {
+ for (Map.Entry<String, String> entry : values.entrySet()) {
+ ModelNode addCredential = getCredentialToAdd(parent, name + "." + entry.getKey(), entry.getValue());
+ credentialsToAdd.add(addCredential);
+ }
+ }
+ }
+
+ private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
ModelNode addCredential = new ModelNode();
addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name));
addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
- addCredential.get(CredentialDefinition.VALUE.getName()).set(reader.getElementText());
- credentialsToAdd.add(addCredential);
+ addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
+ return addCredential;
}
// expects that the current tag will have one single attribute called "name"
@@ -200,11 +239,43 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
}
private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException {
+ Map<String, Object> parsed = new LinkedHashMap<>();
for (Property credential : credentials.asPropertyList()) {
+ String credName = credential.getName();
+ String credValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
+
+ if (credName.contains(".")) {
+ String[] parts = credName.split("\\.");
+ String provider = parts[0];
+ String propKey = parts[1];
+
+ Map<String, String> currentProviderMap = (Map<String, String>) parsed.get(provider);
+ if (currentProviderMap == null) {
+ currentProviderMap = new LinkedHashMap<>();
+ parsed.put(provider, currentProviderMap);
+ }
+ currentProviderMap.put(propKey, credValue);
+ } else {
+ parsed.put(credName, credValue);
+ }
+ }
+
+ for (Map.Entry<String, Object> entry : parsed.entrySet()) {
writer.writeStartElement(CredentialDefinition.TAG_NAME);
- writer.writeAttribute("name", credential.getName());
- String credentialValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
- writeCharacters(writer, credentialValue);
+ writer.writeAttribute("name", entry.getKey());
+
+ Object value = entry.getValue();
+ if (value instanceof String) {
+ writeCharacters(writer, (String) value);
+ } else {
+ Map<String, String> credentialProps = (Map<String, String>) value;
+ for (Map.Entry<String, String> prop : credentialProps.entrySet()) {
+ writer.writeStartElement(prop.getKey());
+ writeCharacters(writer, prop.getValue());
+ writer.writeEndElement();
+ }
+ }
+
writer.writeEndElement();
}
}
diff --git a/integration/wildfly/wf9-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/integration/wildfly/wf9-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
index 269b323..0abaac1 100755
--- a/integration/wildfly/wf9-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
+++ b/integration/wildfly/wf9-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
@@ -95,11 +95,10 @@
</xs:attribute>
</xs:complexType>
- <xs:complexType name="credential-type">
- <xs:simpleContent>
- <xs:extension base="xs:string">
- <xs:attribute name="name" type="xs:string" />
- </xs:extension>
- </xs:simpleContent>
+ <xs:complexType name="credential-type" mixed="true">
+ <xs:sequence maxOccurs="unbounded" minOccurs="0">
+ <xs:any processContents="lax"></xs:any>
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
</xs:schema>
diff --git a/integration/wildfly/wf9-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java b/integration/wildfly/wf9-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java
index baa131b..dc1e981 100755
--- a/integration/wildfly/wf9-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java
+++ b/integration/wildfly/wf9-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java
@@ -16,6 +16,9 @@
*/
package org.keycloak.subsystem.adapter.extension;
+import org.jboss.as.controller.PathAddress;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest;
import org.jboss.dmr.ModelNode;
import org.junit.Test;
@@ -49,13 +52,45 @@ public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest {
node.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes");
node.get("ssl-required").set("external");
node.get("expose-token").set(true);
+
+ ModelNode jwtCredential = new ModelNode();
+ jwtCredential.get("client-keystore-file").set("/tmp/keystore.jks");
+ jwtCredential.get("client-keystore-password").set("changeit");
ModelNode credential = new ModelNode();
- credential.get("password").set("password");
+ credential.get("jwt").set(jwtCredential);
node.get("credentials").set(credential);
System.out.println("json=" + node.toJSONString(false));
}
+ @Test
+ public void testJsonFromSignedJWTCredentials() {
+ KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
+
+ PathAddress addr = PathAddress.pathAddress(PathElement.pathElement("subsystem", "keycloak"), PathElement.pathElement("secure-deployment", "foo"));
+ ModelNode deploymentOp = new ModelNode();
+ deploymentOp.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+ ModelNode deployment = new ModelNode();
+ deployment.get("realm").set("demo");
+ deployment.get("resource").set("customer-portal");
+ service.addSecureDeployment(deploymentOp, deployment);
+
+ addCredential(addr, service, "secret", "secret1");
+ addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks");
+ addCredential(addr, service, "jwt.token-timeout", "10");
+
+ System.out.println("Deployment: " + service.getJSON("foo"));
+ }
+
+ private void addCredential(PathAddress parent, KeycloakAdapterConfigService service, String key, String value) {
+ PathAddress credAddr = PathAddress.pathAddress(parent, PathElement.pathElement("credential", key));
+ ModelNode credOp = new ModelNode();
+ credOp.get(ModelDescriptionConstants.OP_ADDR).set(credAddr.toModelNode());
+ ModelNode credential = new ModelNode();
+ credential.get("value").set(value);
+ service.addCredential(credOp, credential);
+ }
+
@Override
protected String getSubsystemXml() throws IOException {
return readResource("keycloak-1.1.xml");
diff --git a/integration/wildfly/wf9-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml b/integration/wildfly/wf9-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
index 2d12d88..e512f0e 100644
--- a/integration/wildfly/wf9-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
+++ b/integration/wildfly/wf9-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
@@ -19,6 +19,8 @@
</realm-public-key>
<auth-server-url>http://localhost:8080/auth</auth-server-url>
<ssl-required>EXTERNAL</ssl-required>
- <credential name="secret">2769a4a2-5be0-454f-838f-f33b7755b667</credential>
+ <credential name="jwt">
+ <client-keystore-file>/tmp/keystore.jks</client-keystore-file>
+ </credential>
</secure-deployment>
</subsystem>
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
index fbf530b..fa71557 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
@@ -215,10 +215,8 @@ public class ClientManager {
rep.setResource(clientModel.getClientId());
- if (!clientModel.isBearerOnly() && !clientModel.isPublicClient()) {
- String clientAuthenticator = clientModel.getClientAuthenticatorType();
- ClientAuthenticatorFactory authenticator = (ClientAuthenticatorFactory) realmManager.getSession().getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, clientAuthenticator);
- Map<String, Object> adapterConfig = authenticator.getAdapterConfiguration(clientModel);
+ if (showClientCredentialsAdapterConfig(clientModel)) {
+ Map<String, Object> adapterConfig = getClientCredentialsAdapterConfig(clientModel);
rep.setCredentials(adapterConfig);
}
@@ -240,8 +238,23 @@ public class ClientManager {
buffer.append(" <ssl-required>").append(realmModel.getSslRequired().name()).append("</ssl-required>\n");
buffer.append(" <resource>").append(clientModel.getClientId()).append("</resource>\n");
String cred = clientModel.getSecret();
- if (!clientModel.isBearerOnly() && !clientModel.isPublicClient()) {
- buffer.append(" <credential name=\"secret\">").append(cred).append("</credential>\n");
+ if (showClientCredentialsAdapterConfig(clientModel)) {
+ Map<String, Object> adapterConfig = getClientCredentialsAdapterConfig(clientModel);
+ for (Map.Entry<String, Object> entry : adapterConfig.entrySet()) {
+ buffer.append(" <credential name=\"" + entry.getKey() + "\">");
+
+ Object value = entry.getValue();
+ if (value instanceof Map) {
+ buffer.append("\n");
+ Map<String, Object> asMap = (Map<String, Object>) value;
+ for (Map.Entry<String, Object> credEntry : asMap.entrySet()) {
+ buffer.append(" <" + credEntry.getKey() + ">" + credEntry.getValue().toString() + "</" + credEntry.getKey() + ">\n");
+ }
+ buffer.append(" </credential>\n");
+ } else {
+ buffer.append(value.toString()).append("</credential>\n");
+ }
+ }
}
if (clientModel.getRoles().size() > 0) {
buffer.append(" <use-resource-role-mappings>true</use-resource-role-mappings>\n");
@@ -250,4 +263,22 @@ public class ClientManager {
return buffer.toString();
}
+ private boolean showClientCredentialsAdapterConfig(ClientModel client) {
+ if (client.isPublicClient()) {
+ return false;
+ }
+
+ if (client.isBearerOnly() && client.getNodeReRegistrationTimeout() <= 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private Map<String, Object> getClientCredentialsAdapterConfig(ClientModel client) {
+ String clientAuthenticator = client.getClientAuthenticatorType();
+ ClientAuthenticatorFactory authenticator = (ClientAuthenticatorFactory) realmManager.getSession().getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, clientAuthenticator);
+ return authenticator.getAdapterConfiguration(client);
+ }
+
}