Merge pull request #811 from ssilvert/deploy-auth-in-subsys

KEYCLOAK-795 Move Auth Server into Keycloak Subsystem
This commit is contained in:
Bill Burke 2014-11-06 17:57:19 -05:00
commit 31050e0580
33 changed files with 1357 additions and 103 deletions

3
.gitignore vendored
View file

@ -9,10 +9,11 @@
.settings
.classpath
# NetBeans #
############
nbactions.xml
nb-configuration.xml
catalog.xml
# Compiled source #
###################

View file

@ -22,6 +22,8 @@
<exclude>**/*.sh</exclude>
<exclude>welcome-content/*</exclude>
<exclude>**/modules/system/layers/base/org/picketlink/**</exclude>
<exclude>**/modules/system/layers/base/org/jboss/as/cli/**</exclude>
<exclude>**/modules/system/layers/base/org/jboss/aesh/**</exclude>
</excludes>
</fileSet>
<fileSet>
@ -39,13 +41,6 @@
<include>org/picketlink/**</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/unpacked/deployments</directory>
<outputDirectory>keycloak/standalone/deployments</outputDirectory>
<excludes>
<exclude>keycloak-ds.xml</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/unpacked/deployments/auth-server.war/WEB-INF/classes/META-INF</directory>
<outputDirectory>keycloak/standalone/configuration</outputDirectory>

View file

@ -34,7 +34,12 @@
<xsl:template match="node()[name(.)='profile']">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<subsystem xmlns="urn:jboss:domain:keycloak:1.0"/>
<subsystem xmlns="urn:jboss:domain:keycloak:1.0">
<auth-server name="main-auth-server">
<enabled>true</enabled>
<web-context>auth</web-context>
</auth-server>
</subsystem>
</xsl:copy>
</xsl:template>
@ -56,7 +61,7 @@
<!-- for some reason, Wildfly 8 final decided to turn off management-native which means jboss-as-maven-plugin no
longer works -->
longer works -->
<xsl:template match="node()[name(.)='management-interfaces']">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
@ -67,7 +72,7 @@
</xsl:template>
<!-- for some reason, Wildfly 8 final decided to turn off management-native which means jboss-as-maven-plugin no
longer works -->
longer works -->
<xsl:template match="node()[name(.)='socket-binding-group']">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>

View file

@ -51,6 +51,14 @@
<maven-resource group="org.bouncycastle" artifact="bcprov-jdk16"/>
</module-def>
<module-def name="org.jboss.aesh">
<maven-resource group="org.jboss.aesh" artifact="aesh"/>
</module-def>
<module-def name="org.jboss.as.cli">
<maven-resource group="org.wildfly.core" artifact="wildfly-cli"/>
</module-def>
<module-def name="org.keycloak.keycloak-core">
<maven-resource group="org.keycloak" artifact="keycloak-core"/>
</module-def>

View file

@ -53,6 +53,11 @@
<!-- Process the resource -->
<resources/>
<!-- Add keycloak version property to module xml -->
<replace file="${module.repo.output.dir}/${current.module.path}/${module.xml}"
token="$${project.version}"
value="${project.version}"/>
<!-- Some final cleanup -->
<replace file="${module.repo.output.dir}/${current.module.path}/${module.xml}">
<replacetoken>

View file

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ JBoss, Home of Professional Open Source.
~ Copyright 2010, Red Hat, Inc., and individual contributors
~ as indicated by the @author tags. See the copyright.txt file in the
~ distribution for a full listing of individual contributors.
~
~ This is free software; you can redistribute it and/or modify it
~ under the terms of the GNU Lesser General Public License as
~ published by the Free Software Foundation; either version 2.1 of
~ the License, or (at your option) any later version.
~
~ This software is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
~ Lesser General Public License for more details.
~
~ You should have received a copy of the GNU Lesser General Public
~ License along with this software; if not, write to the Free
~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-->
~ JBoss, Home of Professional Open Source.
~ Copyright 2010, Red Hat, Inc., and individual contributors
~ as indicated by the @author tags. See the copyright.txt file in the
~ distribution for a full listing of individual contributors.
~
~ This is free software; you can redistribute it and/or modify it
~ under the terms of the GNU Lesser General Public License as
~ published by the Free Software Foundation; either version 2.1 of
~ the License, or (at your option) any later version.
~
~ This software is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
~ Lesser General Public License for more details.
~
~ You should have received a copy of the GNU Lesser General Public
~ License along with this software; if not, write to the Free
~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@ -83,6 +83,11 @@
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly.core</groupId>
<artifactId>wildfly-cli</artifactId>
<version>${wildfly.core.version}</version>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-common</artifactId>
@ -119,7 +124,10 @@
<groupId>org.picketlink</groupId>
<artifactId>picketlink-impl</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.aesh</groupId>
<artifactId>aesh</artifactId>
</dependency>
</dependencies>
<build>
@ -211,6 +219,31 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>compile</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server</artifactId>
<version>${project.version}</version>
<type>war</type>
<overWrite>true</overWrite>
<outputDirectory>${project.build.directory}/modules/org/keycloak/keycloak-wildfly-subsystem/main/auth-server</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ JBoss, Home of Professional Open Source.
~ Copyright 2010, Red Hat, Inc., and individual contributors
~ as indicated by the @author tags. See the copyright.txt file in the
~ distribution for a full listing of individual contributors.
~
~ This is free software; you can redistribute it and/or modify it
~ under the terms of the GNU Lesser General Public License as
~ published by the Free Software Foundation; either version 2.1 of
~ the License, or (at your option) any later version.
~
~ This software is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
~ Lesser General Public License for more details.
~
~ You should have received a copy of the GNU Lesser General Public
~ License along with this software; if not, write to the Free
~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-->
<module xmlns="urn:jboss:module:1.3" name="org.jboss.aesh">
<properties>
<property name="jboss.api" value="private"/>
</properties>
<resources>
<artifact name="org.jboss.aesh:aesh:0.33.12"/>
</resources>
<dependencies>
<module name="org.fusesource.jansi" />
</dependencies>
</module>

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ JBoss, Home of Professional Open Source.
~ Copyright 2011, Red Hat, Inc., and individual contributors
~ as indicated by the @author tags. See the copyright.txt file in the
~ distribution for a full listing of individual contributors.
~
~ This is free software; you can redistribute it and/or modify it
~ under the terms of the GNU Lesser General Public License as
~ published by the Free Software Foundation; either version 2.1 of
~ the License, or (at your option) any later version.
~
~ This software is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
~ Lesser General Public License for more details.
~
~ You should have received a copy of the GNU Lesser General Public
~ License along with this software; if not, write to the Free
~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-->
<module xmlns="urn:jboss:module:1.3" name="org.jboss.as.cli">
<properties>
<property name="jboss.api" value="private"/>
<property name="jboss.require-java-version" value="1.7"/>
</properties>
<main-class name="org.jboss.as.cli.CommandLineMain"/>
<resources>
<resource-root path="wildfly-cli-1.0.0.Alpha11-SNAPSHOT.jar"/>
</resources>
<dependencies>
<module name="org.jboss.aesh"/>
<module name="org.jboss.modules"/>
<module name="org.jboss.as.controller-client"/>
<module name="org.jboss.as.protocol"/>
<module name="org.wildfly.security.manager"/>
<module name="org.jboss.as.patching.cli" optional="true" services="import"/>
<module name="org.jboss.dmr"/>
<module name="org.jboss.logging"/>
<module name="org.jboss.logmanager" services="import"/>
<module name="org.jboss.remoting"/>
<module name="org.jboss.sasl"/>
<module name="org.jboss.staxmapper"/>
<module name="org.jboss.threads"/>
<module name="org.jboss.vfs"/>
<module name="org.picketbox" optional="true"/>
<module name="javax.api"/>
</dependencies>
</module>

View file

@ -2,7 +2,7 @@
<!--
~ JBoss, Home of Professional Open Source.
~ Copyright 2010, Red Hat, Inc., and individual contributors
~ Copyright 2014, Red Hat, Inc., and individual contributors
~ as indicated by the @author tags. See the copyright.txt file in the
~ distribution for a full listing of individual contributors.
~
@ -23,7 +23,13 @@
-->
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-wildfly-subsystem">
<properties>
<property name="keycloak-version" value="${project.version}"/>
<property name="auth-server-exploded" value="false"/>
</properties>
<resources>
<resource-root path="."/>
<!-- Insert resources here -->
</resources>
@ -31,6 +37,7 @@
<module name="javax.api"/>
<module name="org.jboss.staxmapper"/>
<module name="org.jboss.as.controller"/>
<module name="org.jboss.as.ee"/>
<module name="org.jboss.as.server"/>
<module name="org.jboss.modules"/>
<module name="org.jboss.msc"/>

View file

@ -124,6 +124,14 @@ keycloak-war-dist-all-&project.version;/
If you have Keycloak on JBoss AS 7.1.1 <link linkend="as7-specifics">these steps</link>.
</para>
</section>
<section id="subsystem_installation">
<title>Subsystem Installation</title>
<para>
For WildFly installations, the Keycloak server is not deployed from the /deployments directory. Instead, the Keycloak
subsystem module contains the Keycloak server and it is controlled by the subsystem. If you are using the
appliance install, this subsystem is already present and a Keycloak server is pre-defined in the subsytem declaration.
</para>
</section>
<section>
<title id="configure-server">Configuring the Server</title>
<para>
@ -749,4 +757,142 @@ keycloak-war-dist-all-&project.version;/
</section>
</section>
<section>
<title>Configuring Servers from the Subsystem</title>
<para>
If you are using WildFly, the Keycloak server is deployed and configured from the Keycloak subsystem. This makes provisioning simpler in a domain environment.
It also allows you to create more than one Keycloak server instance inside a single WildFly instance. And, you can upload providers, themes, and
server configurations without disturbing Keycloak's auth-server.war.
</para>
<section>
<title>Manually Creating A Server</title>
<para>
A Keycloak server can be declared by editing standalone.xml or domain.xml.
</para>
<para>
<programlisting><![CDATA[
<server xmlns="urn:jboss:domain:1.4">
<profile>
<subsystem xmlns="urn:jboss:domain:keycloak:1.0">
<auth-server name="keycloak-1">
<enabled>true</enabled>
<web-context>auth</web-context>
</auth-server>
<auth-server name="keyclaok-2">
<enabled>false</enabled>
<web-context>auth2</web-context>
</auth-server>
</subsystem>
</profile>
]]>
</programlisting>
</para>
<warning>
<para>
If you create more than one Keycloak server, you will need to use CLI to fully configure each instance. At the least,
you will need to run the <link linkend="uploading-extra-config">update-server-config</link> operation.
</para>
</warning>
</section>
<section>
<title>Using CLI and CLI GUI with the Keycloak Subsystem</title>
<para>
Servers can also be added/removed or enabled/disabled at runtime using the <ulink url="https://developer.jboss.org/wiki/CommandLineInterface">CLI</ulink> or
<ulink url="https://developer.jboss.org/wiki/AGUIForTheCommandLineInterface">CLI GUI</ulink> tool. These are tools that ship with WildFly and also with
the Keycloak Appliance installation. See <ulink url="https://developer.jboss.org/wiki/CommandLineInterface">CLI</ulink> or
<ulink url="https://developer.jboss.org/wiki/AGUIForTheCommandLineInterface">CLI GUI</ulink> documentation to learn more about how to start the tools,
issue commands, and create CLI scripts.
</para>
<para>
To start CLI with the Keycloak Appliance install:
<programlisting><![CDATA[
cd <APPLIANCE_INSTALL_DIR>/keycloak/bin
./jboss-cli.sh --gui
or
./jboss.cli.bat --gui]]>
</programlisting>
<note>Your Keycloak server must be running to start in --gui mode.</note>
</para>
<section>
<title>Basic CLI Commands</title>
<para>
Command to add a server in CLI:
<programlisting><![CDATA[
/subsystem=keycloak/auth-server=my-auth-server/:add(web-context=my-auth, enabled=true)]]>
</programlisting>
Because "enabled=true", a new Keycloak server will be immediately deployed. By default "enabled" is set to false.
</para>
<para>
Command to remove a server in CLI:
<programlisting><![CDATA[
/subsystem=keycloak/auth-server=my-auth-server/:remove]]>
</programlisting>
The Keycloak server will be immediately deleted and undeployed.
</para>
<para>
Command to enable or disable a server in CLI:
<programlisting><![CDATA[
/subsystem=keycloak/auth-server=foo/:write-attribute(name=enabled,value=true)]]>
</programlisting>
The Keycloak server will be immediately deployed or undeployed, but not deleted.
</para>
</section>
<section id="uploading-extra-config">
<title>Uploading extra configuration using CLI</title>
<para>
The WildFly Keycloak subsystem allows you to upload keycloak-server.json, provider jars, and theme jars to a Keycloak server instance. The
CLI operations for this are "update-server-config" and "add-provider". You may use plain or CLI scripts for these operations. The following
examples are shown using <ulink url="https://developer.jboss.org/wiki/AGUIForTheCommandLineInterface">CLI GUI</ulink> for clarity.
</para>
<para>
To use a new keycloak-server.json file for your server, find your server under the Keycloak subsystem. Then right-click the server,
select "update-server-config", and upload your file.
</para>
<para>
<imagedata fileref="images/update-server-config-select.png"/>
</para>
<para>
<imagedata fileref="images/update-server-config-dialog.png"/>
</para>
<warning>
<para>
If you use the update-server-config operation, you should delete or rename &lt;WILDFLY_HOME&gt;/standalone/configuration/keycloak-server.json.
Otherwise, all Keycloak server instances will use this file instead of your uploaded file.
</para>
</warning>
<para>
To upload a new provider jar or theme jar to your server, find your server under the Keycloak subsystem. Then right-click the server,
select "add-provider", and upload your file.
</para>
<para>
<imagedata fileref="images/add-provider-select.png"/>
</para>
<para>
<imagedata fileref="images/add-provider-dialog.png"/>
</para>
</section>
</section>
<section>
<title>Adding a Keycloak server in Domain Mode</title>
<para>
In domain mode, you start the server with the "domain" command instead of the "standalone" command. In this case, the Keycloak subsystem is
defined in domain/configuration/domain.xml instead of standalone/configuration.standalone.xml. Inside domain.xml, you will see more than one
profile. A Keycloak subsystem can be defined in zero or more of those profiles.
</para>
<para>
In the example below, a Keycloak server named "foo" is defined in the "full" profile. The "full" profile is assigned to the "main-server-group".
Every WildFly instance that belongs to "main-server-group" will get an identically configured deployment of the "foo" Keycloak server.
</para>
<para>
All operations discussed earlier are valid for a Keycloak server in a domain. You can enable/disable, upload new keyclaok-server.json, and add provider jars.
In the following example, any changes that are made to the "foo" server will be automatically propogated to every instance in "main-server-group".
</para>
<para>
<imagedata fileref="images/domain-mode.png"/>
</para>
</section>
</section>
</chapter>

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View file

@ -42,11 +42,9 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.8.1</version>
<configuration>
<redirectTestOutputToFile>false</redirectTestOutputToFile>
<enableAssertions>true</enableAssertions>
<argLine>-Xmx512m</argLine>
<systemProperties>
<property>
<name>jboss.home</name>
@ -56,7 +54,6 @@
<includes>
<include>**/*TestCase.java</include>
</includes>
<forkMode>once</forkMode>
</configuration>
</plugin>
</plugins>

View file

@ -31,6 +31,10 @@ import org.keycloak.subsystem.logging.KeycloakLogger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.jboss.as.ee.component.EEModuleDescription;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.MountedDeploymentOverlay;
/**
* Pass authentication data (keycloak.json) as a servlet context param so it can be read by the KeycloakServletExtension.
@ -45,14 +49,14 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
// two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration.
public static final String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig";
public static final Phase PHASE = Phase.INSTALL;
// Seems wise to have this run after INSTALL_WAR_DEPLOYMENT
public static final int PRIORITY = Phase.INSTALL_WAR_DEPLOYMENT - 1;
public static final Phase PHASE = Phase.POST_MODULE;
// This needs to run just before bean validator factory
public static final int PRIORITY = Phase.POST_MODULE_VALIDATOR_FACTORY - 1;
// not sure if we need this yet, keeping here just in case
protected void addSecurityDomain(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService service) {
String deploymentName = deploymentUnit.getName();
if (!service.isKeycloakDeployment(deploymentName)) {
if (!service.isSecureDeployment(deploymentName)) {
return;
}
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
@ -75,11 +79,16 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
String deploymentName = deploymentUnit.getName();
KeycloakAdapterConfigService service = KeycloakAdapterConfigService.find(phaseContext.getServiceRegistry());
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: " + deploymentName);
if (service.isKeycloakDeployment(deploymentName)) {
if (service.isSecureDeployment(deploymentName)) {
addKeycloakAuthData(phaseContext, deploymentName, service);
}
if (service.isKeycloakServerDeployment(deploymentName)) {
final EEModuleDescription description = deploymentUnit.getAttachment(org.jboss.as.ee.component.Attachments.EE_MODULE_DESCRIPTION);
String webContext = service.getWebContext(deploymentName);
if (webContext == null) throw new DeploymentUnitProcessingException("Can't determine web context/module for Keycloak Auth Server");
description.setModuleName(webContext);
}
// FYI, Undertow Extension will find deployments that have auth-method set to KEYCLOAK
// todo notsure if we need this
@ -99,6 +108,7 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
webMetaData = new JBossWebMetaData();
warMetaData.setMergedJBossWebMetaData(webMetaData);
}
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
if (loginConfig == null) {
loginConfig = new LoginConfigMetaData();

View file

@ -51,7 +51,12 @@ public final class KeycloakAdapterConfigService implements Service<KeycloakAdapt
public static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService();
private Map<String, ModelNode> realms = new HashMap<String, ModelNode>();
private Map<String, ModelNode> deployments = new HashMap<String, ModelNode>();
// keycloak-secured deployments
private Map<String, ModelNode> secureDeployments = new HashMap<String, ModelNode>();
// key=auth-server deployment name; value=web-context
private Map<String, String> webContexts = new HashMap<String, String>();
private KeycloakAdapterConfigService() {
@ -72,6 +77,22 @@ public final class KeycloakAdapterConfigService implements Service<KeycloakAdapt
return this;
}
public void addServerDeployment(String deploymentName, String webContext) {
this.webContexts.put(deploymentName, webContext);
}
public String getWebContext(String deploymentName) {
return webContexts.get(deploymentName);
}
public void removeServerDeployment(String deploymentName) {
this.webContexts.remove(deploymentName);
}
public boolean isWebContextUsed(String webContext) {
return webContexts.containsValue(webContext);
}
public void addRealm(ModelNode operation, ModelNode model) {
this.realms.put(realmNameFromOp(operation), model.clone());
}
@ -87,16 +108,16 @@ public final class KeycloakAdapterConfigService implements Service<KeycloakAdapt
public void addSecureDeployment(ModelNode operation, ModelNode model) {
ModelNode deployment = model.clone();
this.deployments.put(deploymentNameFromOp(operation), deployment);
this.secureDeployments.put(deploymentNameFromOp(operation), deployment);
}
public void updateSecureDeployment(ModelNode operation, String attrName, ModelNode resolvedValue) {
ModelNode deployment = this.deployments.get(deploymentNameFromOp(operation));
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(attrName).set(resolvedValue);
}
public void removeSecureDeployment(ModelNode operation) {
this.deployments.remove(deploymentNameFromOp(operation));
this.secureDeployments.remove(deploymentNameFromOp(operation));
}
public void addCredential(ModelNode operation, ModelNode model) {
@ -108,7 +129,7 @@ public final class KeycloakAdapterConfigService implements Service<KeycloakAdapt
String credentialName = credentialNameFromOp(operation);
credentials.get(credentialName).set(model.get("value").asString());
ModelNode deployment = this.deployments.get(deploymentNameFromOp(operation));
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(CREDENTIALS_JSON_NAME).set(credentials);
}
@ -133,7 +154,7 @@ public final class KeycloakAdapterConfigService implements Service<KeycloakAdapt
}
private ModelNode credentialsFromOp(ModelNode operation) {
ModelNode deployment = this.deployments.get(deploymentNameFromOp(operation));
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
return deployment.get(CREDENTIALS_JSON_NAME);
}
@ -164,13 +185,13 @@ public final class KeycloakAdapterConfigService implements Service<KeycloakAdapt
}
public String getRealmName(String deploymentName) {
ModelNode deployment = this.deployments.get(deploymentName);
ModelNode deployment = this.secureDeployments.get(deploymentName);
return deployment.get(RealmDefinition.TAG_NAME).asString();
}
public String getJSON(String deploymentName) {
ModelNode deployment = this.deployments.get(deploymentName);
ModelNode deployment = this.secureDeployments.get(deploymentName);
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
ModelNode realm = this.realms.get(realmName);
@ -193,10 +214,14 @@ public final class KeycloakAdapterConfigService implements Service<KeycloakAdapt
}
}
public boolean isKeycloakDeployment(String deploymentName) {
public boolean isSecureDeployment(String deploymentName) {
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
return this.deployments.containsKey(deploymentName);
return this.secureDeployments.containsKey(deploymentName);
}
public boolean isKeycloakServerDeployment(String deploymentName) {
return this.webContexts.containsKey(deploymentName);
}
static KeycloakAdapterConfigService find(ServiceRegistry registry) {

View file

@ -44,6 +44,10 @@ public class KeycloakDependencyProcessor implements DeploymentUnitProcessor {
@Override
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
String deploymentName = deploymentUnit.getName();
KeycloakAdapterConfigService service = KeycloakAdapterConfigService.find(phaseContext.getServiceRegistry());
addModules(deploymentUnit);
}

View file

@ -16,6 +16,7 @@
*/
package org.keycloak.subsystem.extension;
import org.keycloak.subsystem.extension.authserver.AuthServerDefinition;
import org.jboss.as.controller.Extension;
import org.jboss.as.controller.ExtensionContext;
import org.jboss.as.controller.PathElement;
@ -46,11 +47,12 @@ public class KeycloakExtension implements Extension {
private static final int MANAGEMENT_API_MICRO_VERSION = 0;
protected static final PathElement SUBSYSTEM_PATH = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);
private static final ResourceDefinition KEYCLOAK_SUBSYSTEM_RESOURCE = new KeycloakSubsystemDefinition();
static final AuthServerDefinition AUTH_SERVER_DEFINITION = new AuthServerDefinition();
static final RealmDefinition REALM_DEFINITION = new RealmDefinition();
static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition();
static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition();
static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
for (String kp : keyPrefix) {
prefix.append('.').append(kp);
@ -76,7 +78,8 @@ public class KeycloakExtension implements Extension {
MANAGEMENT_API_MINOR_VERSION, MANAGEMENT_API_MICRO_VERSION);
ManagementResourceRegistration registration = subsystem.registerSubsystemModel(KEYCLOAK_SUBSYSTEM_RESOURCE);
ManagementResourceRegistration realmRegistration = registration.registerSubModel(REALM_DEFINITION);
registration.registerSubModel(AUTH_SERVER_DEFINITION);
registration.registerSubModel(REALM_DEFINITION);
ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION);
secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION);

View file

@ -16,6 +16,7 @@
*/
package org.keycloak.subsystem.extension;
import org.jboss.as.controller.AbstractBoottimeAddStepHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
@ -27,6 +28,7 @@ import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.ServiceController;
import java.util.List;
import org.jboss.as.controller.registry.Resource;
/**
* The Keycloak subsystem add update handler.
@ -38,8 +40,10 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
static final KeycloakSubsystemAdd INSTANCE = new KeycloakSubsystemAdd();
@Override
protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
model.setEmptyObject();
protected void populateModel(OperationContext context, ModelNode operation, Resource resource) throws OperationFailedException {
resource.getModel().setEmptyObject();
}
@Override
@ -49,9 +53,9 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
protected void execute(DeploymentProcessorTarget processorTarget) {
processorTarget.addDeploymentProcessor(KeycloakExtension.SUBSYSTEM_NAME, Phase.DEPENDENCIES, 0, new KeycloakDependencyProcessor());
processorTarget.addDeploymentProcessor(KeycloakExtension.SUBSYSTEM_NAME,
KeycloakAdapterConfigDeploymentProcessor.PHASE,
KeycloakAdapterConfigDeploymentProcessor.PRIORITY,
new KeycloakAdapterConfigDeploymentProcessor());
KeycloakAdapterConfigDeploymentProcessor.PHASE,
KeycloakAdapterConfigDeploymentProcessor.PRIORITY,
new KeycloakAdapterConfigDeploymentProcessor());
}
}, OperationContext.Stage.RUNTIME);
}

View file

@ -16,6 +16,7 @@
*/
package org.keycloak.subsystem.extension;
import org.keycloak.subsystem.extension.authserver.AuthServerDefinition;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
@ -58,6 +59,8 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
}
else if (reader.getLocalName().equals(SecureDeploymentDefinition.TAG_NAME)) {
readDeployment(reader, list);
} else if (reader.getLocalName().equals(AuthServerDefinition.TAG_NAME)) {
readAuthServer(reader, list);
}
}
}
@ -67,6 +70,24 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
return reader.nextTag();
}
private void readAuthServer(XMLExtendedStreamReader reader, List<ModelNode> list) throws XMLStreamException {
String authServerName = readNameAttribute(reader);
ModelNode addAuthServer = new ModelNode();
addAuthServer.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME),
PathElement.pathElement(AuthServerDefinition.TAG_NAME, authServerName));
addAuthServer.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
String tagName = reader.getLocalName();
SimpleAttributeDefinition def = AuthServerDefinition.lookup(tagName);
if (def == null) throw new XMLStreamException("Unknown auth-server tag " + tagName);
def.parseAndSetParameter(reader.getElementText(), addAuthServer, reader);
}
list.add(addAuthServer);
}
private void readRealm(XMLExtendedStreamReader reader, List<ModelNode> list) throws XMLStreamException {
String realmName = readNameAttribute(reader);
ModelNode addRealm = new ModelNode();
@ -157,11 +178,28 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
@Override
public void writeContent(final XMLExtendedStreamWriter writer, final SubsystemMarshallingContext context) throws XMLStreamException {
context.startSubsystemElement(KeycloakExtension.NAMESPACE, false);
writeAuthServers(writer, context);
writeRealms(writer, context);
writeSecureDeployments(writer, context);
writer.writeEndElement();
}
private void writeAuthServers(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
if (!context.getModelNode().get(AuthServerDefinition.TAG_NAME).isDefined()) {
return;
}
for (Property authServer : context.getModelNode().get(AuthServerDefinition.TAG_NAME).asPropertyList()) {
writer.writeStartElement(AuthServerDefinition.TAG_NAME);
writer.writeAttribute("name", authServer.getName());
ModelNode authServerElements = authServer.getValue();
for (AttributeDefinition element : AuthServerDefinition.ALL_ATTRIBUTES) {
element.marshallAsElement(authServerElements, writer);
}
writer.writeEndElement();
}
}
private void writeRealms(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
if (!context.getModelNode().get(RealmDefinition.TAG_NAME).isDefined()) {
return;

View file

@ -0,0 +1,182 @@
/*
* Copyright 2014 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.extension.authserver;
import java.util.Set;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationStepHandler;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.ProcessType;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT_OVERLAY;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.controller.registry.Resource;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import static org.keycloak.subsystem.extension.authserver.AuthServerUtil.getHandler;
/**
* Base class for operations that create overlays for an auth server.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public abstract class AbstractAddOverlayHandler implements OperationStepHandler {
protected static final String UPLOADED_FILE_OP_NAME = "uploaded-file-name";
protected static final SimpleAttributeDefinition BYTES_TO_UPLOAD
= new SimpleAttributeDefinitionBuilder("bytes-to-upload", ModelType.BYTES, false)
.setAllowExpression(false)
.setAllowNull(false)
.build();
@Override
public void execute(final OperationContext context, final ModelNode operation) throws OperationFailedException {
//System.out.println("*** execute operation ***");
//System.out.println(scrub(operation));
String uploadFileName = operation.get(UPLOADED_FILE_OP_NAME).asString();
String overlayPath = getOverlayPath(uploadFileName);
String overlayName = AuthServerUtil.getAuthServerName(operation) + "-keycloak-overlay";
PathAddress overlayAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT_OVERLAY, overlayName));
boolean isOverlayExists = isOverlayExists(context, overlayName, PathAddress.EMPTY_ADDRESS);
if (!isOverlayExists) {
addOverlay(context, overlayAddress);
if (!isHostController(context)) {
addDeploymentToOverlay(context, overlayAddress, AuthServerUtil.getDeploymentName(operation));
}
}
if (isHostController(context)) {
addOverlayToServerGroups(context, overlayAddress, operation, overlayName);
}
// There is no way to do an overwrite of content from here because it involves
// removing the overlay service in the runtime phase. You have to remove
// the content in a seperate operation.
if (isOverlayExists && isContentExists(context, overlayAddress, overlayPath)) {
throw new OperationFailedException(pathExistsMessage(overlayAddress, overlayPath));
}
addContent(context, overlayAddress, operation.get(BYTES_TO_UPLOAD.getName()).asBytes(), overlayPath);
context.restartRequired();
context.completeStep(OperationContext.ResultHandler.NOOP_RESULT_HANDLER);
}
private boolean isHostController(OperationContext context) {
return context.getProcessType() == ProcessType.HOST_CONTROLLER;
}
private String pathExistsMessage(PathAddress overlayAddress, String overlayPath) {
PathAddress contentAddress = overlayAddress.append("content", overlayPath);
String msg = "Can not update overlay. ";
msg += "First remove the overlay with CLI using the following command with the content path in double quotes: ";
msg += contentAddress.toCLIStyleString() + ":remove";
return msg;
}
private boolean isOverlayExists(OperationContext context, String overlayName, PathAddress address) {
Resource resource = context.readResourceFromRoot(address);
return resource.getChildrenNames("deployment-overlay").contains(overlayName);
}
private boolean isContentExists(OperationContext context, PathAddress overlayAddress, String overlayPath) {
Resource resource = context.readResourceFromRoot(overlayAddress);
return resource.getChildrenNames("content").contains(overlayPath);
}
private void addOverlay(OperationContext context, PathAddress overlayAddress) {
ModelNode op = Util.createAddOperation(overlayAddress);
doAddStep(context, overlayAddress, op);
}
private void addDeploymentToOverlay(OperationContext context, PathAddress overlayAddress, String deploymentName) {
PathAddress deploymentAddress = overlayAddress.append("deployment", deploymentName);
ModelNode op = Util.createAddOperation(deploymentAddress);
doAddStep(context, deploymentAddress, op);
}
// only call this if context.getProcessType() == ProcessType.HOST_CONTROLLER
private void addOverlayToServerGroups(OperationContext context, PathAddress overlayAddress, ModelNode operation, String overlayName) {
String myProfile = findMyProfile(operation);
for (String serverGroup : getServerGroupNames(context)) {
PathAddress address = PathAddress.pathAddress("server-group", serverGroup);
ModelNode serverGroupModel = context.readResourceFromRoot(address).getModel();
if (serverGroupModel.get("profile").asString().equals(myProfile)) {
PathAddress serverGroupOverlayAddress = address.append(overlayAddress);
boolean isOverlayExists = isOverlayExists(context, overlayName, address);
if (!isOverlayExists) {
addOverlay(context, serverGroupOverlayAddress);
addDeploymentToOverlay(context, serverGroupOverlayAddress, AuthServerUtil.getDeploymentName(operation));
}
}
}
}
// only call this if context.getProcessType() == ProcessType.HOST_CONTROLLER
private String findMyProfile(ModelNode operation) {
PathAddress address = PathAddress.pathAddress(operation.get("address"));
return address.getElement(0).getValue();
}
private Set<String> getServerGroupNames(OperationContext context) {
return context.readResourceFromRoot(PathAddress.EMPTY_ADDRESS).getChildrenNames("server-group");
}
private void addContent(OperationContext context, PathAddress overlayAddress, byte[] bytes, String overlayPath) throws OperationFailedException {
PathAddress contentAddress = overlayAddress.append("content", overlayPath);
ModelNode op = Util.createAddOperation(contentAddress);
ModelNode content = new ModelNode();
content.get("bytes").set(bytes);
op.get("content").set(content);
doAddStep(context, contentAddress, op);
}
private void doAddStep(OperationContext context, PathAddress address, ModelNode operation) {
//System.out.println("**** Adding Add Step ****");
//System.out.println(scrub(operation).toString());
context.addStep(operation, getHandler(context, address, ADD), OperationContext.Stage.MODEL);
}
// used for debugging
private ModelNode scrub(ModelNode op) {
ModelNode scrubbed = op.clone();
if (scrubbed.has("content")) {
scrubbed.get("content").set("BYTES REMOVED FOR DISPLAY");
}
if (scrubbed.has("bytes-to-upload")) {
scrubbed.get("bytes-to-upload").set("BYTES REMOVED FOR DISPLAY");
}
return scrubbed;
}
/**
* Get the WAR path where the overlay will live.
*
* @param file The name of the file being uploaded.
* @return The overlay path as a String.
*/
abstract String getOverlayPath(String fileName);
}

View file

@ -0,0 +1,59 @@
/*
* Copyright 2014 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.extension.authserver;
import java.io.File;
import org.jboss.as.controller.OperationDefinition;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
/**
* Operation to add a provider jar to WEB-INF/lib.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public class AddProviderHandler extends AbstractAddOverlayHandler {
public static final String OP = "add-provider";
public static final AddProviderHandler INSTANCE = new AddProviderHandler();
protected static final SimpleAttributeDefinition UPLOADED_FILE_NAME =
new SimpleAttributeDefinitionBuilder(UPLOADED_FILE_OP_NAME, ModelType.STRING, false)
.setAllowExpression(false)
.setAllowNull(false)
.setDefaultValue(new ModelNode().set("myprovider.jar"))
.build();
public static OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OP, AuthServerDefinition.rscDescriptionResolver)
.addParameter(BYTES_TO_UPLOAD)
.addParameter(UPLOADED_FILE_NAME)
.build();
@Override
String getOverlayPath(String fileName) {
if (!fileName.toLowerCase().endsWith(".jar")) {
throw new IllegalArgumentException("Uploaded file name must end with .jar");
}
return "/WEB-INF/lib/" + fileName;
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright 2014 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.extension.authserver;
import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import org.jboss.as.controller.registry.Resource;
import org.keycloak.subsystem.extension.KeycloakAdapterConfigService;
/**
* Add an auth server.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public final class AuthServerAddHandler extends AbstractAddStepHandler {
public static AuthServerAddHandler INSTANCE = new AuthServerAddHandler();
private AuthServerAddHandler() {
}
@Override
protected void populateModel(OperationContext context, ModelNode operation, Resource resource) throws OperationFailedException {
// TODO: localize exception. get id number
if (!operation.get(OP).asString().equals(ADD)) {
throw new OperationFailedException("Unexpected operation for add Auth Server. operation=" + operation.toString());
}
ModelNode model = resource.getModel();
for (AttributeDefinition attr : AuthServerDefinition.ALL_ATTRIBUTES) {
attr.validateAndSet(operation, model);
}
// returns early if on domain controller
if (!requiresRuntime(context)) return;
// don't want to try to start server on host controller
if (!context.isNormalServer()) return;
ModelNode webContextNode = model.get(AuthServerDefinition.WEB_CONTEXT.getName());
if (!webContextNode.isDefined()) webContextNode = AuthServerDefinition.WEB_CONTEXT.getDefaultValue();
String webContext = webContextNode.asString();
ModelNode isEnabled = model.get("enabled");
boolean enabled = isEnabled.isDefined() && isEnabled.asBoolean();
AuthServerUtil authServerUtil = new AuthServerUtil(operation);
authServerUtil.addStepToUploadAuthServer(context, enabled);
KeycloakAdapterConfigService.INSTANCE.addServerDeployment(authServerUtil.getDeploymentName(), webContext);
}
@Override
protected boolean requiresRuntimeVerification() {
return false;
}
}

View file

@ -0,0 +1,131 @@
/*
* Copyright 2014 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.extension.authserver;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.descriptions.ResourceDescriptionResolver;
import org.jboss.as.controller.operations.validation.ParameterValidator;
import org.jboss.as.controller.registry.OperationEntry;
import org.keycloak.subsystem.extension.KeycloakAdapterConfigService;
import org.keycloak.subsystem.extension.KeycloakExtension;
/**
* Defines attributes and operations for an Auth Server
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public class AuthServerDefinition extends SimpleResourceDefinition {
public static final String TAG_NAME = "auth-server";
protected static final SimpleAttributeDefinition ENABLED =
new SimpleAttributeDefinitionBuilder("enabled", ModelType.BOOLEAN, true)
.setXmlName("enabled")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.setRestartAllServices()
.build();
protected static final SimpleAttributeDefinition WEB_CONTEXT =
new SimpleAttributeDefinitionBuilder("web-context", ModelType.STRING, true)
.setXmlName("web-context")
.setAllowExpression(true)
.setDefaultValue(new ModelNode("auth"))
.setValidator(new WebContextValidator())
.setRestartAllServices()
.build();
protected static final ResourceDescriptionResolver rscDescriptionResolver = KeycloakExtension.getResourceDescriptionResolver(TAG_NAME);
public static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
static {
ALL_ATTRIBUTES.add(ENABLED);
ALL_ATTRIBUTES.add(WEB_CONTEXT);
}
private static final Map<String, SimpleAttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, SimpleAttributeDefinition>();
static {
for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
DEFINITION_LOOKUP.put(def.getXmlName(), def);
}
}
private static AuthServerWriteAttributeHandler attrHandler = new AuthServerWriteAttributeHandler(ALL_ATTRIBUTES);
public AuthServerDefinition() {
super(PathElement.pathElement(TAG_NAME),
rscDescriptionResolver,
AuthServerAddHandler.INSTANCE,
AuthServerRemoveHandler.INSTANCE,
null,
OperationEntry.Flag.RESTART_ALL_SERVICES);
}
@Override
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
super.registerOperations(resourceRegistration);
resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
resourceRegistration.registerOperationHandler(AddProviderHandler.DEFINITION, AddProviderHandler.INSTANCE);
resourceRegistration.registerOperationHandler(OverlayKeycloakServerJsonHandler.DEFINITION, OverlayKeycloakServerJsonHandler.INSTANCE);
}
@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
super.registerAttributes(resourceRegistration);
for (AttributeDefinition attrDef : ALL_ATTRIBUTES) {
resourceRegistration.registerReadWriteAttribute(attrDef, null, attrHandler);
}
}
public static SimpleAttributeDefinition lookup(String name) {
return DEFINITION_LOOKUP.get(name);
}
private static class WebContextValidator implements ParameterValidator {
@Override
public void validateParameter(String paramName, ModelNode value) throws OperationFailedException {
String strValue = value.asString();
if (KeycloakAdapterConfigService.INSTANCE.isWebContextUsed(strValue)) {
throw new OperationFailedException("Can not set web-context to '" + strValue + "'. web-context must be unique among all deployments.");
}
}
@Override
public void validateResolvedParameter(String paramName, ModelNode value) throws OperationFailedException {
String strValue = value.asString();
if (KeycloakAdapterConfigService.INSTANCE.isWebContextUsed(strValue)) {
throw new OperationFailedException("Can not set web-context to '" + strValue + "'. web-context must be unique among all deployments.");
}
}
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright 2014 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.extension.authserver;
import org.jboss.as.controller.AbstractRemoveStepHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationStepHandler;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.dmr.ModelNode;
import org.keycloak.subsystem.extension.KeycloakAdapterConfigService;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE;
import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration;
/**
* Remove an auth-server from a realm.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public final class AuthServerRemoveHandler extends AbstractRemoveStepHandler {
public static AuthServerRemoveHandler INSTANCE = new AuthServerRemoveHandler();
private AuthServerRemoveHandler() {}
@Override
protected void performRemove(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
String deploymentName = AuthServerUtil.getDeploymentName(operation);
KeycloakAdapterConfigService.INSTANCE.removeServerDeployment(deploymentName);
if (requiresRuntime(context)) { // don't do this on a domain controller
addStepToRemoveAuthServer(context, deploymentName);
}
super.performRemove(context, operation, model);
}
private void addStepToRemoveAuthServer(OperationContext context, String deploymentName) {
PathAddress deploymentAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT, deploymentName));
ModelNode op = Util.createOperation(REMOVE, deploymentAddress);
context.addStep(op, getRemoveHandler(context, deploymentAddress), OperationContext.Stage.MODEL);
}
private OperationStepHandler getRemoveHandler(OperationContext context, PathAddress address) {
ImmutableManagementResourceRegistration rootResourceRegistration = context.getRootResourceRegistration();
return rootResourceRegistration.getOperationHandler(address, REMOVE);
}
}

View file

@ -0,0 +1,186 @@
/*
* Copyright 2014 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.extension.authserver;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationStepHandler;
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.ARCHIVE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CONTENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOY;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ENABLED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PERSISTENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PATH;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REDEPLOY;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RUNTIME_NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.UNDEPLOY;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.URL;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration;
import org.jboss.dmr.ModelNode;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoadException;
/**
* Utility methods that help assemble and start an auth server.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public class AuthServerUtil {
private static final ModuleIdentifier KEYCLOAK_SUBSYSTEM = ModuleIdentifier.create("org.keycloak.keycloak-wildfly-subsystem");
private final String authServerName;
private final PathAddress pathAddress;
private final String deploymentName;
//private String overlayName;
private final Module subsysModule;
private final String keycloakVersion;
private final boolean isAuthServerExploded;
//private File overlaysDir;
private final URI authServerUri;
//private URL serverConfig = null;
//private Set<URL> spiUrls = new HashSet<URL>();
AuthServerUtil(ModelNode operation) {
this.authServerName = getAuthServerName(operation);
this.pathAddress = getPathAddress(operation);
this.deploymentName = getDeploymentName(operation);
this.subsysModule = findSubsysModule();
this.keycloakVersion = subsysModule.getProperty("keycloak-version");
this.isAuthServerExploded = Boolean.parseBoolean(subsysModule.getProperty("auth-server-exploded"));
this.authServerUri = findAuthServerUri();
}
String getDeploymentName() {
return this.deploymentName;
}
private Module findSubsysModule() {
try {
return Module.getModuleFromCallerModuleLoader(KEYCLOAK_SUBSYSTEM);
} catch (ModuleLoadException e) {
throw new IllegalStateException("Can't find Keycloak subsystem.", e);
}
}
private URI findAuthServerUri() throws IllegalStateException {
try {
URL subsysJar = this.subsysModule.getExportedResource("keycloak-wildfly-subsystem-" + this.keycloakVersion + ".jar");
File subsysDir = new File(subsysJar.toURI()).getParentFile();
File authServerDir = new File(subsysDir, "auth-server");
if (this.isAuthServerExploded) {
return authServerDir.toURI();
} else {
return new File(authServerDir, "keycloak-server-" + keycloakVersion + ".war").toURI();
}
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
} catch (IllegalArgumentException e) {
throw new IllegalStateException(e);
}
}
void addStepToUploadAuthServer(OperationContext context, boolean isEnabled) throws OperationFailedException {
PathAddress deploymentAddress = deploymentAddress();
ModelNode op = Util.createOperation(ADD, deploymentAddress);
op.get(ENABLED).set(isEnabled);
op.get(PERSISTENT).set(false); // prevents writing this deployment out to standalone.xml
if (authServerUri == null) {
throw new OperationFailedException("Keycloak Auth Server WAR not found in keycloak-wildfly-subsystem module");
}
op.get(CONTENT).add(makeContentItem());
context.addStep(op, getHandler(context, deploymentAddress, ADD), OperationContext.Stage.MODEL);
}
private ModelNode makeContentItem() throws OperationFailedException {
ModelNode contentItem = new ModelNode();
if (this.isAuthServerExploded) {
String urlString = new File(authServerUri).getAbsolutePath();
contentItem.get(PATH).set(urlString);
contentItem.get(ARCHIVE).set(false);
} else {
String urlString = authServerUri.toString();
contentItem.get(URL).set(urlString);
}
return contentItem;
}
void addStepToRedeployAuthServer(OperationContext context) {
addDeploymentAction(context, REDEPLOY);
}
void addStepToUndeployAuthServer(OperationContext context) {
addDeploymentAction(context, UNDEPLOY);
}
void addStepToDeployAuthServer(OperationContext context) {
addDeploymentAction(context, DEPLOY);
}
private void addDeploymentAction(OperationContext context, String operation) {
PathAddress deploymentAddress = deploymentAddress();
ModelNode op = Util.createOperation(operation, deploymentAddress);
op.get(RUNTIME_NAME).set(deploymentName);
context.addStep(op, getHandler(context, deploymentAddress, operation), OperationContext.Stage.MODEL);
}
private PathAddress deploymentAddress() {
return PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT, deploymentName));
}
static OperationStepHandler getHandler(OperationContext context, PathAddress address, String opName) {
ImmutableManagementResourceRegistration rootResourceRegistration = context.getRootResourceRegistration();
return rootResourceRegistration.getOperationHandler(address, opName);
}
static String getDeploymentName(ModelNode operation) {
String deploymentName = Util.getNameFromAddress(operation.get(ADDRESS));
if (!deploymentName.toLowerCase().endsWith(".war")) {
deploymentName += ".war";
}
return deploymentName;
}
static String getAuthServerName(ModelNode operation) {
PathAddress pathAddr = getPathAddress(operation);
return pathAddr.getElement(pathAddr.size() - 1).getValue();
}
static PathAddress getPathAddress(ModelNode operation) {
return PathAddress.pathAddress(operation.get(ADDRESS));
}
}

View file

@ -0,0 +1,91 @@
/*
* Copyright 2014 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.extension.authserver;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinition;
import java.util.List;
import org.jboss.as.controller.ModelOnlyWriteAttributeHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.registry.Resource;
import org.jboss.dmr.ModelNode;
import org.keycloak.subsystem.extension.KeycloakAdapterConfigService;
/**
* Update an attribute on an Auth Server.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public class AuthServerWriteAttributeHandler extends ModelOnlyWriteAttributeHandler { //extends ReloadRequiredWriteAttributeHandler {
public AuthServerWriteAttributeHandler(List<SimpleAttributeDefinition> definitions) {
this(definitions.toArray(new AttributeDefinition[definitions.size()]));
}
public AuthServerWriteAttributeHandler(AttributeDefinition... definitions) {
super(definitions);
}
@Override
protected void finishModelStage(OperationContext context, ModelNode operation, String attributeName, ModelNode newValue, ModelNode oldValue, Resource model) throws OperationFailedException {
if (!context.isNormalServer() || attribNotChanging(attributeName, newValue, oldValue)) {
super.finishModelStage(context, operation, attributeName, newValue, oldValue, model);
return;
}
AuthServerUtil authServerUtil = new AuthServerUtil(operation);
boolean isEnabled = isEnabled(model); // is server currently enabled?
if (attributeName.equals(AuthServerDefinition.WEB_CONTEXT.getName())) {
String deploymentName = AuthServerUtil.getDeploymentName(operation);
KeycloakAdapterConfigService.INSTANCE.removeServerDeployment(deploymentName);
KeycloakAdapterConfigService.INSTANCE.addServerDeployment(deploymentName, newValue.asString());
if (isEnabled) {
authServerUtil.addStepToRedeployAuthServer(context);
}
}
if (attributeName.equals(AuthServerDefinition.ENABLED.getName())) {
if (!isEnabled) { // we are disabling
authServerUtil.addStepToUndeployAuthServer(context);
} else { // we are enabling
authServerUtil.addStepToDeployAuthServer(context);
}
}
super.finishModelStage(context, operation, attributeName, newValue, oldValue, model);
}
// Is auth server currently enabled?
private boolean isEnabled(Resource model) {
ModelNode authServer = model.getModel();
ModelNode isEnabled = authServer.get(AuthServerDefinition.ENABLED.getName());
if (!isEnabled.isDefined()) isEnabled = AuthServerDefinition.ENABLED.getDefaultValue();
return isEnabled.asBoolean();
}
private boolean attribNotChanging(String attributeName, ModelNode newValue, ModelNode oldValue) {
SimpleAttributeDefinition attribDef = AuthServerDefinition.lookup(attributeName);
if (!oldValue.isDefined()) oldValue = attribDef.getDefaultValue();
if (!newValue.isDefined()) newValue = attribDef.getDefaultValue();
return newValue.equals(oldValue);
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2014 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.extension.authserver;
import org.jboss.as.controller.OperationDefinition;
import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
/**
* Operation to overlay keycloak-server.json.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public class OverlayKeycloakServerJsonHandler extends AbstractAddOverlayHandler {
public static final String OP = "update-server-config";
public static final OverlayKeycloakServerJsonHandler INSTANCE = new OverlayKeycloakServerJsonHandler();
public static OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OP, AuthServerDefinition.rscDescriptionResolver)
.addParameter(BYTES_TO_UPLOAD)
.build();
@Override
String getOverlayPath(String fileName) {
return "/WEB-INF/classes/META-INF/keycloak-server.json";
}
}

View file

@ -1,9 +1,23 @@
keycloak.subsystem=Keycloak subsystem
keycloak.subsystem.add=Operation Adds Keycloak subsystem
keycloak.subsystem.remove=Operation removes Keycloak subsystem
keycloak.subsystem.auth-server=Keycloak Auth Server
keycloak.subsystem.realm=A Keycloak realm.
keycloak.subsystem.secure-deployment=A deployment secured by Keycloak.
keycloak.auth-server=A Keycloak Auth Server
keycloak.auth-server.add=Add an Auth Server to the subsystem.
keycloak.auth-server.remove=Remove an Auth Server from the subsystem.
keycloak.auth-server.add-provider=Add a provider service jar to the Keycloak auth server.
keycloak.auth-server.add-provider.uploaded-file-name=The file name of the provider service jar to be added or updated.
keycloak.auth-server.add-provider.bytes-to-upload=The bytes of the provider service jar to be added or updated.
keycloak.auth-server.update-server-config=Upload a new keycloak-server.json configuration file for the Keycloak auth server.
keycloak.auth-server.update-server-config.uploaded-file-name=Should be the name keycloak-server.json.
keycloak.auth-server.update-server-config.bytes-to-upload=The bytes of the keycloak-server.json file to be added or updated.
keycloak.auth-server.enabled=Enable or disable the Auth Server.
keycloak.auth-server.web-context=Web context the auth-server will use. Also, the module name of the auth-server deployment.
keycloak.realm=A Keycloak realm.
keycloak.realm.add=Add a realm definition to the subsystem.
keycloak.realm.remove=Remove a realm from the subsystem.

111
pom.xml
View file

@ -12,6 +12,7 @@
<packaging>pom</packaging>
<properties>
<aesh.version>0.33.12</aesh.version>
<bouncycastle.version>1.46</bouncycastle.version>
<jackson.version>1.9.9</jackson.version>
<keycloak.apache.httpcomponents.version>4.2.1</keycloak.apache.httpcomponents.version>
@ -35,7 +36,7 @@
<slf4j.version>1.5.10</slf4j.version>
<jboss.version>7.1.1.Final</jboss.version>
<wildfly.version>8.1.0.Final</wildfly.version>
<wildfly.core.version>1.0.0.Alpha5</wildfly.core.version>
<wildfly.core.version>1.0.0.Alpha9</wildfly.core.version>
<servlet.api.30.version>1.0.2.Final</servlet.api.30.version>
<google.zxing.version>2.2</google.zxing.version>
<google.client.version>1.14.1-beta</google.client.version>
@ -143,6 +144,17 @@
<artifactId>mail</artifactId>
<version>${javax.mail.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.aesh</groupId>
<artifactId>aesh</artifactId>
<version>${aesh.version}</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId>
@ -347,24 +359,24 @@
<version>${twitter4j.version}</version>
</dependency>
<!-- QR Code Generator -->
<!-- QR Code Generator -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>${google.zxing.version}</version>
</dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>${google.zxing.version}</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>${google.zxing.version}</version>
</dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>${google.zxing.version}</version>
</dependency>
<!-- Email Test Servers -->
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>1.3.1b</version>
</dependency>
<!-- Email Test Servers -->
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>1.3.1b</version>
</dependency>
<!-- Encrypted ZIP -->
<dependency>
@ -373,18 +385,18 @@
<version>${winzipaes.version}</version>
</dependency>
<!-- Selenium -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chrome-driver</artifactId>
<version>${selenium.version}</version>
<!-- Selenium -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chrome-driver</artifactId>
<version>${selenium.version}</version>
<scope>test</scope>
</dependency>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
@ -424,12 +436,12 @@
<version>${mysql.version}</version>
</dependency>
<!-- the dependency seems to override Resteasy 3.0.5's depending on 4.2.1
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${keycloak.apache.httpcomponents.version}</version>
</dependency>
-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${keycloak.apache.httpcomponents.version}</version>
</dependency>
-->
<dependency>
<groupId>org.wildfly.core</groupId>
<artifactId>wildfly-controller</artifactId>
@ -452,6 +464,18 @@
<type>pom</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wildfly.core</groupId>
<artifactId>wildfly-core-feature-pack</artifactId>
<type>pom</type>
<version>${wildfly.core.version}</version>
</dependency>
<dependency>
<groupId>org.wildfly.core</groupId>
<artifactId>wildfly-core-feature-pack</artifactId>
<type>zip</type>
<version>${wildfly.core.version}</version>
</dependency>
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-undertow</artifactId>
@ -486,6 +510,11 @@
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
@ -593,14 +622,14 @@
<artifactId>maven-deploy-plugin</artifactId>
<version>2.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>com.lazerycode.jmeter</groupId>
<artifactId>jmeter-maven-plugin</artifactId>