KEYCLOAK-3196: Use WildFly management model for server configuration.

This commit is contained in:
Stan Silvert 2016-08-12 15:04:42 -04:00
parent 3be47dee37
commit 3493aa4ab7
39 changed files with 2225 additions and 189 deletions

View file

@ -59,6 +59,65 @@
<xsl:apply-templates select="node()|@*"/>
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
<web-context>auth</web-context>
<providers>
<provider>classpath:${jboss.home.dir}/providers/*</provider>
</providers>
<master-realm-name>master</master-realm-name>
<scheduled-task-interval>900</scheduled-task-interval>
<theme>
<staticMaxAge>2592000</staticMaxAge>
<cacheThemes>true</cacheThemes>
<cacheTemplates>true</cacheTemplates>
<dir>${jboss.home.dir}/themes</dir>
</theme>
<spi name="eventsStore">
<default-provider>jpa</default-provider>
<provider name="jpa" enabled="true">
<properties>
<property name="exclude-events" value="[&quot;REFRESH_TOKEN&quot;]"/>
</properties>
</provider>
</spi>
<spi name="realm">
<default-provider>jpa</default-provider>
</spi>
<spi name="user">
<default-provider>jpa</default-provider>
</spi>
<spi name="userCache">
<provider name="default" enabled="true"/>
</spi>
<spi name="userSessionPersister">
<default-provider>jpa</default-provider>
</spi>
<spi name="authorizationPersister">
<default-provider>jpa</default-provider>
</spi>
<spi name="timer">
<default-provider>basic</default-provider>
</spi>
<spi name="connectionsHttpClient">
<provider name="default" enabled="true"/>
</spi>
<spi name="connectionsJpa">
<provider name="default" enabled="true">
<properties>
<property name="dataSource" value="java:jboss/datasources/KeycloakDS"/>
<property name="databaseSchema" value="update"/>
</properties>
</provider>
</spi>
<spi name="realmCache">
<provider name="default" enabled="true"/>
</spi>
<spi name="connectionsInfinispan">
<default-provider>default</default-provider>
<provider name="default" enabled="true">
<properties>
<property name="cacheContainer" value="java:comp/env/infinispan/Keycloak"/>
</properties>
</provider>
</spi>
</subsystem>
<subsystem xmlns="urn:jboss:domain:keycloak:1.1"/>
<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1"/>

View file

@ -59,15 +59,4 @@
</includes>
</fileSet>
</fileSets>
<files>
<file>
<source>src/main/resources/content/standalone/configuration/keycloak-server.json</source>
<outputDirectory>content/domain/servers/server-one/configuration</outputDirectory>
</file>
<file>
<source>src/main/resources/content/standalone/configuration/keycloak-server.json</source>
<outputDirectory>content/domain/servers/server-two/configuration</outputDirectory>
</file>
</files>
</assembly>

View file

@ -1,89 +0,0 @@
{
"providers": [
"classpath:${jboss.home.dir}/providers/*"
],
"admin": {
"realm": "master"
},
"eventsStore": {
"provider": "jpa",
"jpa": {
"exclude-events": [ "REFRESH_TOKEN" ]
}
},
"realm": {
"provider": "jpa"
},
"user": {
"provider": "jpa"
},
"userCache": {
"default" : {
"enabled": true
}
},
"userSessionPersister": {
"provider": "jpa"
},
"authorizationPersister": {
"provider": "jpa"
},
"timer": {
"provider": "basic"
},
"theme": {
"staticMaxAge": 2592000,
"cacheTemplates": true,
"cacheThemes": true,
"folder": {
"dir": "${jboss.home.dir}/themes"
}
},
"scheduled": {
"interval": 900
},
"connectionsHttpClient": {
"default": {}
},
"connectionsJpa": {
"default": {
"dataSource": "java:jboss/datasources/KeycloakDS",
"initializeEmpty": true,
"migrationStrategy": "update",
"migrationExport": "${jboss.home.dir}/keycloak-database-update.sql"
}
},
"realmCache": {
"default" : {
"enabled": true
}
},
"connectionsInfinispan": {
"provider": "default",
"default": {
"cacheContainer" : "java:comp/env/infinispan/Keycloak"
}
},
"jta-lookup": {
"provider": "${keycloak.jta.lookup.provider:jboss}",
"jboss" : {
"enabled": true
}
}
}

View file

@ -55,6 +55,7 @@
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
<module name="org.jboss.resteasy.resteasy-crypto"/>
<module name="org.jboss.resteasy.resteasy-multipart-provider"/>
<module name="org.jboss.dmr"/>
<module name="javax.servlet.api"/>
<module name="com.fasterxml.jackson.core.jackson-core"/>
<module name="com.fasterxml.jackson.core.jackson-annotations"/>

View file

@ -28,6 +28,8 @@
</resources>
<dependencies>
<module name="com.fasterxml.jackson.core.jackson-core"/>
<module name="com.fasterxml.jackson.core.jackson-databind"/>
<module name="javax.api"/>
<module name="org.jboss.staxmapper"/>
<module name="org.jboss.as.controller"/>

View file

@ -84,10 +84,6 @@
</fileSets>
<files>
<file>
<source>${project.build.directory}/unpacked/keycloak-${project.version}/standalone/configuration/keycloak-server.json</source>
<outputDirectory>standalone/configuration</outputDirectory>
</file>
<file>
<source>${project.build.directory}/unpacked/keycloak-${project.version}/bin/add-user-keycloak.sh</source>
<outputDirectory>bin</outputDirectory>

View file

@ -11,4 +11,22 @@ embed-server --server-config=standalone-ha.xml
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authorization:add(mode="SYNC",owners="1")
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work:add(mode="SYNC")
/extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
/subsystem=keycloak-server:add(web-context=auth)
/subsystem=keycloak-server:add(web-context=auth,master-realm-name=master,scheduled-task-interval=900,providers=[classpath:${jboss.home.dir}/providers/*])
/subsystem=keycloak-server/theme=defaults/:add(dir=${jboss.home.dir}/themes,staticMaxAge=2592000,cacheTemplates=true,cacheThemes=true)
/subsystem=keycloak-server/spi=eventsStore/:add(default-provider=jpa)
/subsystem=keycloak-server/spi=eventsStore/provider=jpa/:add(properties={exclude-events => "[\"REFRESH_TOKEN\"]"},enabled=true)
/subsystem=keycloak-server/spi=realm/:add(default-provider=jpa)
/subsystem=keycloak-server/spi=user/:add(default-provider=jpa)
/subsystem=keycloak-server/spi=userCache/:add
/subsystem=keycloak-server/spi=userCache/provider=default/:add(enabled=true)
/subsystem=keycloak-server/spi=userSessionPersister/:add(default-provider=jpa)
/subsystem=keycloak-server/spi=authorizationPersister/:add(default-provider=jpa)
/subsystem=keycloak-server/spi=timer/:add(default-provider=basic)
/subsystem=keycloak-server/spi=connectionsHttpClient/:add
/subsystem=keycloak-server/spi=connectionsHttpClient/provider=default/:add(enabled=true)
/subsystem=keycloak-server/spi=connectionsJpa/:add
/subsystem=keycloak-server/spi=connectionsJpa/provider=default/:add(properties={dataSource => "java:jboss/datasources/KeycloakDS",databaseSchema => "update"},enabled=true)
/subsystem=keycloak-server/spi=realmCache/:add
/subsystem=keycloak-server/spi=realmCache/provider=default/:add(enabled=true)
/subsystem=keycloak-server/spi=connectionsInfinispan/:add(default-provider=default)
/subsystem=keycloak-server/spi=connectionsInfinispan/provider=default/:add(properties={cacheContainer => "java:comp/env/infinispan/Keycloak"},enabled=true)

View file

@ -11,4 +11,22 @@ embed-server --server-config=standalone.xml
/subsystem=infinispan/cache-container=keycloak/local-cache=authorization:add()
/subsystem=infinispan/cache-container=keycloak/local-cache=authorization/eviction=EVICTION:add(max-entries=100,strategy=LRU)
/extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
/subsystem=keycloak-server:add(web-context=auth)
/subsystem=keycloak-server:add(web-context=auth,master-realm-name=master,scheduled-task-interval=900,providers=[classpath:${jboss.home.dir}/providers/*])
/subsystem=keycloak-server/theme=defaults/:add(dir=${jboss.home.dir}/themes,staticMaxAge=2592000,cacheTemplates=true,cacheThemes=true)
/subsystem=keycloak-server/spi=eventsStore/:add(default-provider=jpa)
/subsystem=keycloak-server/spi=eventsStore/provider=jpa/:add(properties={exclude-events => "[\"REFRESH_TOKEN\"]"},enabled=true)
/subsystem=keycloak-server/spi=realm/:add(default-provider=jpa)
/subsystem=keycloak-server/spi=user/:add(default-provider=jpa)
/subsystem=keycloak-server/spi=userCache/:add
/subsystem=keycloak-server/spi=userCache/provider=default/:add(enabled=true)
/subsystem=keycloak-server/spi=userSessionPersister/:add(default-provider=jpa)
/subsystem=keycloak-server/spi=authorizationPersister/:add(default-provider=jpa)
/subsystem=keycloak-server/spi=timer/:add(default-provider=basic)
/subsystem=keycloak-server/spi=connectionsHttpClient/:add
/subsystem=keycloak-server/spi=connectionsHttpClient/provider=default/:add(enabled=true)
/subsystem=keycloak-server/spi=connectionsJpa/:add
/subsystem=keycloak-server/spi=connectionsJpa/provider=default/:add(properties={dataSource => "java:jboss/datasources/KeycloakDS",databaseSchema => "update"},enabled=true)
/subsystem=keycloak-server/spi=realmCache/:add
/subsystem=keycloak-server/spi=realmCache/provider=default/:add(enabled=true)
/subsystem=keycloak-server/spi=connectionsInfinispan/:add(default-provider=default)
/subsystem=keycloak-server/spi=connectionsInfinispan/provider=default/:add(properties={cacheContainer => "java:comp/env/infinispan/Keycloak"},enabled=true)

View file

@ -72,6 +72,11 @@
<artifactId>twitter4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly.core</groupId>
<artifactId>wildfly-controller</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>

View file

@ -56,12 +56,17 @@ import java.io.*;
import java.net.URI;
import java.net.URL;
import java.util.*;
import org.jboss.dmr.ModelNode;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakApplication extends Application {
// This param name is defined again in Keycloak Server Subsystem class
// org.keycloak.subsystem.server.extension.KeycloakServerDeploymentProcessor. We have this value in
// two places to avoid dependency between Keycloak Subsystem and Keycloak Services module.
public static final String KEYCLOAK_CONFIG_PARAM_NAME = "org.keycloak.server-subsystem.Config";
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
@ -73,7 +78,7 @@ public class KeycloakApplication extends Application {
public KeycloakApplication(@Context ServletContext context, @Context Dispatcher dispatcher) {
try {
loadConfig();
loadConfig(context);
this.contextPath = context.getContextPath();
this.sessionFactory = createSessionFactory();
@ -209,12 +214,18 @@ public class KeycloakApplication extends Application {
return uriInfo.getBaseUriBuilder().replacePath(getContextPath()).build();
}
public static void loadConfig() {
public static void loadConfig(ServletContext context) {
try {
JsonNode node = null;
String dmrConfig = loadDmrConfig(context);
if (dmrConfig != null) {
node = new ObjectMapper().readTree(dmrConfig);
logger.loadingFrom("standalone.xml or domain.xml");
}
String configDir = System.getProperty("jboss.server.config.dir");
if (configDir != null) {
if (node == null && configDir != null) {
File f = new File(configDir + File.separator + "keycloak-server.json");
if (f.isFile()) {
logger.loadingFrom(f.getAbsolutePath());
@ -235,12 +246,23 @@ public class KeycloakApplication extends Application {
Config.init(new JsonConfigProvider(node, properties));
return;
} else {
throw new RuntimeException("Config 'keycloak-server.json' not found");
throw new RuntimeException("Keycloak config not found.");
}
} catch (IOException e) {
throw new RuntimeException("Failed to load config", e);
}
}
private static String loadDmrConfig(ServletContext context) {
String dmrConfig = context.getInitParameter(KEYCLOAK_CONFIG_PARAM_NAME);
if (dmrConfig == null) return null;
ModelNode dmrConfigNode = ModelNode.fromString(dmrConfig);
if (dmrConfigNode.asPropertyList().isEmpty()) return null;
// note that we need to resolve expressions BEFORE we convert to JSON
return dmrConfigNode.resolve().toJSONString(true);
}
public static KeycloakSessionFactory createSessionFactory() {
DefaultKeycloakSessionFactory factory = new DefaultKeycloakSessionFactory();

View file

@ -29,7 +29,7 @@ import java.net.Socket;
* <p>
* This SSLSocketFactory can only use truststore configured by TruststoreProvider after the ProviderFactory was
* initialized using standard Spi load / init mechanism. That will only happen if "truststore" provider is configured
* in keycloak-server.json.
* in standalone.xml or domain.xml.
* <p>
* If TruststoreProvider is not available this SSLSocketFactory will delegate all operations to javax.net.ssl.SSLSocketFactory.getDefault().
*

View file

@ -684,7 +684,7 @@ ldap.custom-user-ldap-filter.tooltip=Additional LDAP Filter for filtering search
search-scope=Search Scope
ldap.search-scope.tooltip=For one level, we search for users just in DNs specified by User DNs. For subtree, we search in whole of their subtree. See LDAP documentation for more details
use-truststore-spi=Use Truststore SPI
ldap.use-truststore-spi.tooltip=Specifies whether LDAP connection will use the truststore SPI with the truststore configured in keycloak-server.json. 'Always' means that it will always use it. 'Never' means that it won't use it. 'Only for ldaps' means that it will use if your connection URL use ldaps. Note even if keycloak-server.json is not configured, the default Java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used.
ldap.use-truststore-spi.tooltip=Specifies whether LDAP connection will use the truststore SPI with the truststore configured in standalone.xml/domain.xml. 'Always' means that it will always use it. 'Never' means that it won't use it. 'Only for ldaps' means that it will use if your connection URL use ldaps. Note even if standalone.xml/domain.xml is not configured, the default Java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used.
connection-pooling=Connection Pooling
ldap.connection-pooling.tooltip=Does Keycloak should use connection pooling for accessing LDAP server
ldap.pagination.tooltip=Does the LDAP server support pagination.

View file

@ -52,6 +52,14 @@
</build>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly.core</groupId>
<artifactId>wildfly-controller</artifactId>

View file

@ -0,0 +1,32 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.subsystem.server.attributes;
import org.jboss.as.controller.StringListAttributeDefinition;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class ModulesListAttributeBuilder extends StringListAttributeDefinition.Builder {
public ModulesListAttributeBuilder() {
super("modules");
setAllowExpression(true);
setAllowNull(true);
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.subsystem.server.attributes;
import org.jboss.as.controller.StringListAttributeDefinition;
import org.jboss.dmr.ModelNode;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class ProvidersListAttributeBuilder extends StringListAttributeDefinition.Builder {
public ProvidersListAttributeBuilder() {
super("providers");
ModelNode provider = new ModelNode();
provider.add("classpath:${jboss.home.dir}/providers/*");
this.defaultValue = provider;
setAllowExpression(true);
setAllowNull(true);
}
}

View file

@ -0,0 +1,262 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.subsystem.server.extension;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.dmr.ModelNode;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION;
import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.PROVIDERS;
import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.MASTER_REALM_NAME;
import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.SCHEDULED_TASK_INTERVAL;
import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.CACHE_TEMPLATES;
import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.CACHE_THEMES;
import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.DEFAULT;
import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.DIR;
import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.MODULES;
import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.STATIC_MAX_AGE;
import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.WELCOME_THEME;
/**
* Converts json representation of Keycloak config to DMR operations.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class JsonConfigConverter {
private static final List<String> NON_SPI_LIST = new ArrayList();
static {
NON_SPI_LIST.add("providers");
NON_SPI_LIST.add("admin");
NON_SPI_LIST.add("theme");
NON_SPI_LIST.add("scheduled");
}
/**
* Convert keycloak-server.json to DMR operations that write to standalone.xml
* or domain.xml.
*
* @param json The json representation of the config.
* @param subsysAddress The management model address of the keycloak-server subsystem.
* @return A list of DMR operations.
* @throws IOException If the json can not be parsed.
*/
public static List<ModelNode> convertJsonConfig(String json, PathAddress subsysAddress) throws IOException {
List<ModelNode> list = new ArrayList<>();
JsonNode root = new ObjectMapper().readTree(json);
list.add(masterRealmName(root, subsysAddress));
list.add(scheduledTaskInterval(root, subsysAddress));
list.add(providers(root, subsysAddress));
list.add(theme(root, subsysAddress.append(ThemeResourceDefinition.TAG_NAME,
ThemeResourceDefinition.RESOURCE_NAME)));
list.addAll(spis(root, subsysAddress));
return list;
}
private static ModelNode masterRealmName(JsonNode root, PathAddress addr) {
JsonNode targetNode = getNode(root, "admin", "realm");
String value = MASTER_REALM_NAME.getDefaultValue().asString();
if (targetNode != null) value = targetNode.asText(value);
ModelNode op = Util.createOperation(WRITE_ATTRIBUTE_OPERATION, addr);
op.get("name").set(MASTER_REALM_NAME.getName());
op.get("value").set(value);
return op;
}
private static ModelNode scheduledTaskInterval(JsonNode root, PathAddress addr) {
JsonNode targetNode = getNode(root, "scheduled", "interval");
Long value = SCHEDULED_TASK_INTERVAL.getDefaultValue().asLong();
if (targetNode != null) value = targetNode.asLong(value);
ModelNode op = Util.createOperation(WRITE_ATTRIBUTE_OPERATION, addr);
op.get("name").set(SCHEDULED_TASK_INTERVAL.getName());
op.get("value").set(value);
return op;
}
private static ModelNode providers(JsonNode root, PathAddress addr) {
JsonNode targetNode = getNode(root, "providers");
ModelNode value = PROVIDERS.getDefaultValue();
if (targetNode != null && targetNode.isArray()) {
value = new ModelNode();
for (JsonNode node : targetNode) {
value.add(node.asText());
}
}
ModelNode op = Util.createOperation(WRITE_ATTRIBUTE_OPERATION, addr);
op.get("name").set(PROVIDERS.getName());
op.get("value").set(value);
return op;
}
private static ModelNode theme(JsonNode root, PathAddress addr) {
JsonNode themeNode = getNode(root, "theme");
ModelNode op = Util.createAddOperation(addr);
JsonNode targetNode = getNode(themeNode, "staticMaxAge");
Long lValue = STATIC_MAX_AGE.getDefaultValue().asLong();
if (targetNode != null) lValue = targetNode.asLong(lValue);
op.get(STATIC_MAX_AGE.getName()).set(lValue);
targetNode = getNode(themeNode, "cacheTemplates");
Boolean bValue = CACHE_TEMPLATES.getDefaultValue().asBoolean();
if (targetNode != null) bValue = targetNode.asBoolean(bValue);
op.get(CACHE_TEMPLATES.getName()).set(bValue);
targetNode = getNode(themeNode, "cacheThemes");
bValue = CACHE_THEMES.getDefaultValue().asBoolean();
if (targetNode != null) bValue = targetNode.asBoolean(bValue);
op.get(CACHE_THEMES.getName()).set(bValue);
targetNode = getNode(themeNode, "folder", "dir");
String sValue = DIR.getDefaultValue().asString();
if (targetNode != null) sValue = targetNode.asText(sValue);
op.get(DIR.getName()).set(sValue);
targetNode = getNode(themeNode, "welcomeTheme");
if (targetNode != null) op.get(WELCOME_THEME.getName()).set(targetNode.asText());
targetNode = getNode(themeNode, "default");
if (targetNode != null) op.get(DEFAULT.getName()).set(targetNode.asText());
targetNode = getNode(themeNode, "module", "modules");
if (targetNode != null && targetNode.isArray()) {
op.get(MODULES.getName()).set(themeModules(targetNode));
}
return op;
}
private static ModelNode themeModules(JsonNode modulesNode) {
ModelNode modules = new ModelNode();
for (JsonNode node : modulesNode) {
modules.add(node.asText());
}
return modules;
}
private static Collection<ModelNode> spis(JsonNode root, PathAddress addr) {
List<ModelNode> spis = new ArrayList<>();
Iterator<String> spiIterator = root.fieldNames();
while (spiIterator.hasNext()) {
String spiName = spiIterator.next();
if (NON_SPI_LIST.contains(spiName)) continue;
PathAddress spiAddr = addr.append("spi", spiName);
spis.addAll(spi(root, spiAddr, spiName));
}
return spis;
}
private static List<ModelNode> spi(JsonNode root, PathAddress spiAddr, String spiName) {
List<ModelNode> spiAndProviders = new ArrayList<>();
ModelNode op = Util.createAddOperation(spiAddr);
spiAndProviders.add(op);
Iterator<String> providerIterator = root.get(spiName).fieldNames();
while (providerIterator.hasNext()) {
String providerName = providerIterator.next();
if ("provider".equals(providerName)) {
op.get(SpiResourceDefinition.DEFAULT_PROVIDER.getName()).set(getNode(root, spiName, "provider").asText());
} else {
PathAddress providerAddr = spiAddr.append("provider", providerName);
spiAndProviders.add(spiProvider(getNode(root, spiName, providerName), providerAddr));
}
}
return spiAndProviders;
}
private static ModelNode spiProvider(JsonNode providerNode, PathAddress providerAddr) {
ModelNode op = Util.createAddOperation(providerAddr);
ModelNode properties = new ModelNode();
Iterator<String> propNames = providerNode.fieldNames();
while (propNames.hasNext()) {
String propName = propNames.next();
if ("enabled".equals(propName)) {
op.get(ProviderResourceDefinition.ENABLED.getName()).set(providerNode.get(propName).asBoolean());
} else {
if (providerNode.get(propName).isArray()) {
properties.get(propName).set(makeArrayText(providerNode.get(propName)));
} else {
properties.get(propName).set(providerNode.get(propName).asText());
}
}
}
if (properties.isDefined() && !properties.asPropertyList().isEmpty()) {
op.get("properties").set(properties);
}
if (!op.hasDefined(ProviderResourceDefinition.ENABLED.getName())) {
op.get(ProviderResourceDefinition.ENABLED.getName()).set(ProviderResourceDefinition.ENABLED.getDefaultValue());
}
return op;
}
private static String makeArrayText(JsonNode arrayNode) {
StringBuilder builder = new StringBuilder("[");
Iterator<JsonNode> nodes = arrayNode.iterator();
while (nodes.hasNext()) {
JsonNode node = nodes.next();
builder.append("\"");
builder.append(node.asText());
builder.append("\"");
if (nodes.hasNext()) builder.append(",");
}
builder.append("]");
return builder.toString();
}
private static JsonNode getNode(JsonNode root, String... path) {
if (root == null) {
return null;
}
JsonNode n = root;
for (String p : path) {
n = n.get(p);
if (n == null) {
return null;
}
}
return n;
}
}

View file

@ -16,9 +16,15 @@
*/
package org.keycloak.subsystem.server.extension;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.dmr.ModelNode;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
import org.jboss.dmr.Property;
/**
* This service keeps track of the entire Keycloak management model so as to provide
* adapter configuration to each deployment at deploy time.
* configuration to the Keycloak Server.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
@ -27,6 +33,8 @@ public final class KeycloakAdapterConfigService {
static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService();
static final String DEPLOYMENT_NAME = "keycloak-server.war";
static ModelNode fullConfig = new ModelNode();
private String webContext;
@ -34,6 +42,127 @@ public final class KeycloakAdapterConfigService {
private KeycloakAdapterConfigService() {
}
void updateConfig(ModelNode operation, ModelNode config) {
PathAddress address = PathAddress.pathAddress(operation.get(ADDRESS));
address = address.subAddress(1); // remove root (subsystem=keycloak-server)
ModelNode newConfig = fullConfig.clone();
ModelNode subNode = newConfig;
for (PathElement pathElement : address) {
subNode = subNode.get(pathElement.getKey(), pathElement.getValue());
}
subNode.set(config.clone());
// remove undefined properties
for (Property prop : subNode.asPropertyList()) {
if (!prop.getValue().isDefined()) {
subNode.remove(prop.getName());
}
}
fullConfig = newConfig;
}
ModelNode getConfig() {
ModelNode copy = fullConfig.clone();
//System.out.println("******** BEFORE *************");
//System.out.println(copy);
//System.out.println("*****************************");
copy.remove("web-context");
massageScheduledTaskInterval(copy);
massageMasterRealm(copy);
massageTheme(copy);
massageSpis(copy);
//System.out.println("******** JSON *************");
//System.out.println(copy.resolve().toJSONString(false));
//System.out.println("**********************");
return copy;
}
// The "massage" methods rearrange the model so that everything will
// be where the Keycloak server's Config interface expects it to be.
private void massageScheduledTaskInterval(ModelNode copy) {
if (!copy.hasDefined("scheduled-task-intervale")) return;
ModelNode taskInterval = copy.remove("scheduled-task-interval");
copy.get("scheduled", "interval").set(taskInterval);
}
private void massageMasterRealm(ModelNode copy) {
if (!copy.hasDefined("master-realm-name")) return;
ModelNode master = copy.remove("master-realm-name");
copy.get("admin", "realm").set(master);
}
private void massageTheme(ModelNode copy) {
if (!copy.hasDefined("theme")) return;
if (!copy.get("theme").hasDefined("defaults")) return;
ModelNode themeDefaults = copy.get("theme", "defaults");
copy.get("theme").set(themeDefaults);
if (copy.has("theme", "dir")) {
ModelNode dir = copy.get("theme", "dir");
copy.get("theme", "folder", "dir").set(dir);
copy.get("theme").remove("dir");
}
if (copy.has("theme", "modules")) {
ModelNode modules = copy.get("theme").remove("modules");
copy.get("theme", "module", "modules").set(modules);
}
}
private void massageSpis(ModelNode copy) {
if (!copy.hasDefined("spi")) return;
ModelNode spis = copy.remove("spi");
for (Property prop : spis.asPropertyList()) {
ModelNode spi = prop.getValue();
if (spi.has("provider")) {
massageProviders(spi);
}
if (spi.has("default-provider")) {
ModelNode defaultProvider = spi.remove("default-provider");
spi.get("provider").set(defaultProvider);
}
copy.get(prop.getName()).set(spi);
}
}
private void massageProviders(ModelNode spi) {
if (!spi.hasDefined("provider")) return;
ModelNode providers = spi.remove("provider");
for (Property prop : providers.asPropertyList()) {
ModelNode provider = prop.getValue();
if (provider.has("properties")) {
massageProviderProps(provider);
}
spi.get(prop.getName()).set(provider);
}
}
private void massageProviderProps(ModelNode provider) {
if (!provider.hasDefined("properties")) return;
ModelNode providerProps = provider.remove("properties");
for (Property prop : providerProps.asPropertyList()) {
ModelNode value = prop.getValue();
if (isArray(value.asString().trim())) {
provider.get(prop.getName()).set(ModelNode.fromString(value.asString()).asList());
} else {
provider.get(prop.getName()).set(value);
}
}
}
private boolean isArray(String value) {
return value.startsWith("[") && value.endsWith("]");
}
void setWebContext(String webContext) {
this.webContext = webContext;
}

View file

@ -26,6 +26,7 @@ import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver;
import org.jboss.as.controller.parsing.ExtensionParsingContext;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import static org.keycloak.subsystem.server.logging.KeycloakLogger.ROOT_LOGGER;
@ -44,6 +45,10 @@ public class KeycloakExtension implements Extension {
private static final ResourceDefinition KEYCLOAK_SUBSYSTEM_RESOURCE = new KeycloakSubsystemDefinition();
private static final KeycloakSubsystemParser PARSER = new KeycloakSubsystemParser();
private static final ModelVersion MGMT_API_VERSION = ModelVersion.create(1,1,0);
static final ThemeResourceDefinition THEME_DEFINITION = new ThemeResourceDefinition();
static final SpiResourceDefinition SPI_DEFINITION = new SpiResourceDefinition();
static final ProviderResourceDefinition PROVIDER_DEFINITION = new ProviderResourceDefinition();
static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
@ -69,7 +74,11 @@ public class KeycloakExtension implements Extension {
ROOT_LOGGER.debug("Activating Keycloak Extension");
final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, MGMT_API_VERSION);
subsystem.registerSubsystemModel(KEYCLOAK_SUBSYSTEM_RESOURCE);
ManagementResourceRegistration subsystemRegistration = subsystem.registerSubsystemModel(KEYCLOAK_SUBSYSTEM_RESOURCE);
subsystemRegistration.registerSubModel(THEME_DEFINITION);
ManagementResourceRegistration spiRegistration = subsystemRegistration.registerSubModel(SPI_DEFINITION);
spiRegistration.registerSubModel(PROVIDER_DEFINITION);
subsystem.registerXMLElementWriter(PARSER);
}
}

View file

@ -16,41 +16,78 @@
*/
package org.keycloak.subsystem.server.extension;
import java.util.ArrayList;
import java.util.List;
import org.jboss.as.ee.component.EEModuleDescription;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.web.common.WarMetaData;
import org.jboss.metadata.javaee.spec.ParamValueMetaData;
import org.jboss.metadata.web.jboss.JBossWebMetaData;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceTarget;
/**
* DUP responsible for setting the web context of a Keycloak auth server.
* DUP responsible for setting the web context of a Keycloak auth server and
* passing the Keycloak configuration to the Keycloak server.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public class KeycloakServerDeploymentProcessor implements DeploymentUnitProcessor {
// This param name is defined again in Keycloak Services class
// org.keycloak.services.resources.KeycloakApplication. We have this value in
// two places to avoid dependency between Keycloak Subsystem and Keyclaok Services module.
public static final String KEYCLOAK_CONFIG_PARAM_NAME = "org.keycloak.server-subsystem.Config";
private static final ServiceName cacheContainerService = ServiceName.of("jboss", "infinispan", "keycloak");
@Override
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
KeycloakAdapterConfigService config = KeycloakAdapterConfigService.INSTANCE;
KeycloakAdapterConfigService configService = KeycloakAdapterConfigService.INSTANCE;
String deploymentName = deploymentUnit.getName();
if (!config.isKeycloakServerDeployment(deploymentName)) {
if (!configService.isKeycloakServerDeployment(deploymentName)) {
return;
}
final EEModuleDescription description = deploymentUnit.getAttachment(org.jboss.as.ee.component.Attachments.EE_MODULE_DESCRIPTION);
String webContext = config.getWebContext();
String webContext = configService.getWebContext();
if (webContext == null) {
throw new DeploymentUnitProcessingException("Can't determine web context/module for Keycloak Server");
}
description.setModuleName(webContext);
addInfinispanCaches(phaseContext);
addConfiguration(deploymentUnit, configService);
}
private void addConfiguration(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService configService) throws DeploymentUnitProcessingException {
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
if (warMetaData == null) {
throw new DeploymentUnitProcessingException("WarMetaData not found for KeycloakServer.");
}
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
if (webMetaData == null) {
webMetaData = new JBossWebMetaData();
warMetaData.setMergedJBossWebMetaData(webMetaData);
}
List<ParamValueMetaData> contextParams = webMetaData.getContextParams();
if (contextParams == null) {
contextParams = new ArrayList<ParamValueMetaData>();
}
ParamValueMetaData param = new ParamValueMetaData();
param.setParamName(KEYCLOAK_CONFIG_PARAM_NAME);
param.setParamValue(configService.getConfig().toString());
contextParams.add(param);
webMetaData.setContextParams(contextParams);
}
private void addInfinispanCaches(DeploymentPhaseContext context) {

View file

@ -62,9 +62,10 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
}, OperationContext.Stage.RUNTIME);
}
protected void populateModel(final OperationContext context, final ModelNode operation, final Resource resource) throws OperationFailedException {
@Override
protected void populateModel(final OperationContext context, final ModelNode operation, final Resource resource) throws OperationFailedException {
ModelNode model = resource.getModel();
// set attribute values from parsed model
for (AttributeDefinition attrDef : ALL_ATTRIBUTES) {
attrDef.validateAndSet(operation, model);
@ -89,5 +90,7 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
ServerUtil serverUtil = new ServerUtil(operation);
serverUtil.addStepToUploadServerWar(context);
KeycloakAdapterConfigService.INSTANCE.setWebContext(webContext);
KeycloakAdapterConfigService.INSTANCE.updateConfig(operation, model);
}
}

View file

@ -29,6 +29,8 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.as.controller.StringListAttributeDefinition;
import org.keycloak.subsystem.server.attributes.ProvidersListAttributeBuilder;
/**
* Definition of subsystem=keycloak-server.
@ -44,15 +46,34 @@ public class KeycloakSubsystemDefinition extends SimpleResourceDefinition {
.setRestartAllServices()
.build();
static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
static final StringListAttributeDefinition PROVIDERS = new ProvidersListAttributeBuilder().build();
static final SimpleAttributeDefinition MASTER_REALM_NAME =
new SimpleAttributeDefinitionBuilder("master-realm-name", ModelType.STRING, true)
.setAllowExpression(true)
.setDefaultValue(new ModelNode("master"))
.setRestartAllServices()
.build();
static final SimpleAttributeDefinition SCHEDULED_TASK_INTERVAL =
new SimpleAttributeDefinitionBuilder("scheduled-task-interval", ModelType.LONG, true)
.setAllowExpression(true)
.setDefaultValue(new ModelNode("900"))
.setRestartAllServices()
.build();
static final List<AttributeDefinition> ALL_ATTRIBUTES = new ArrayList<AttributeDefinition>();
static {
ALL_ATTRIBUTES.add(WEB_CONTEXT);
ALL_ATTRIBUTES.add(PROVIDERS);
ALL_ATTRIBUTES.add(MASTER_REALM_NAME);
ALL_ATTRIBUTES.add(SCHEDULED_TASK_INTERVAL);
}
private static final Map<String, SimpleAttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, SimpleAttributeDefinition>();
private static final Map<String, AttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, AttributeDefinition>();
static {
for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
for (AttributeDefinition def : ALL_ATTRIBUTES) {
DEFINITION_LOOKUP.put(def.getXmlName(), def);
}
}
@ -71,6 +92,7 @@ public class KeycloakSubsystemDefinition extends SimpleResourceDefinition {
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
super.registerOperations(resourceRegistration);
resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
resourceRegistration.registerOperationHandler(MigrateJsonOperation.DEFINITION, new MigrateJsonOperation());
}
@Override
@ -81,7 +103,7 @@ public class KeycloakSubsystemDefinition extends SimpleResourceDefinition {
}
}
public static SimpleAttributeDefinition lookup(String name) {
public static AttributeDefinition lookup(String name) {
return DEFINITION_LOOKUP.get(name);
}
}

View file

@ -29,9 +29,26 @@ import org.jboss.staxmapper.XMLExtendedStreamWriter;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import java.util.List;
import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.PropertiesAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.dmr.Property;
import static org.keycloak.subsystem.server.extension.KeycloakExtension.PATH_SUBSYSTEM;
import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.WEB_CONTEXT;
import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.PROVIDERS;
import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.MASTER_REALM_NAME;
import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.SCHEDULED_TASK_INTERVAL;
import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.MODULES;
import static org.keycloak.subsystem.server.extension.SpiResourceDefinition.DEFAULT_PROVIDER;
import static org.keycloak.subsystem.server.extension.ProviderResourceDefinition.ENABLED;
import static org.keycloak.subsystem.server.extension.ProviderResourceDefinition.PROPERTIES;
/**
* The subsystem parser, which uses stax to read and write to and from xml
@ -51,12 +68,116 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
if (reader.getLocalName().equals(WEB_CONTEXT.getXmlName())) {
WEB_CONTEXT.parseAndSetParameter(reader.getElementText(), addKeycloakSub, reader);
} else if (reader.getLocalName().equals(PROVIDERS.getXmlName())) {
readProviders(reader, addKeycloakSub);
} else if (reader.getLocalName().equals(MASTER_REALM_NAME.getXmlName())) {
MASTER_REALM_NAME.parseAndSetParameter(reader.getElementText(), addKeycloakSub, reader);
} else if (reader.getLocalName().equals(SCHEDULED_TASK_INTERVAL.getXmlName())) {
SCHEDULED_TASK_INTERVAL.parseAndSetParameter(reader.getElementText(), addKeycloakSub, reader);
} else if (reader.getLocalName().equals(ThemeResourceDefinition.TAG_NAME)) {
readTheme(list, reader);
} else if (reader.getLocalName().equals(SpiResourceDefinition.TAG_NAME)) {
readSpi(list, reader);
} else {
throw new XMLStreamException("Unknown keycloak-server subsystem tag: " + reader.getLocalName());
}
}
}
private void readProviders(final XMLExtendedStreamReader reader, ModelNode addKeycloakSub) throws XMLStreamException {
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
PROVIDERS.parseAndAddParameterElement(reader.getElementText(),addKeycloakSub, reader);
}
}
private void readTheme(final List<ModelNode> list, final XMLExtendedStreamReader reader) throws XMLStreamException {
ModelNode addThemeDefaults = new ModelNode();
addThemeDefaults.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME),
PathElement.pathElement(ThemeResourceDefinition.TAG_NAME, ThemeResourceDefinition.RESOURCE_NAME));
addThemeDefaults.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
list.add(addThemeDefaults);
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
String tagName = reader.getLocalName();
if (MODULES.getName().equals(tagName)) {
readModules(reader, addThemeDefaults);
continue;
}
SimpleAttributeDefinition def = KeycloakExtension.THEME_DEFINITION.lookup(tagName);
if (def == null) throw new XMLStreamException("Unknown theme tag " + tagName);
def.parseAndSetParameter(reader.getElementText(), addThemeDefaults, reader);
}
}
private void readModules(final XMLExtendedStreamReader reader, ModelNode addThemeDefaults) throws XMLStreamException {
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
MODULES.parseAndAddParameterElement(reader.getElementText(),addThemeDefaults, reader);
}
}
private void readSpi(final List<ModelNode> list, final XMLExtendedStreamReader reader) throws XMLStreamException {
String spiName = ParseUtils.requireAttributes(reader, "name")[0];
ModelNode addSpi = new ModelNode();
addSpi.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME),
PathElement.pathElement(SpiResourceDefinition.TAG_NAME, spiName));
addSpi.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
list.add(addSpi);
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
if (reader.getLocalName().equals(DEFAULT_PROVIDER.getXmlName())) {
DEFAULT_PROVIDER.parseAndSetParameter(reader.getElementText(), addSpi, reader);
} else if (reader.getLocalName().equals(ProviderResourceDefinition.TAG_NAME)) {
readProvider(list, spiName, reader);
}
}
}
private void readProvider(final List<ModelNode> list, String spiName, final XMLExtendedStreamReader reader) throws XMLStreamException {
String[] attributes = ParseUtils.requireAttributes(reader, "name", ENABLED.getXmlName());
String providerName = attributes[0];
String enabled = attributes[1];
ModelNode addProvider = new ModelNode();
addProvider.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME),
PathElement.pathElement(SpiResourceDefinition.TAG_NAME, spiName),
PathElement.pathElement(ProviderResourceDefinition.TAG_NAME, providerName));
addProvider.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
addProvider.get(ENABLED.getName()).set(Boolean.valueOf(enabled));
list.add(addProvider);
while (nextTag(reader) != END_ELEMENT) {
if (reader.getLocalName().equals(PROPERTIES.getXmlName())) {
readProperties(PROPERTIES, addProvider, reader);
}
}
}
private void readProperties(final PropertiesAttributeDefinition attrDef, ModelNode addOp, final XMLExtendedStreamReader reader) throws XMLStreamException {
while (nextTag(reader) != END_ELEMENT) {
int attrCount = reader.getAttributeCount();
if (attrCount != 2) throw new XMLStreamException("Property must have only two attributes");
String name = "";
String value = "";
for (int i=0 ; i < 2; i++) {
String attrName = reader.getAttributeLocalName(i);
String attrValue = reader.getAttributeValue(i);
if (attrName.equals("name")) {
name = attrValue;
} else if (attrName.equals("value")) {
value = attrValue;
} else {
throw new XMLStreamException("Property can only have attributes named 'name' and 'value'");
}
}
attrDef.parseAndAddParameterElement(name, value, addOp, reader);
nextTag(reader);
}
}
// used for debugging
private int nextTag(XMLExtendedStreamReader reader) throws XMLStreamException {
return reader.nextTag();
@ -69,9 +190,64 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
public void writeContent(final XMLExtendedStreamWriter writer, final SubsystemMarshallingContext context) throws XMLStreamException {
context.startSubsystemElement(KeycloakExtension.NAMESPACE, false);
writeWebContext(writer, context);
writeList(writer, context.getModelNode(), PROVIDERS, "provider");
writeAdmin(writer, context);
writeScheduledTaskInterval(writer, context);
writeThemeDefaults(writer, context);
writeSpis(writer, context);
writer.writeEndElement();
}
private void writeThemeDefaults(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
if (!context.getModelNode().get(ThemeResourceDefinition.TAG_NAME).isDefined()) {
return;
}
writer.writeStartElement(ThemeResourceDefinition.TAG_NAME);
ModelNode themeElements = context.getModelNode().get(ThemeResourceDefinition.TAG_NAME, ThemeResourceDefinition.RESOURCE_NAME);
for (AttributeDefinition def : ThemeResourceDefinition.ALL_ATTRIBUTES) {
if (themeElements.hasDefined(def.getName())) {
if (def == MODULES) {
ModelNode themeContext = context.getModelNode().get("theme", "defaults");
writeList(writer, themeContext, def, "module");
} else {
def.marshallAsElement(themeElements, writer);
}
}
}
writer.writeEndElement();
}
private void writeSpis(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
if (!context.getModelNode().get(SpiResourceDefinition.TAG_NAME).isDefined()) {
return;
}
for (Property spi : context.getModelNode().get(SpiResourceDefinition.TAG_NAME).asPropertyList()) {
writer.writeStartElement(SpiResourceDefinition.TAG_NAME);
writer.writeAttribute("name", spi.getName());
ModelNode spiElements = spi.getValue();
DEFAULT_PROVIDER.marshallAsElement(spiElements, writer);
writeProviders(writer, spiElements);
writer.writeEndElement();
}
}
private void writeProviders(XMLExtendedStreamWriter writer, ModelNode spiElements) throws XMLStreamException {
if (!spiElements.get(ProviderResourceDefinition.TAG_NAME).isDefined()) {
return;
}
for (Property provider : spiElements.get(ProviderResourceDefinition.TAG_NAME).asPropertyList()) {
writer.writeStartElement(ProviderResourceDefinition.TAG_NAME);
writer.writeAttribute("name", provider.getName());
ModelNode providerElements = provider.getValue();
ENABLED.marshallAsAttribute(providerElements, writer);
PROPERTIES.marshallAsElement(providerElements, writer);
writer.writeEndElement();
}
}
private void writeWebContext(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
if (!context.getModelNode().get(WEB_CONTEXT.getName()).isDefined()) {
return;
@ -79,4 +255,36 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
WEB_CONTEXT.marshallAsElement(context.getModelNode(), writer);
}
}
private void writeAdmin(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
if (!context.getModelNode().get(MASTER_REALM_NAME.getName()).isDefined()) {
return;
}
MASTER_REALM_NAME.marshallAsElement(context.getModelNode(), writer);
}
private void writeScheduledTaskInterval(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
if (!context.getModelNode().get(SCHEDULED_TASK_INTERVAL.getName()).isDefined()) {
return;
}
SCHEDULED_TASK_INTERVAL.marshallAsElement(context.getModelNode(), writer);
}
private void writeList(XMLExtendedStreamWriter writer, ModelNode context, AttributeDefinition def, String elementName) throws XMLStreamException {
if (!context.get(def.getName()).isDefined()) {
return;
}
writer.writeStartElement(def.getXmlName());
ModelNode modules = context.get(def.getName());
for (ModelNode module : modules.asList()) {
writer.writeStartElement(elementName);
writer.writeCharacters(module.asString());
writer.writeEndElement();
}
writer.writeEndElement();
}
}

View file

@ -17,7 +17,6 @@
package org.keycloak.subsystem.server.extension;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinition;
import java.util.List;
import org.jboss.as.controller.ModelOnlyWriteAttributeHandler;
@ -33,7 +32,7 @@ import org.jboss.dmr.ModelNode;
*/
public class KeycloakSubsystemWriteAttributeHandler extends ModelOnlyWriteAttributeHandler { //extends ReloadRequiredWriteAttributeHandler {
public KeycloakSubsystemWriteAttributeHandler(List<SimpleAttributeDefinition> definitions) {
public KeycloakSubsystemWriteAttributeHandler(List<AttributeDefinition> definitions) {
this(definitions.toArray(new AttributeDefinition[definitions.size()]));
}
@ -59,7 +58,7 @@ public class KeycloakSubsystemWriteAttributeHandler extends ModelOnlyWriteAttrib
}
private boolean attribNotChanging(String attributeName, ModelNode newValue, ModelNode oldValue) {
SimpleAttributeDefinition attribDef = KeycloakSubsystemDefinition.lookup(attributeName);
AttributeDefinition attribDef = KeycloakSubsystemDefinition.lookup(attributeName);
if (!oldValue.isDefined()) {
oldValue = attribDef.getDefaultValue();
}

View file

@ -0,0 +1,97 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.subsystem.server.extension;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationDefinition;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationStepHandler;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
/**
* This operation provides a migration path from keycloak-server.json to
* standalone.xml or domain.xml.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class MigrateJsonOperation implements OperationStepHandler {
public static final String OPERATION_NAME = "migrate-json";
private static final String CONFIG_DIR = System.getProperty("jboss.server.config.dir");
private static final Path DEFAULT_CONFIG_FILE = Paths.get(CONFIG_DIR, "keycloak-server.json");
private static final AttributeDefinition FILE_ATTRIBUTE = SimpleAttributeDefinitionBuilder.create("file", ModelType.BYTES, true).build();
public static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OPERATION_NAME, KeycloakExtension.getResourceDescriptionResolver())
.setRuntimeOnly()
.setReadOnly()
.setReplyType(ModelType.STRING)
.setParameters(FILE_ATTRIBUTE)
.build();
private String localConfig() throws IOException {
if (Files.notExists(DEFAULT_CONFIG_FILE)) return null;
return new String(Files.readAllBytes(DEFAULT_CONFIG_FILE));
}
private String readConfig(ModelNode operation) throws IOException {
ModelNode file = operation.get(FILE_ATTRIBUTE.getName());
if (file.isDefined() && file.asBytes().length > 0) {
return new String(file.asBytes());
}
String localConfig = localConfig();
if (localConfig != null) return localConfig;
throw new IOException("Can not find json file to migrate");
}
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
List<ModelNode> ops = null;
try {
PathAddress currentAddr = context.getCurrentAddress();
ops = JsonConfigConverter.convertJsonConfig(readConfig(operation), currentAddr);
} catch (IOException ioe) {
throw new OperationFailedException(ioe);
}
for (ModelNode op : ops) {
PathAddress addr = PathAddress.pathAddress(op.get(ADDRESS));
String opName = op.get(OP).asString();
context.addStep(op,
context.getRootResourceRegistration().getOperationHandler(addr, opName),
OperationContext.Stage.MODEL);
}
context.completeStep(OperationContext.RollbackHandler.NOOP_ROLLBACK_HANDLER);
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.subsystem.server.extension;
import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.OperationFailedException;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import org.jboss.dmr.ModelNode;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class ProviderResourceAddHandler extends AbstractAddStepHandler {
public static ProviderResourceAddHandler INSTANCE = new ProviderResourceAddHandler();
private ProviderResourceAddHandler() {
}
@Override
protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
// TODO: localize exception. get id number
if (!operation.get(OP).asString().equals(ADD)) {
throw new OperationFailedException("Unexpected operation for add SPI. operation=" + operation.toString());
}
ProviderResourceDefinition.ENABLED.validateAndSet(operation, model);
ProviderResourceDefinition.PROPERTIES.validateAndSet(operation, model);
KeycloakAdapterConfigService.INSTANCE.updateConfig(operation, model);
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.subsystem.server.extension;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.PropertiesAttributeDefinition;
import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class ProviderResourceDefinition extends SimpleResourceDefinition {
public static final String TAG_NAME = "provider";
protected static final SimpleAttributeDefinition ENABLED =
new SimpleAttributeDefinitionBuilder("enabled", ModelType.BOOLEAN, true)
.setAllowExpression(true)
.setDefaultValue(new ModelNode(true))
.setAllowNull(false)
.setRestartAllServices()
.build();
static final PropertiesAttributeDefinition PROPERTIES =
new PropertiesAttributeDefinition.Builder("properties", true)
.setRestartAllServices()
.build();
protected static final ReloadRequiredWriteAttributeHandler WRITE_ATTR_HANDLER = new ReloadRequiredWriteAttributeHandler(ENABLED);
protected ProviderResourceDefinition() {
super(PathElement.pathElement(TAG_NAME),
KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
ProviderResourceAddHandler.INSTANCE,
ProviderResourceRemoveHandler.INSTANCE
);
}
@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
super.registerAttributes(resourceRegistration);
resourceRegistration.registerReadWriteAttribute(ENABLED, null, WRITE_ATTR_HANDLER);
resourceRegistration.registerReadWriteAttribute(PROPERTIES, null, WRITE_ATTR_HANDLER);
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.subsystem.server.extension;
import org.jboss.as.controller.AbstractRemoveStepHandler;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class ProviderResourceRemoveHandler extends AbstractRemoveStepHandler {
public static ProviderResourceRemoveHandler INSTANCE = new ProviderResourceRemoveHandler();
private ProviderResourceRemoveHandler() {}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.subsystem.server.extension;
import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.OperationFailedException;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import org.jboss.dmr.ModelNode;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class SpiResourceAddHandler extends AbstractAddStepHandler {
public static SpiResourceAddHandler INSTANCE = new SpiResourceAddHandler();
private SpiResourceAddHandler() {}
@Override
protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
// TODO: localize exception. get id number
if (!operation.get(OP).asString().equals(ADD)) {
throw new OperationFailedException("Unexpected operation for add SPI. operation=" + operation.toString());
}
SpiResourceDefinition.DEFAULT_PROVIDER.validateAndSet(operation, model);
KeycloakAdapterConfigService.INSTANCE.updateConfig(operation, model);
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.subsystem.server.extension;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.dmr.ModelType;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class SpiResourceDefinition extends SimpleResourceDefinition {
public static final String TAG_NAME = "spi";
protected static final SimpleAttributeDefinition DEFAULT_PROVIDER =
new SimpleAttributeDefinitionBuilder("default-provider", ModelType.STRING, true)
.setAllowExpression(true)
.setRestartAllServices()
.build();
protected static final ReloadRequiredWriteAttributeHandler WRITE_ATTR_HANDLER = new ReloadRequiredWriteAttributeHandler(DEFAULT_PROVIDER);
protected SpiResourceDefinition() {
super(PathElement.pathElement(TAG_NAME),
KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
SpiResourceAddHandler.INSTANCE,
SpiResourceRemoveHandler.INSTANCE
);
}
@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
super.registerAttributes(resourceRegistration);
resourceRegistration.registerReadWriteAttribute(DEFAULT_PROVIDER, null, WRITE_ATTR_HANDLER);
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.subsystem.server.extension;
import org.jboss.as.controller.AbstractRemoveStepHandler;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class SpiResourceRemoveHandler extends AbstractRemoveStepHandler {
public static SpiResourceRemoveHandler INSTANCE = new SpiResourceRemoveHandler();
private SpiResourceRemoveHandler() {}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.subsystem.server.extension;
import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import org.jboss.dmr.ModelNode;
import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.ALL_ATTRIBUTES;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class ThemeResourceAddHandler extends AbstractAddStepHandler {
public static ThemeResourceAddHandler INSTANCE = new ThemeResourceAddHandler();
private ThemeResourceAddHandler() {}
@Override
protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
// TODO: localize exception. get id number
if (!operation.get(OP).asString().equals(ADD)) {
throw new OperationFailedException("Unexpected operation for add Theme. operation=" + operation.toString());
}
PathAddress address = PathAddress.pathAddress(operation.get(ADDRESS));
PathElement last = address.getLastElement();
if (!last.getValue().equals(ThemeResourceDefinition.RESOURCE_NAME)) {
throw new OperationFailedException("Theme resource with name " + last.getValue() + " not allowed.");
}
for (AttributeDefinition def : ALL_ATTRIBUTES) {
def.validateAndSet(operation, model);
}
KeycloakAdapterConfigService.INSTANCE.updateConfig(operation, model);
}
}

View file

@ -0,0 +1,135 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.subsystem.server.extension;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.StringListAttributeDefinition;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.keycloak.subsystem.server.attributes.ModulesListAttributeBuilder;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class ThemeResourceDefinition extends SimpleResourceDefinition {
public static final String TAG_NAME = "theme";
// This is the internal name of the singleton resource
public static final String RESOURCE_NAME = "defaults";
// NOTE: All attributes must be SimpleAttributeDefinition. If that needs to
// change then refactor starting with lookup() method below.
static final SimpleAttributeDefinition STATIC_MAX_AGE =
new SimpleAttributeDefinitionBuilder("staticMaxAge", ModelType.LONG, true)
.setAllowExpression(true)
.setDefaultValue(new ModelNode("2592000"))
.setRestartAllServices()
.build();
static final SimpleAttributeDefinition CACHE_THEMES =
new SimpleAttributeDefinitionBuilder("cacheThemes", ModelType.BOOLEAN, true)
.setAllowExpression(true)
.setDefaultValue(new ModelNode(true))
.setAllowNull(false)
.setRestartAllServices()
.build();
static final SimpleAttributeDefinition CACHE_TEMPLATES =
new SimpleAttributeDefinitionBuilder("cacheTemplates", ModelType.BOOLEAN, true)
.setAllowExpression(true)
.setDefaultValue(new ModelNode(true))
.setAllowNull(false)
.setRestartAllServices()
.build();
static final SimpleAttributeDefinition WELCOME_THEME =
new SimpleAttributeDefinitionBuilder("welcomeTheme", ModelType.STRING, true)
.setAllowExpression(true)
.setRestartAllServices()
.build();
static final SimpleAttributeDefinition DEFAULT =
new SimpleAttributeDefinitionBuilder("default", ModelType.STRING, true)
.setAllowExpression(true)
.setRestartAllServices()
.build();
static final SimpleAttributeDefinition DIR =
new SimpleAttributeDefinitionBuilder("dir", ModelType.STRING, true)
.setAllowExpression(true)
.setDefaultValue(new ModelNode("${jboss.home.dir}/themes"))
.setRestartAllServices()
.build();
static final StringListAttributeDefinition MODULES = new ModulesListAttributeBuilder().build();
static final List<AttributeDefinition> ALL_ATTRIBUTES = new ArrayList<>();
static {
ALL_ATTRIBUTES.add(STATIC_MAX_AGE);
ALL_ATTRIBUTES.add(CACHE_THEMES);
ALL_ATTRIBUTES.add(CACHE_TEMPLATES);
ALL_ATTRIBUTES.add(WELCOME_THEME);
ALL_ATTRIBUTES.add(DEFAULT);
ALL_ATTRIBUTES.add(DIR);
ALL_ATTRIBUTES.add(MODULES);
}
private static final Map<String, AttributeDefinition> DEFINITION_LOOKUP = new HashMap<>();
static {
for (AttributeDefinition def : ALL_ATTRIBUTES) {
DEFINITION_LOOKUP.put(def.getXmlName(), def);
}
}
protected static final ReloadRequiredWriteAttributeHandler WRITE_ATTR_HANDLER = new ReloadRequiredWriteAttributeHandler(ALL_ATTRIBUTES);
protected ThemeResourceDefinition() {
super(PathElement.pathElement(TAG_NAME),
KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
ThemeResourceAddHandler.INSTANCE,
ThemeResourceRemoveHandler.INSTANCE
);
}
@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
super.registerAttributes(resourceRegistration);
for (AttributeDefinition def : ALL_ATTRIBUTES) {
resourceRegistration.registerReadWriteAttribute(def, null, WRITE_ATTR_HANDLER);
}
}
public static SimpleAttributeDefinition lookup(String name) {
return (SimpleAttributeDefinition)DEFINITION_LOOKUP.get(name);
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.subsystem.server.extension;
import org.jboss.as.controller.AbstractRemoveStepHandler;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class ThemeResourceRemoveHandler extends AbstractRemoveStepHandler {
public static ThemeResourceRemoveHandler INSTANCE = new ThemeResourceRemoveHandler();
private ThemeResourceRemoveHandler() {}
}

View file

@ -15,7 +15,38 @@
# limitations under the License.
#
keycloak-server.migrate-json=Migrate keycloak-server.json to standalone.xml or domain.xml
keycloak-server.migrate-json.file=Optional local path to keycloak-server.json
keycloak-server.subsystem=Keycloak subsystem
keycloak-server.subsystem.add=Operation Adds Keycloak subsystem
keycloak-server.subsystem.remove=Operation removes Keycloak subsystem
keycloak-server.subsystem.web-context=Web context where Keycloak server is bound. Default value is 'auth'.
keycloak-server.subsystem.providers=Paths to search for Keycloak provider jars.
keycloak-server.subsystem.master-realm-name=The name of the master admin realm.
keycloak-server.subsystem.scheduled-task-interval=The interval (in seconds) to run scheduled tasks.
keycloak-server.subsystem.spi=A Service Provider type.
keycloak-server.subsystem.theme=Theme configuration properties.
keycloak-server.theme=Theme configuration properties.
keycloak-server.theme.add=Add the theme config properties.
keycloak-server.theme.remove=Remove the theme config properties.
keycloak-server.theme.staticMaxAge=Foo
keycloak-server.theme.cacheThemes=foo
keycloak-server.theme.cacheTemplates=foo
keycloak-server.theme.welcomeTheme=foo
keycloak-server.theme.default=foo
keycloak-server.theme.dir=Directory where themes can be located.
keycloak-server.theme.modules=List of modules containing themes.
keycloak-server.spi=A Service Provider type.
keycloak-server.spi.add=Add an spi.
keycloak-server.spi.remove=Remove an spi.
keycloak-server.spi.default-provider=The default provider for the spi.
keycloak-server.spi.provider=A provider for the spi.
keycloak-server.provider=A provider for the spi.
keycloak-server.provider.add=Add a provider.
keycloak-server.provider.remove=Remove a provider.
keycloak-server.provider.enabled=Enable or disable the provider.
keycloak-server.provider.properties=The properties for the provider.

View file

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:jboss:domain:keycloak-server:1.1"
@ -35,8 +35,73 @@
]]>
</xs:documentation>
</xs:annotation>
<xs:choice minOccurs="0" maxOccurs="1">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="web-context" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="providers" type="providersLocationType" minOccurs="0" maxOccurs="1"/>
<xs:element name="master-realm-name" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="scheduled-task-interval" type="xs:long" minOccurs="0" maxOccurs="1"/>
<xs:element name="theme" type="themeDefaultsType" minOccurs="0" maxOccurs="1"/>
<xs:element name="spi" type="spiType" minOccurs="0" maxOccurs="unbounded"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="providersLocationType">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="provider" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="themeDefaultsType">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="staticMaxAge" type="xs:long" minOccurs="0" maxOccurs="1"/>
<xs:element name="cacheThemes" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="cacheTemplates" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="welcomeTheme" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="default" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="dir" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="modules" type="modulesType" minOccurs="0" maxOccurs="1"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="modulesType">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="module" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="spiType">
<xs:sequence>
<xs:element name="default-provider" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="provider" type="providerType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="name" use="required"/>
</xs:complexType>
<xs:complexType name="providerType">
<xs:sequence>
<xs:element name="properties" type="properties" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute name="name" use="required"/>
<xs:attribute name="enabled" use="required"/>
</xs:complexType>
<xs:complexType name="properties">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="property" type="propertyType" maxOccurs="unbounded"/>
<xs:element name="list" type="listType" maxOccurs="unbounded"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="propertyType">
<xs:attribute name="name" use="required"/>
<xs:attribute name="value" use="optional"/>
</xs:complexType>
<xs:complexType name="listType">
<xs:sequence>
<xs:element name="value" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="name" use="required"/>
</xs:complexType>
</xs:schema>

View file

@ -1,25 +1,84 @@
<?xml version='1.0' encoding='UTF-8'?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!-- Template used by WildFly build when directed to include Keycloak subsystem in a configuration. -->
<config>
<extension-module>org.keycloak.keycloak-server-subsystem</extension-module>
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
<web-context>auth</web-context>
</subsystem>
<extension-module>org.keycloak.keycloak-server-subsystem</extension-module>
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
<web-context>auth</web-context>
<providers>
<provider>classpath:${jboss.home.dir}/providers/*</provider>
</providers>
<master-realm-name>master</master-realm-name>
<scheduled-task-interval>900</scheduled-task-interval>
<theme>
<staticMaxAge>2592000</staticMaxAge>
<cacheThemes>true</cacheThemes>
<cacheTemplates>true</cacheTemplates>
<dir>${jboss.home.dir}/themes</dir>
</theme>
<spi name="eventsStore">
<default-provider>jpa</default-provider>
<provider name="jpa" enabled="true">
<properties>
<property name="exclude-events" value="[&quot;REFRESH_TOKEN&quot;]"/>
</properties>
</provider>
</spi>
<spi name="realm">
<default-provider>jpa</default-provider>
</spi>
<spi name="user">
<default-provider>jpa</default-provider>
</spi>
<spi name="userCache">
<provider name="default" enabled="true"/>
</spi>
<spi name="userSessionPersister">
<default-provider>jpa</default-provider>
</spi>
<spi name="authorizationPersister">
<default-provider>jpa</default-provider>
</spi>
<spi name="timer">
<default-provider>basic</default-provider>
</spi>
<spi name="connectionsHttpClient">
<provider name="default" enabled="true"/>
</spi>
<spi name="connectionsJpa">
<provider name="default" enabled="true">
<properties>
<property name="dataSource" value="java:jboss/datasources/KeycloakDS"/>
<property name="databaseSchema" value="update"/>
</properties>
</provider>
</spi>
<spi name="realmCache">
<provider name="default" enabled="true"/>
</spi>
<spi name="connectionsInfinispan">
<default-provider>default</default-provider>
<provider name="default" enabled="true">
<properties>
<property name="cacheContainer" value="java:comp/env/infinispan/Keycloak"/>
</properties>
</provider>
</spi>
</subsystem>
</config>

View file

@ -0,0 +1,446 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.subsystem.server.extension;
import java.util.ArrayList;
import java.util.List;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.dmr.ModelNode;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class JsonConfigConverterTestCase {
private final PathElement domainRoot = PathElement.pathElement("profile", "auth-server-clustered");
private final PathAddress domainAddress = PathAddress.pathAddress(domainRoot)
.append(KeycloakExtension.PATH_SUBSYSTEM);
private final PathAddress standaloneAddress = PathAddress.pathAddress(KeycloakExtension.PATH_SUBSYSTEM);
@Test
public void testConvertJsonStandaloneWithModules() throws Exception {
String json = basicJsonConfig(true);
List<ModelNode> expResult = expectedOperations(true, false);
List<ModelNode> result = JsonConfigConverter.convertJsonConfig(json, standaloneAddress);
assertEquals(expResult, result);
}
@Test
public void testConvertJsonStandaloneWithoutModules() throws Exception {
String json = basicJsonConfig(false);
List<ModelNode> expResult = expectedOperations(false, false);
List<ModelNode> result = JsonConfigConverter.convertJsonConfig(json, standaloneAddress);
assertEquals(expResult, result);
}
@Test
public void testConvertJsonDomainWithModules() throws Exception {
String json = basicJsonConfig(true);
List<ModelNode> expResult = expectedOperations(true, true);
List<ModelNode> result = JsonConfigConverter.convertJsonConfig(json, domainAddress);
assertEquals(expResult, result);
}
@Test
public void testConvertJsonDomainWithoutModules() throws Exception {
String json = basicJsonConfig(false);
List<ModelNode> expResult = expectedOperations(false, true);
List<ModelNode> result = JsonConfigConverter.convertJsonConfig(json, domainAddress);
assertEquals(expResult, result);
}
private String basicJsonConfig(boolean includeModules) {
String basicConfig =
"{\n"
+ " \"providers\": [\n"
+ " \"classpath:${jboss.home.dir}/providers/*\"\n"
+ " ],\n"
+ "\n"
+ " \"admin\": {\n"
+ " \"realm\": \"master\"\n"
+ " },\n"
+ "\n"
+ " \"eventsStore\": {\n"
+ " \"provider\": \"jpa\",\n"
+ " \"jpa\": {\n"
+ " \"exclude-events\": [ \"REFRESH_TOKEN\" ]\n"
+ " }\n"
+ " },\n"
+ "\n"
+ " \"realm\": {\n"
+ " \"provider\": \"jpa\"\n"
+ " },\n"
+ "\n"
+ " \"user\": {\n"
+ " \"provider\": \"jpa\"\n"
+ " },\n"
+ "\n"
+ " \"userCache\": {\n"
+ " \"default\" : {\n"
+ " \"enabled\": true\n"
+ " }\n"
+ " },\n"
+ "\n"
+ " \"userSessionPersister\": {\n"
+ " \"provider\": \"jpa\"\n"
+ " },\n"
+ "\n"
+ " \"authorizationPersister\": {\n"
+ " \"provider\": \"jpa\"\n"
+ " },\n"
+ "\n"
+ " \"timer\": {\n"
+ " \"provider\": \"basic\"\n"
+ " },\n"
+ "\n"
+ " \"theme\": {\n"
+ " \"staticMaxAge\": 2592001,\n"
+ " \"cacheTemplates\": false,\n"
+ " \"cacheThemes\": false,\n"
+ " \"welcomeTheme\": \"welcome\",\n"
+ " \"default\": \"default\",\n"
+ " \"folder\": {\n"
+ " \"dir\": \"${jboss.home.dir}/themes\"\n";
if (includeModules) {
basicConfig +=
" },\n"
+ " \"module\": {\n"
+ " \"modules\": [ \"org.keycloak.example.themes\" ]\n"
+ " }\n";
} else {
basicConfig +=
" }\n";
}
basicConfig +=
" },\n"
+ "\n"
+ " \"scheduled\": {\n"
+ " \"interval\": 900\n"
+ " },\n"
+ "\n"
+ " \"connectionsHttpClient\": {\n"
+ " \"default\": {}\n"
+ " },\n"
+ "\n"
+ " \"connectionsJpa\": {\n"
+ " \"default\": {\n"
+ " \"dataSource\": \"java:jboss/datasources/KeycloakDS\",\n"
+ " \"databaseSchema\": \"update\"\n"
+ " }\n"
+ " },\n"
+ "\n"
+ " \"realmCache\": {\n"
+ " \"default\" : {\n"
+ " \"enabled\": true\n"
+ " }\n"
+ " },\n"
+ "\n"
+ " \"connectionsInfinispan\": {\n"
+ " \"provider\": \"default\",\n"
+ " \"default\": {\n"
+ " \"cacheContainer\" : \"java:comp/env/infinispan/Keycloak\"\n"
+ " }\n"
+ " }\n"
+ "}";
return basicConfig;
}
private List<ModelNode> expectedOperations(boolean includeModules, boolean isDomain) {
List<ModelNode> ops = new ArrayList<>();
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"write-attribute\",\n" +
" \"address\" => [(\"subsystem\" => \"keycloak-server\")],\n" +
" \"name\" => \"master-realm-name\",\n" +
" \"value\" => \"master\"\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"write-attribute\",\n" +
" \"address\" => [(\"subsystem\" => \"keycloak-server\")],\n" +
" \"name\" => \"scheduled-task-interval\",\n" +
" \"value\" => 900L\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"write-attribute\",\n" +
" \"address\" => [(\"subsystem\" => \"keycloak-server\")],\n" +
" \"name\" => \"providers\",\n" +
" \"value\" => [\"classpath:${jboss.home.dir}/providers/*\"]\n" +
"}"
));
if (includeModules) {
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"theme\" => \"defaults\")\n" +
" ],\n" +
" \"staticMaxAge\" => 2592001L,\n" +
" \"cacheTemplates\" => false,\n" +
" \"cacheThemes\" => false,\n" +
" \"dir\" => \"${jboss.home.dir}/themes\",\n" +
" \"welcomeTheme\" => \"welcome\",\n" +
" \"default\" => \"default\",\n" +
" \"modules\" => [\"org.keycloak.example.themes\"]\n" +
"}"
));
} else {
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"theme\" => \"defaults\")\n" +
" ],\n" +
" \"staticMaxAge\" => 2592001L,\n" +
" \"cacheTemplates\" => false,\n" +
" \"cacheThemes\" => false,\n" +
" \"dir\" => \"${jboss.home.dir}/themes\",\n" +
" \"welcomeTheme\" => \"welcome\",\n" +
" \"default\" => \"default\",\n" +
"}"
));
}
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"eventsStore\")\n" +
" ],\n" +
" \"default-provider\" => \"jpa\"\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"eventsStore\"),\n" +
" (\"provider\" => \"jpa\")\n" +
" ],\n" +
" \"properties\" => {\"exclude-events\" => \"[\\\"REFRESH_TOKEN\\\"]\"},\n" +
" \"enabled\" => true\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"realm\")\n" +
" ],\n" +
" \"default-provider\" => \"jpa\"\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"user\")\n" +
" ],\n" +
" \"default-provider\" => \"jpa\"\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"userCache\")\n" +
" ]\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"userCache\"),\n" +
" (\"provider\" => \"default\")\n" +
" ],\n" +
" \"enabled\" => true\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"userSessionPersister\")\n" +
" ],\n" +
" \"default-provider\" => \"jpa\"\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"authorizationPersister\")\n" +
" ],\n" +
" \"default-provider\" => \"jpa\"\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"timer\")\n" +
" ],\n" +
" \"default-provider\" => \"basic\"\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"connectionsHttpClient\")\n" +
" ]\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"connectionsHttpClient\"),\n" +
" (\"provider\" => \"default\")\n" +
" ],\n" +
" \"enabled\" => true\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"connectionsJpa\")\n" +
" ]\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"connectionsJpa\"),\n" +
" (\"provider\" => \"default\")\n" +
" ],\n" +
" \"properties\" => {\n" +
" \"dataSource\" => \"java:jboss/datasources/KeycloakDS\",\n" +
" \"databaseSchema\" => \"update\"\n" +
" },\n" +
" \"enabled\" => true\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"realmCache\")\n" +
" ]\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"realmCache\"),\n" +
" (\"provider\" => \"default\")\n" +
" ],\n" +
" \"enabled\" => true\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"connectionsInfinispan\")\n" +
" ],\n" +
" \"default-provider\" => \"default\"\n" +
"}"
));
ops.add(ModelNode.fromString(
"{\n" +
" \"operation\" => \"add\",\n" +
" \"address\" => [\n" +
" (\"subsystem\" => \"keycloak-server\"),\n" +
" (\"spi\" => \"connectionsInfinispan\"),\n" +
" (\"provider\" => \"default\")\n" +
" ],\n" +
" \"properties\" => {\"cacheContainer\" => \"java:comp/env/infinispan/Keycloak\"},\n" +
" \"enabled\" => true\n" +
"}"
));
if (isDomain) { // prepend the domain root
for (ModelNode op : ops) {
PathAddress addr = PathAddress.pathAddress(op.get(ADDRESS));
PathAddress domainAddr = PathAddress.pathAddress(domainRoot).append(addr);
op.get(ADDRESS).set(domainAddr.toModelNode());
}
}
return ops;
}
}

View file

@ -17,10 +17,9 @@
package org.keycloak.subsystem.server.extension;
import java.io.IOException;
import java.util.Properties;
import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest;
import org.jboss.dmr.ModelNode;
import org.junit.Test;
/**
* Tests all management expects for subsystem, parsing, marshaling, model definition and other
@ -38,14 +37,13 @@ public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest {
super(KeycloakExtension.SUBSYSTEM_NAME, new KeycloakExtension());
}
@Test
public void testJson() throws Exception {
ModelNode node = new ModelNode();
node.get("web-context").set("auth");
System.out.println("json=" + node.toJSONString(false));
@Override
protected Properties getResolvedProperties() {
Properties properties = new Properties();
properties.put("jboss.home.dir", System.getProperty("java.io.tmpdir"));
return properties;
}
@Override
protected String getSubsystemXml() throws IOException {
return readResource("keycloak-server-1.1.xml");

View file

@ -1,20 +1,79 @@
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
<web-context>auth</web-context>
<providers>
<provider>classpath:${jboss.home.dir}/providers/*</provider>
</providers>
<master-realm-name>master</master-realm-name>
<scheduled-task-interval>900</scheduled-task-interval>
<theme>
<staticMaxAge>2592000</staticMaxAge>
<cacheThemes>true</cacheThemes>
<cacheTemplates>true</cacheTemplates>
<dir>${jboss.home.dir}/themes</dir>
</theme>
<spi name="eventsStore">
<default-provider>jpa</default-provider>
<provider name="jpa" enabled="true">
<properties>
<property name="exclude-events" value="[&quot;REFRESH_TOKEN&quot;]"/>
</properties>
</provider>
</spi>
<spi name="realm">
<default-provider>jpa</default-provider>
</spi>
<spi name="user">
<default-provider>jpa</default-provider>
</spi>
<spi name="userCache">
<provider name="default" enabled="true"/>
</spi>
<spi name="userSessionPersister">
<default-provider>jpa</default-provider>
</spi>
<spi name="authorizationPersister">
<default-provider>jpa</default-provider>
</spi>
<spi name="timer">
<default-provider>basic</default-provider>
</spi>
<spi name="connectionsHttpClient">
<provider name="default" enabled="true"/>
</spi>
<spi name="connectionsJpa">
<provider name="default" enabled="true">
<properties>
<property name="dataSource" value="java:jboss/datasources/KeycloakDS"/>
<property name="databaseSchema" value="update"/>
</properties>
</provider>
</spi>
<spi name="realmCache">
<provider name="default" enabled="true"/>
</spi>
<spi name="connectionsInfinispan">
<default-provider>default</default-provider>
<provider name="default" enabled="true">
<properties>
<property name="cacheContainer" value="java:comp/env/infinispan/Keycloak"/>
</properties>
</provider>
</spi>
</subsystem>