Merge pull request #1604 from mposolda/master
KEYCLOAK-1811 Pluggable client authentication config through adapter …
This commit is contained in:
commit
d0b08cacd8
15 changed files with 418 additions and 51 deletions
|
@ -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
|
||||
|
|
|
@ -85,7 +85,19 @@ public final class KeycloakAdapterConfigService {
|
|||
}
|
||||
|
||||
String credentialName = credentialNameFromOp(operation);
|
||||
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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -84,7 +84,19 @@ public final class KeycloakAdapterConfigService {
|
|||
}
|
||||
|
||||
String credentialName = credentialNameFromOp(operation);
|
||||
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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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>
|
|
@ -84,7 +84,19 @@ public final class KeycloakAdapterConfigService {
|
|||
}
|
||||
|
||||
String credentialName = credentialNameFromOp(operation);
|
||||
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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue