Merge pull request #1604 from mposolda/master

KEYCLOAK-1811 Pluggable client authentication config through adapter …
This commit is contained in:
Marek Posolda 2015-09-08 00:12:49 +02:00
commit d0b08cacd8
15 changed files with 418 additions and 51 deletions

View file

@ -51,7 +51,7 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
} }
String clientKeystoreType = (String) cfg.get("client-keystore-type"); 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"); String clientKeystorePassword = (String) cfg.get("client-keystore-password");
if (clientKeystorePassword == null) { if (clientKeystorePassword == null) {
@ -69,8 +69,23 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
} }
this.privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat); this.privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
Integer tokenExp = (Integer) cfg.get("token-timeout"); this.tokenTimeout = asInt(cfg, "token-timeout", 10);
this.tokenTimeout = (tokenExp==null) ? 10 : tokenExp; }
// 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 @Override

View file

@ -85,7 +85,19 @@ public final class KeycloakAdapterConfigService {
} }
String credentialName = credentialNameFromOp(operation); 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)); ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(CREDENTIALS_JSON_NAME).set(credentials); deployment.get(CREDENTIALS_JSON_NAME).set(credentials);

View file

@ -34,7 +34,10 @@ import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* The subsystem parser, which uses stax to read and write to and from xml * 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 { public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
String name = readNameAttribute(reader); 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(); ModelNode addCredential = new ModelNode();
addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name)); PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name));
addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
addCredential.get(CredentialDefinition.VALUE.getName()).set(reader.getElementText()); addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
credentialsToAdd.add(addCredential); return addCredential;
} }
// expects that the current tag will have one single attribute called "name" // 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 { private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException {
Map<String, Object> parsed = new LinkedHashMap<>();
for (Property credential : credentials.asPropertyList()) { 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.writeStartElement(CredentialDefinition.TAG_NAME);
writer.writeAttribute("name", credential.getName()); writer.writeAttribute("name", entry.getKey());
String credentialValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
writeCharacters(writer, credentialValue); 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(); writer.writeEndElement();
} }
} }

View file

@ -94,12 +94,11 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="credential-type"> <xs:complexType name="credential-type" mixed="true">
<xs:simpleContent> <xs:sequence maxOccurs="unbounded" minOccurs="0">
<xs:extension base="xs:string"> <xs:any processContents="lax"></xs:any>
<xs:attribute name="name" type="xs:string" /> </xs:sequence>
</xs:extension> <xs:attribute name="name" type="xs:string" use="required" />
</xs:simpleContent>
</xs:complexType> </xs:complexType>
</xs:schema> </xs:schema>

View file

@ -84,7 +84,19 @@ public final class KeycloakAdapterConfigService {
} }
String credentialName = credentialNameFromOp(operation); 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)); ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(CREDENTIALS_JSON_NAME).set(credentials); deployment.get(CREDENTIALS_JSON_NAME).set(credentials);

View file

@ -35,7 +35,10 @@ import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* The subsystem parser, which uses stax to read and write to and from xml * 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 { public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
String name = readNameAttribute(reader); 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(); ModelNode addCredential = new ModelNode();
addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name)); PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name));
addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
addCredential.get(CredentialDefinition.VALUE.getName()).set(reader.getElementText()); addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
credentialsToAdd.add(addCredential); return addCredential;
} }
// expects that the current tag will have one single attribute called "name" // 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 { private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException {
Map<String, Object> parsed = new LinkedHashMap<>();
for (Property credential : credentials.asPropertyList()) { 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.writeStartElement(CredentialDefinition.TAG_NAME);
writer.writeAttribute("name", credential.getName()); writer.writeAttribute("name", entry.getKey());
String credentialValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
writeCharacters(writer, credentialValue); 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(); writer.writeEndElement();
} }
} }

View file

@ -94,12 +94,11 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="credential-type"> <xs:complexType name="credential-type" mixed="true">
<xs:simpleContent> <xs:sequence maxOccurs="unbounded" minOccurs="0">
<xs:extension base="xs:string"> <xs:any processContents="lax"></xs:any>
<xs:attribute name="name" type="xs:string" /> </xs:sequence>
</xs:extension> <xs:attribute name="name" type="xs:string" use="required" />
</xs:simpleContent>
</xs:complexType> </xs:complexType>
</xs:schema> </xs:schema>

View file

@ -16,6 +16,9 @@
*/ */
package org.keycloak.subsystem.wf8.extension; 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.as.subsystem.test.AbstractSubsystemBaseTest;
import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelNode;
import org.junit.Test; 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("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes");
node.get("ssl-required").set("external"); node.get("ssl-required").set("external");
node.get("expose-token").set(true); 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(); ModelNode credential = new ModelNode();
credential.get("password").set("password"); credential.get("jwt").set(jwtCredential);
node.get("credentials").set(credential); node.get("credentials").set(credential);
System.out.println("json=" + node.toJSONString(false)); 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 @Override
protected String getSubsystemXml() throws IOException { protected String getSubsystemXml() throws IOException {
return readResource("keycloak-1.1.xml"); return readResource("keycloak-1.1.xml");

View file

@ -19,6 +19,8 @@
</realm-public-key> </realm-public-key>
<auth-server-url>http://localhost:8080/auth</auth-server-url> <auth-server-url>http://localhost:8080/auth</auth-server-url>
<ssl-required>EXTERNAL</ssl-required> <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> </secure-deployment>
</subsystem> </subsystem>

View file

@ -84,7 +84,19 @@ public final class KeycloakAdapterConfigService {
} }
String credentialName = credentialNameFromOp(operation); 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)); ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(CREDENTIALS_JSON_NAME).set(credentials); deployment.get(CREDENTIALS_JSON_NAME).set(credentials);

View file

@ -35,7 +35,10 @@ import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* The subsystem parser, which uses stax to read and write to and from xml * 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 { public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
String name = readNameAttribute(reader); 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(); ModelNode addCredential = new ModelNode();
addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name)); PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name));
addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
addCredential.get(CredentialDefinition.VALUE.getName()).set(reader.getElementText()); addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
credentialsToAdd.add(addCredential); return addCredential;
} }
// expects that the current tag will have one single attribute called "name" // 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 { private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException {
Map<String, Object> parsed = new LinkedHashMap<>();
for (Property credential : credentials.asPropertyList()) { 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.writeStartElement(CredentialDefinition.TAG_NAME);
writer.writeAttribute("name", credential.getName()); writer.writeAttribute("name", entry.getKey());
String credentialValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
writeCharacters(writer, credentialValue); 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(); writer.writeEndElement();
} }
} }

View file

@ -95,11 +95,10 @@
</xs:attribute> </xs:attribute>
</xs:complexType> </xs:complexType>
<xs:complexType name="credential-type"> <xs:complexType name="credential-type" mixed="true">
<xs:simpleContent> <xs:sequence maxOccurs="unbounded" minOccurs="0">
<xs:extension base="xs:string"> <xs:any processContents="lax"></xs:any>
<xs:attribute name="name" type="xs:string" /> </xs:sequence>
</xs:extension> <xs:attribute name="name" type="xs:string" use="required" />
</xs:simpleContent>
</xs:complexType> </xs:complexType>
</xs:schema> </xs:schema>

View file

@ -16,6 +16,9 @@
*/ */
package org.keycloak.subsystem.adapter.extension; 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.as.subsystem.test.AbstractSubsystemBaseTest;
import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelNode;
import org.junit.Test; 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("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes");
node.get("ssl-required").set("external"); node.get("ssl-required").set("external");
node.get("expose-token").set(true); 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(); ModelNode credential = new ModelNode();
credential.get("password").set("password"); credential.get("jwt").set(jwtCredential);
node.get("credentials").set(credential); node.get("credentials").set(credential);
System.out.println("json=" + node.toJSONString(false)); 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 @Override
protected String getSubsystemXml() throws IOException { protected String getSubsystemXml() throws IOException {
return readResource("keycloak-1.1.xml"); return readResource("keycloak-1.1.xml");

View file

@ -19,6 +19,8 @@
</realm-public-key> </realm-public-key>
<auth-server-url>http://localhost:8080/auth</auth-server-url> <auth-server-url>http://localhost:8080/auth</auth-server-url>
<ssl-required>EXTERNAL</ssl-required> <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> </secure-deployment>
</subsystem> </subsystem>

View file

@ -215,10 +215,8 @@ public class ClientManager {
rep.setResource(clientModel.getClientId()); rep.setResource(clientModel.getClientId());
if (!clientModel.isBearerOnly() && !clientModel.isPublicClient()) { if (showClientCredentialsAdapterConfig(clientModel)) {
String clientAuthenticator = clientModel.getClientAuthenticatorType(); Map<String, Object> adapterConfig = getClientCredentialsAdapterConfig(clientModel);
ClientAuthenticatorFactory authenticator = (ClientAuthenticatorFactory) realmManager.getSession().getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, clientAuthenticator);
Map<String, Object> adapterConfig = authenticator.getAdapterConfiguration(clientModel);
rep.setCredentials(adapterConfig); rep.setCredentials(adapterConfig);
} }
@ -240,8 +238,23 @@ public class ClientManager {
buffer.append(" <ssl-required>").append(realmModel.getSslRequired().name()).append("</ssl-required>\n"); buffer.append(" <ssl-required>").append(realmModel.getSslRequired().name()).append("</ssl-required>\n");
buffer.append(" <resource>").append(clientModel.getClientId()).append("</resource>\n"); buffer.append(" <resource>").append(clientModel.getClientId()).append("</resource>\n");
String cred = clientModel.getSecret(); String cred = clientModel.getSecret();
if (!clientModel.isBearerOnly() && !clientModel.isPublicClient()) { if (showClientCredentialsAdapterConfig(clientModel)) {
buffer.append(" <credential name=\"secret\">").append(cred).append("</credential>\n"); 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) { if (clientModel.getRoles().size() > 0) {
buffer.append(" <use-resource-role-mappings>true</use-resource-role-mappings>\n"); buffer.append(" <use-resource-role-mappings>true</use-resource-role-mappings>\n");
@ -250,4 +263,22 @@ public class ClientManager {
return buffer.toString(); 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);
}
} }