Merge pull request #811 from ssilvert/deploy-auth-in-subsys
KEYCLOAK-795 Move Auth Server into Keycloak Subsystem
This commit is contained in:
commit
31050e0580
33 changed files with 1357 additions and 103 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -9,10 +9,11 @@
|
|||
.settings
|
||||
.classpath
|
||||
|
||||
|
||||
# NetBeans #
|
||||
############
|
||||
nbactions.xml
|
||||
nb-configuration.xml
|
||||
catalog.xml
|
||||
|
||||
# Compiled source #
|
||||
###################
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,14 +72,14 @@
|
|||
</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()|@*"/>
|
||||
<socket-binding name="management-native" interface="management" port="9999"/>
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:template match="@*|node()">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="@*|node()" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -52,7 +52,12 @@
|
|||
|
||||
<!-- 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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
Binary file not shown.
|
@ -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"/>
|
||||
|
|
|
@ -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 <WILDFLY_HOME>/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>
|
||||
|
|
BIN
docbook/reference/en/images/add-provider-dialog.png
Normal file
BIN
docbook/reference/en/images/add-provider-dialog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
BIN
docbook/reference/en/images/add-provider-select.png
Normal file
BIN
docbook/reference/en/images/add-provider-select.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
BIN
docbook/reference/en/images/domain-mode.png
Normal file
BIN
docbook/reference/en/images/domain-mode.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 103 KiB |
BIN
docbook/reference/en/images/update-server-config-dialog.png
Normal file
BIN
docbook/reference/en/images/update-server-config-dialog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
BIN
docbook/reference/en/images/update-server-config-select.png
Normal file
BIN
docbook/reference/en/images/update-server-config-select.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
|
@ -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,9 +54,8 @@
|
|||
<includes>
|
||||
<include>**/*TestCase.java</include>
|
||||
</includes>
|
||||
<forkMode>once</forkMode>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -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
111
pom.xml
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue