From 050c65a52003deb07216df539d9ca82b3917a963 Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 7 Sep 2015 18:26:10 +0200 Subject: [PATCH] KEYCLOAK-1811 Pluggable client authentication config through adapter subsystem --- .../JWTClientCredentialsProvider.java | 21 ++++- .../as7/KeycloakAdapterConfigService.java | 14 +++- .../as7/KeycloakSubsystemParser.java | 81 +++++++++++++++++-- .../main/resources/schema/keycloak_1_1.xsd | 13 ++- .../KeycloakAdapterConfigService.java | 14 +++- .../extension/KeycloakSubsystemParser.java | 81 +++++++++++++++++-- .../resources/schema/wildfly-keycloak_1_1.xsd | 13 ++- .../extension/SubsystemParsingTestCase.java | 38 ++++++++- .../subsystem/wf8/extension/keycloak-1.1.xml | 4 +- .../KeycloakAdapterConfigService.java | 14 +++- .../extension/KeycloakSubsystemParser.java | 81 +++++++++++++++++-- .../resources/schema/wildfly-keycloak_1_1.xsd | 11 ++- .../extension/SubsystemParsingTestCase.java | 37 ++++++++- .../adapter/extension/keycloak-1.1.xml | 4 +- .../services/managers/ClientManager.java | 43 ++++++++-- 15 files changed, 418 insertions(+), 51 deletions(-) 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 6503e678d9..d68c7cb8e7 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 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 af9e74fa88..845da8ed36 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 5c61e55e58..42b3996063 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
  • credentialsToAdd) throws XMLStreamException { String name = readNameAttribute(reader); + + Map 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 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
  • 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 currentProviderMap = (Map) 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 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 credentialProps = (Map) value; + for (Map.Entry 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 269b3232dd..75de38ad83 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 @@ - - - - - - - + + + + + + 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 4843534260..2a0b93c845 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 efa260b46e..a261bc48c4 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
  • credentialsToAdd) throws XMLStreamException { String name = readNameAttribute(reader); + + Map 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 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
  • 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 currentProviderMap = (Map) 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 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 credentialProps = (Map) value; + for (Map.Entry 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 269b3232dd..75de38ad83 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 @@ - - - - - - - + + + + + + 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 93e9a59fcb..6089293736 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 2d12d88576..e512f0e85a 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 @@ http://localhost:8080/auth EXTERNAL - 2769a4a2-5be0-454f-838f-f33b7755b667 + + /tmp/keystore.jks + \ 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 c6f616adce..8eb4b9efbd 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 3c63f7e832..9a9e667762 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
  • credentialsToAdd) throws XMLStreamException { String name = readNameAttribute(reader); + + Map 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 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
  • 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 currentProviderMap = (Map) 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 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 credentialProps = (Map) value; + for (Map.Entry 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 269b3232dd..0abaac18d8 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 @@ - - - - - - + + + + + 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 baa131b09d..dc1e98132c 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 2d12d88576..e512f0e85a 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 @@ http://localhost:8080/auth EXTERNAL - 2769a4a2-5be0-454f-838f-f33b7755b667 + + /tmp/keystore.jks + \ 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 fbf530b36f..fa715576c4 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 adapterConfig = authenticator.getAdapterConfiguration(clientModel); + if (showClientCredentialsAdapterConfig(clientModel)) { + Map adapterConfig = getClientCredentialsAdapterConfig(clientModel); rep.setCredentials(adapterConfig); } @@ -240,8 +238,23 @@ public class ClientManager { buffer.append(" ").append(realmModel.getSslRequired().name()).append("\n"); buffer.append(" ").append(clientModel.getClientId()).append("\n"); String cred = clientModel.getSecret(); - if (!clientModel.isBearerOnly() && !clientModel.isPublicClient()) { - buffer.append(" ").append(cred).append("\n"); + if (showClientCredentialsAdapterConfig(clientModel)) { + Map adapterConfig = getClientCredentialsAdapterConfig(clientModel); + for (Map.Entry entry : adapterConfig.entrySet()) { + buffer.append(" "); + + Object value = entry.getValue(); + if (value instanceof Map) { + buffer.append("\n"); + Map asMap = (Map) value; + for (Map.Entry credEntry : asMap.entrySet()) { + buffer.append(" <" + credEntry.getKey() + ">" + credEntry.getValue().toString() + "\n"); + } + buffer.append(" \n"); + } else { + buffer.append(value.toString()).append("\n"); + } + } } if (clientModel.getRoles().size() > 0) { buffer.append(" true\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 getClientCredentialsAdapterConfig(ClientModel client) { + String clientAuthenticator = client.getClientAuthenticatorType(); + ClientAuthenticatorFactory authenticator = (ClientAuthenticatorFactory) realmManager.getSession().getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, clientAuthenticator); + return authenticator.getAdapterConfiguration(client); + } + }