keycloak-uncached

KEYCLOAK-1758 add-user script

11/23/2015 11:45:25 AM

Changes

docbook/auth-server-docs/reference/en/en-US/modules/admin-recovery.xml 15(+0 -15)

pom.xml 12(+9 -3)

services/src/main/java/org/keycloak/offlineconfig/AdminRecovery.java 90(+0 -90)

services/src/main/java/org/keycloak/offlineconfig/OfflineConfigException.java 32(+0 -32)

testsuite/integration/src/test/java/org/keycloak/testsuite/offlineconfig/AdminRecoveryTest.java 133(+0 -133)

wildfly/pom.xml 22(+22 -0)

Details

diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java
index 986faac..e76648b 100644
--- a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java
+++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java
@@ -57,6 +57,8 @@ public class ClientRegistrationCLI {
                 .create();
 
         aeshConsole.start();
+
+
 /*
         if (args.length > 0) {
             CommandContainer command = registry.getCommand(args[0], null);
diff --git a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
index 1e57dbc..6ad9715 100755
--- a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
@@ -29,7 +29,7 @@ public class CredentialRepresentation {
     private Integer period;
 
     // only used when updating a credential.  Might set required action
-    protected boolean temporary;
+    protected Boolean temporary;
 
     public String getType() {
         return type;
@@ -79,11 +79,11 @@ public class CredentialRepresentation {
         this.hashIterations = hashIterations;
     }
 
-    public boolean isTemporary() {
+    public Boolean isTemporary() {
         return temporary;
     }
 
-    public void setTemporary(boolean temporary) {
+    public void setTemporary(Boolean temporary) {
         this.temporary = temporary;
     }
 
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 00785cf..9b76490 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -84,7 +84,6 @@ public class RealmRepresentation {
     private List<IdentityProviderRepresentation> identityProviders;
     private List<IdentityProviderMapperRepresentation> identityProviderMappers;
     private List<ProtocolMapperRepresentation> protocolMappers;
-    private Boolean identityFederationEnabled;
     protected Boolean internationalizationEnabled;
     protected Set<String> supportedLocales;
     protected String defaultLocale;
@@ -613,10 +612,6 @@ public class RealmRepresentation {
         identityProviders.add(identityProviderRepresentation);
     }
 
-    public boolean isIdentityFederationEnabled() {
-        return identityProviders != null && !identityProviders.isEmpty();
-    }
-
     public List<ProtocolMapperRepresentation> getProtocolMappers() {
         return protocolMappers;
     }
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index 8635014..0990a4e 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -17,9 +17,9 @@ public class UserRepresentation {
     protected String id;
     protected Long createdTimestamp;
     protected String username;
-    protected boolean enabled;
-    protected boolean totp;
-    protected boolean emailVerified;
+    protected Boolean enabled;
+    protected Boolean totp;
+    protected Boolean emailVerified;
     protected String firstName;
     protected String lastName;
     protected String email;
@@ -98,27 +98,27 @@ public class UserRepresentation {
         this.username = username;
     }
 
-    public boolean isEnabled() {
+    public Boolean isEnabled() {
         return enabled;
     }
 
-    public void setEnabled(boolean enabled) {
+    public void setEnabled(Boolean enabled) {
         this.enabled = enabled;
     }
 
-    public boolean isTotp() {
+    public Boolean isTotp() {
         return totp;
     }
 
-    public void setTotp(boolean totp) {
+    public void setTotp(Boolean totp) {
         this.totp = totp;
     }
 
-    public boolean isEmailVerified() {
+    public Boolean isEmailVerified() {
         return emailVerified;
     }
 
-    public void setEmailVerified(boolean emailVerified) {
+    public void setEmailVerified(Boolean emailVerified) {
         this.emailVerified = emailVerified;
     }
 
diff --git a/core/src/main/java/org/keycloak/util/JsonSerialization.java b/core/src/main/java/org/keycloak/util/JsonSerialization.java
index a1a93ba..19df33f 100755
--- a/core/src/main/java/org/keycloak/util/JsonSerialization.java
+++ b/core/src/main/java/org/keycloak/util/JsonSerialization.java
@@ -3,6 +3,7 @@ package org.keycloak.util;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.SerializationConfig;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.type.TypeReference;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -27,7 +28,10 @@ public class JsonSerialization {
 
     public static void writeValueToStream(OutputStream os, Object obj) throws IOException {
         mapper.writeValue(os, obj);
+    }
 
+    public static void writeValuePrettyToStream(OutputStream os, Object obj) throws IOException {
+        prettyMapper.writeValue(os, obj);
     }
 
     public static String writeValueAsPrettyString(Object obj) throws IOException {
@@ -53,6 +57,10 @@ public class JsonSerialization {
         return readValue(bytes, type, false);
     }
 
+    public static <T> T readValue(InputStream bytes, TypeReference<T> type) throws IOException {
+        return mapper.readValue(bytes, type);
+    }
+
     public static <T> T readValue(InputStream bytes, Class<T> type, boolean replaceSystemProperties) throws IOException {
         if (replaceSystemProperties) {
             return sysPropertiesAwareMapper.readValue(bytes, type);
diff --git a/distribution/feature-packs/server-feature-pack/pom.xml b/distribution/feature-packs/server-feature-pack/pom.xml
index 0f5dd37..6f05056 100644
--- a/distribution/feature-packs/server-feature-pack/pom.xml
+++ b/distribution/feature-packs/server-feature-pack/pom.xml
@@ -38,17 +38,25 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-wildfly-adduser</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-wildfly-extensions</artifactId>
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-wf9-server-subsystem</artifactId>
+            <artifactId>keycloak-wildfly-server-subsystem</artifactId>
         </dependency>
         <dependency>
             <groupId>org.wildfly</groupId>
             <artifactId>wildfly-feature-pack</artifactId>
             <type>zip</type>
         </dependency>
+        <dependency>
+            <groupId>org.jboss.aesh</groupId>
+            <artifactId>aesh</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.bat b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.bat
new file mode 100644
index 0000000..dca1136
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.bat
@@ -0,0 +1,73 @@
+@echo off
+rem -------------------------------------------------------------------------
+rem Add User script for Windows
+rem -------------------------------------------------------------------------
+rem
+rem A simple utility for adding new users to the properties file used
+rem for domain management authentication out of the box.
+
+rem $Id$
+
+@if not "%ECHO%" == ""  echo %ECHO%
+@if "%OS%" == "Windows_NT" setlocal
+
+if "%OS%" == "Windows_NT" (
+  set "DIRNAME=%~dp0%"
+) else (
+  set DIRNAME=.\
+)
+
+pushd "%DIRNAME%.."
+set "RESOLVED_JBOSS_HOME=%CD%"
+popd
+
+if "x%JBOSS_HOME%" == "x" (
+  set "JBOSS_HOME=%RESOLVED_JBOSS_HOME%"
+)
+
+pushd "%JBOSS_HOME%"
+set "SANITIZED_JBOSS_HOME=%CD%"
+popd
+
+if /i "%RESOLVED_JBOSS_HOME%" NEQ "%SANITIZED_JBOSS_HOME%" (
+   echo.
+   echo   WARNING: The JBOSS_HOME ^("%SANITIZED_JBOSS_HOME%"^) that this script uses points to a different installation than the one that this script resides in ^("%RESOLVED_JBOSS_HOME%"^). Unpredictable results may occur.
+   echo.
+   echo       JBOSS_HOME: "%JBOSS_HOME%"
+   echo.
+)
+
+rem Setup JBoss specific properties
+if "x%JAVA_HOME%" == "x" (
+  set  JAVA=java
+  echo JAVA_HOME is not set. Unexpected results may occur.
+  echo Set JAVA_HOME to the directory of your local JDK to avoid this message.
+) else (
+  set "JAVA=%JAVA_HOME%\bin\java"
+)
+
+rem Find jboss-modules.jar, or we can't continue
+if exist "%JBOSS_HOME%\jboss-modules.jar" (
+    set "RUNJAR=%JBOSS_HOME%\jboss-modules.jar"
+) else (
+  echo Could not locate "%JBOSS_HOME%\jboss-modules.jar".
+  echo Please check that you are in the bin directory when running this script.
+  goto END
+)
+
+rem Set default module root paths
+if "x%JBOSS_MODULEPATH%" == "x" (
+  set "JBOSS_MODULEPATH=%JBOSS_HOME%\modules"
+)
+
+rem Uncomment to override standalone and domain user location
+rem set "JAVA_OPTS=%JAVA_OPTS% -Djboss.server.config.user.dir=..\standalone\configuration -Djboss.domain.config.user.dir=..\domain\configuration"
+
+"%JAVA%" %JAVA_OPTS% ^
+    -jar "%JBOSS_HOME%\jboss-modules.jar" ^
+    -mp "%JBOSS_MODULEPATH%" ^
+     org.keycloak.keycloak-wildfly-adduser ^
+     %*
+
+:END
+if "x%NOPAUSE%" == "x" pause
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.sh b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.sh
new file mode 100755
index 0000000..1f6dfff
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+# Add User Utility
+#
+# A simple utility for adding new users to the properties file used
+# for domain management authentication out of the box.
+#
+
+DIRNAME=`dirname "$0"`
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false;
+if  [ `uname|grep -i CYGWIN` ]; then
+    cygwin=true;
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+    [ -n "$JBOSS_HOME" ] &&
+        JBOSS_HOME=`cygpath --unix "$JBOSS_HOME"`
+    [ -n "$JAVA_HOME" ] &&
+        JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+    [ -n "$JAVAC_JAR" ] &&
+        JAVAC_JAR=`cygpath --unix "$JAVAC_JAR"`
+fi
+
+# Setup JBOSS_HOME
+RESOLVED_JBOSS_HOME=`cd "$DIRNAME/.."; pwd`
+if [ "x$JBOSS_HOME" = "x" ]; then
+    # get the full path (without any relative bits)
+    JBOSS_HOME=$RESOLVED_JBOSS_HOME
+else
+ SANITIZED_JBOSS_HOME=`cd "$JBOSS_HOME"; pwd`
+ if [ "$RESOLVED_JBOSS_HOME" != "$SANITIZED_JBOSS_HOME" ]; then
+   echo "WARNING: The JBOSS_HOME ($SANITIZED_JBOSS_HOME) that this script uses points to a different installation than the one that this script resides in ($RESOLVED_JBOSS_HOME). Unpredictable results may occur."
+   echo ""
+ fi
+fi
+export JBOSS_HOME
+
+# Setup the JVM
+if [ "x$JAVA" = "x" ]; then
+    if [ "x$JAVA_HOME" != "x" ]; then
+        JAVA="$JAVA_HOME/bin/java"
+    else
+        JAVA="java"
+    fi
+fi
+
+if [ "x$JBOSS_MODULEPATH" = "x" ]; then
+    JBOSS_MODULEPATH="$JBOSS_HOME/modules"
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+    JBOSS_HOME=`cygpath --path --windows "$JBOSS_HOME"`
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+    JBOSS_MODULEPATH=`cygpath --path --windows "$JBOSS_MODULEPATH"`
+fi
+
+# Sample JPDA settings for remote socket debugging
+#JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=y"
+# Uncomment to override standalone and domain user location  
+#JAVA_OPTS="$JAVA_OPTS -Djboss.server.config.user.dir=../standalone/configuration -Djboss.domain.config.user.dir=../domain/configuration"
+
+JAVA_OPTS="$JAVA_OPTS"
+
+eval \"$JAVA\" $JAVA_OPTS \
+         -jar \""$JBOSS_HOME"/jboss-modules.jar\" \
+         -mp \""${JBOSS_MODULEPATH}"\" \
+         org.keycloak.keycloak-wildfly-adduser \
+         '"$@"'
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/jboss/aesh/0.65/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/jboss/aesh/0.65/module.xml
new file mode 100644
index 0000000..4166dbd
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/jboss/aesh/0.65/module.xml
@@ -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" slot="0.65">
+    <properties>
+        <property name="jboss.api" value="private"/>
+    </properties>
+
+    <resources>
+        <artifact name="${org.jboss.aesh:aesh}"/>
+    </resources>
+
+    <dependencies>
+        <module name="org.fusesource.jansi" />
+    </dependencies>
+</module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/module.xml
index d6e7d81..8002aa2 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/module.xml
@@ -29,6 +29,6 @@
     </resources>
 
     <dependencies>
-        <module name="org.keycloak.keycloak-wf9-server-subsystem" services="export" export="true"/>
+        <module name="org.keycloak.keycloak-wildfly-server-subsystem" services="export" export="true"/>
     </dependencies>
 </module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adduser/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adduser/main/module.xml
new file mode 100755
index 0000000..d27499b
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adduser/main/module.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-wildfly-adduser">
+    <main-class name="org.keycloak.wildfly.adduser.AddUser"/>
+    <resources>
+        <artifact name="${org.keycloak:keycloak-wildfly-adduser}"/>
+    </resources>
+    <dependencies>
+        <module name="org.keycloak.keycloak-common"/>
+        <module name="org.keycloak.keycloak-core"/>
+        <module name="org.keycloak.keycloak-model-api"/>
+        <module name="org.jboss.aesh" slot="0.65"/>
+        <module name="org.jboss.as.domain-management"/>
+        <module name="org.codehaus.jackson.jackson-core-asl"/>
+    </dependencies>
+</module>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/build.xml b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
index 9f60836..e8086dc 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/build.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
@@ -274,8 +274,8 @@
 
         <!-- subsystems -->
 
-        <module-def name="org.keycloak.keycloak-as7-server-subsystem">
-            <maven-resource group="org.keycloak" artifact="keycloak-as7-server-subsystem"/>
+        <module-def name="org.keycloak.keycloak-eap6-server-subsystem">
+            <maven-resource group="org.keycloak" artifact="keycloak-eap6-server-subsystem"/>
         </module-def>
 
         <module-def name="org.keycloak.keycloak-server-subsystem"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/pom.xml b/distribution/server-overlay/eap6/eap6-server-modules/pom.xml
index 91b2969..b3225fd 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/pom.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/pom.xml
@@ -32,7 +32,7 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-as7-server-subsystem</artifactId>
+            <artifactId>keycloak-eap6-server-subsystem</artifactId>
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/main/module.xml
index 90939b0..4829258 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/main/module.xml
@@ -30,6 +30,6 @@
     </resources>
 
     <dependencies>
-        <module name="org.keycloak.keycloak-as7-server-subsystem" services="export" export="true"/>
+        <module name="org.keycloak.keycloak-eap6-server-subsystem" services="export" export="true"/>
     </dependencies>
 </module>
diff --git a/docbook/auth-server-docs/reference/en/en-US/master.xml b/docbook/auth-server-docs/reference/en/en-US/master.xml
index 16a8601..d6bf5ff 100755
--- a/docbook/auth-server-docs/reference/en/en-US/master.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/master.xml
@@ -37,7 +37,6 @@
                 <!ENTITY UserFederation SYSTEM "modules/user-federation.xml">
                 <!ENTITY Kerberos SYSTEM "modules/kerberos.xml">
                 <!ENTITY ExportImport SYSTEM "modules/export-import.xml">
-                <!ENTITY AdminRecovery SYSTEM "modules/admin-recovery.xml">
                 <!ENTITY ServerCache SYSTEM "modules/cache.xml">
                 <!ENTITY SecurityVulnerabilities SYSTEM "modules/security-vulnerabilities.xml">
                 <!ENTITY Clustering SYSTEM "modules/clustering.xml">
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
index 78d9a4b..01ad7e6 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
@@ -128,6 +128,25 @@ cd &lt;WILDFLY_HOME&gt;/bin
             </itemizedlist>
         </para>
         <section>
+            <title>Admin User</title>
+            <para>
+                To access the admin console you need an account to login. Currently, there's a default account added
+                with the username <literal>admin</literal> and password <literal>admin</literal>. You will be required
+                to change the password on first login. We are planning on removing the built-in account soon and will
+                instead have an initial step to create the user.
+            </para>
+            <para>
+                You can also create a user with the <literal>add-user</literal> script found in <literal>bin</literal>.
+                This script will create a temporary file with the details of the user, which are imported at startup.
+                To add a user with this script run:
+<programlisting><![CDATA[
+bin/add-user.[sh|bat] -r master -u <username> -p <password>
+]]></programlisting>
+                Then restart the server.
+            </para>
+        </section>
+
+        <section>
             <title>Relational Database Configuration</title>
             <para>
                 You might want to use a better relational database for Keycloak like PostgreSQL or MySQL.  You might also
diff --git a/integration/as7-eap6/pom.xml b/integration/as7-eap6/pom.xml
index e1bca6a..52a1053 100755
--- a/integration/as7-eap6/pom.xml
+++ b/integration/as7-eap6/pom.xml
@@ -17,6 +17,5 @@
         <module>as7-adapter-spi</module>
         <module>as7-adapter</module>
         <module>as7-subsystem</module>
-        <module>as7-server-subsystem</module>
     </modules>
 </project>
\ No newline at end of file
diff --git a/integration/wildfly/pom.xml b/integration/wildfly/pom.xml
index 141cd87..3afdcc6 100644
--- a/integration/wildfly/pom.xml
+++ b/integration/wildfly/pom.xml
@@ -15,9 +15,7 @@
 
     <modules>
         <module>wildfly-adapter</module>
-        <module>wildfly-extensions</module>
         <module>wf8-subsystem</module>
         <module>wf9-subsystem</module>
-        <module>wf9-server-subsystem</module>
     </modules>
 </project>
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 3916fd1..dd82098 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -996,14 +996,14 @@ public class RepresentationToModel {
 
         // Import users just to user storage. Don't federate
         UserModel user = session.userStorage().addUser(newRealm, userRep.getId(), userRep.getUsername(), false, false);
-        user.setEnabled(userRep.isEnabled());
+        user.setEnabled(userRep.isEnabled() != null && userRep.isEnabled());
         user.setCreatedTimestamp(userRep.getCreatedTimestamp());
         user.setEmail(userRep.getEmail());
-        user.setEmailVerified(userRep.isEmailVerified());
+        if (userRep.isEmailVerified() != null) user.setEmailVerified(userRep.isEmailVerified());
         user.setFirstName(userRep.getFirstName());
         user.setLastName(userRep.getLastName());
         user.setFederationLink(userRep.getFederationLink());
-        user.setOtpEnabled(userRep.isTotp());
+        if (userRep.isTotp() != null) user.setOtpEnabled(userRep.isTotp());
         if (userRep.getAttributes() != null) {
             for (Map.Entry<String, Object> entry : userRep.getAttributes().entrySet()) {
                 Object value = entry.getValue();

pom.xml 12(+9 -3)

diff --git a/pom.xml b/pom.xml
index c137e59..62ae25c 100755
--- a/pom.xml
+++ b/pom.xml
@@ -76,7 +76,7 @@
         <log4j.version>1.2.17</log4j.version>
         <greenmail.version>1.3.1b</greenmail.version>
         <xmlsec.version>1.5.1</xmlsec.version>
-        <aesh.version>0.66</aesh.version>
+        <aesh.version>0.65.1</aesh.version>
 
         <enforcer.plugin.version>1.4</enforcer.plugin.version>
         <jboss.as.plugin.version>7.5.Final</jboss.as.plugin.version>
@@ -154,6 +154,7 @@
         <module>timer</module>
         <module>export-import</module>
         <module>util</module>
+        <module>wildfly</module>
     </modules>
 
     <dependencyManagement>
@@ -827,7 +828,7 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
-                <artifactId>keycloak-as7-server-subsystem</artifactId>
+                <artifactId>keycloak-eap6-server-subsystem</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
@@ -842,7 +843,7 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
-                <artifactId>keycloak-wf9-server-subsystem</artifactId>
+                <artifactId>keycloak-wildfly-server-subsystem</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
@@ -947,6 +948,11 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-wildfly-adduser</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-wildfly-extensions</artifactId>
                 <version>${project.version}</version>
             </dependency>
diff --git a/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java b/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java
index 89dc097..cedcbdd 100644
--- a/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java
+++ b/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java
@@ -60,7 +60,8 @@ public class ExportImportManager {
 	                        
 	                        // Check if master realm was exported. If it's not, then it needs to be created before other realms are imported
 	                        if (!importProvider.isMasterRealmExported()) {
-	                            new ApplianceBootstrap().bootstrap(sessionFactory, contextPath);
+	                            ApplianceBootstrap.setupDefaultRealm(sessionFactory, contextPath);
+                                ApplianceBootstrap.setupDefaultUser(sessionFactory);
 	                        }
 	
 	                        importProvider.importModel(sessionFactory, strategy);
@@ -69,7 +70,8 @@ public class ExportImportManager {
 	
 	                        if (!realmName.equals(Config.getAdminRealm())) {
 	                            // Check if master realm exists. If it's not, then it needs to be created before other realm is imported
-	                            new ApplianceBootstrap().bootstrap(sessionFactory, contextPath);
+                                ApplianceBootstrap.setupDefaultRealm(sessionFactory, contextPath);
+                                ApplianceBootstrap.setupDefaultUser(sessionFactory);
 	                        }
 	
 	                        importProvider.importRealm(sessionFactory, realmName, strategy);
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
index 5178cec..0fa1b92 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -23,62 +23,71 @@ public class ApplianceBootstrap {
 
     private static final Logger logger = Logger.getLogger(ApplianceBootstrap.class);
 
-    public void bootstrap(KeycloakSessionFactory sessionFactory, String contextPath) {
+    public static boolean setupDefaultRealm(KeycloakSessionFactory sessionFactory, String contextPath) {
         KeycloakSession session = sessionFactory.create();
         session.getTransaction().begin();
 
         try {
-            bootstrap(session, contextPath);
+            String adminRealmName = Config.getAdminRealm();
+            if (session.realms().getRealm(adminRealmName) != null) {
+                return false;
+            }
+
+            logger.info("Initializing " + adminRealmName + " realm");
+
+            RealmManager manager = new RealmManager(session);
+            manager.setContextPath(contextPath);
+            RealmModel realm = manager.createRealm(adminRealmName, adminRealmName);
+            realm.setName(adminRealmName);
+            realm.setEnabled(true);
+            realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
+            realm.setSsoSessionIdleTimeout(1800);
+            realm.setAccessTokenLifespan(60);
+            realm.setSsoSessionMaxLifespan(36000);
+            realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
+            realm.setAccessCodeLifespan(60);
+            realm.setAccessCodeLifespanUserAction(300);
+            realm.setAccessCodeLifespanLogin(1800);
+            realm.setSslRequired(SslRequired.EXTERNAL);
+            realm.setRegistrationAllowed(false);
+            realm.setRegistrationEmailAsUsername(false);
+            KeycloakModelUtils.generateRealmKeys(realm);
+
             session.getTransaction().commit();
+            return true;
         } finally {
             session.close();
         }
     }
 
-    public void bootstrap(KeycloakSession session, String contextPath) {
-        String adminRealmName = Config.getAdminRealm();
-        if (session.realms().getRealm(adminRealmName) != null) {
-            return;
-        }
-
-        logger.info("Initializing " + adminRealmName + " realm");
-
-        RealmManager manager = new RealmManager(session);
-        manager.setContextPath(contextPath);
-        RealmModel realm = manager.createRealm(adminRealmName, adminRealmName);
-        realm.setName(adminRealmName);
-        realm.setEnabled(true);
-        realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
-        realm.setSsoSessionIdleTimeout(1800);
-        realm.setAccessTokenLifespan(60);
-        realm.setSsoSessionMaxLifespan(36000);
-        realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
-        realm.setAccessCodeLifespan(60);
-        realm.setAccessCodeLifespanUserAction(300);
-        realm.setAccessCodeLifespanLogin(1800);
-        realm.setSslRequired(SslRequired.EXTERNAL);
-        realm.setRegistrationAllowed(false);
-        realm.setRegistrationEmailAsUsername(false);
-        KeycloakModelUtils.generateRealmKeys(realm);
+    public static boolean setupDefaultUser(KeycloakSessionFactory sessionFactory) {
+        KeycloakSession session = sessionFactory.create();
+        session.getTransaction().begin();
 
-        UserModel adminUser = session.users().addUser(realm, "admin");
-        setupAdminUser(session, realm, adminUser, "admin");
-    }
+        try {
+            RealmModel realm = session.realms().getRealm(Config.getAdminRealm());
+            if (session.users().getUserByUsername("admin", realm) == null) {
+                UserModel adminUser = session.users().addUser(realm, "admin");
 
-    public static void setupAdminUser(KeycloakSession session, RealmModel realm, UserModel adminUser, String password) {
-        adminUser.setEnabled(true);
-        UserCredentialModel usrCredModel = new UserCredentialModel();
-        usrCredModel.setType(UserCredentialModel.PASSWORD);
-        usrCredModel.setValue(password);
-        session.users().updateCredential(realm, adminUser, usrCredModel);
-        adminUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+                adminUser.setEnabled(true);
+                UserCredentialModel usrCredModel = new UserCredentialModel();
+                usrCredModel.setType(UserCredentialModel.PASSWORD);
+                usrCredModel.setValue("admin");
+                session.users().updateCredential(realm, adminUser, usrCredModel);
+                adminUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
 
-        RoleModel adminRole = realm.getRole(AdminRoles.ADMIN);
-        adminUser.grantRole(adminRole);
+                RoleModel adminRole = realm.getRole(AdminRoles.ADMIN);
+                adminUser.grantRole(adminRole);
 
-        ClientModel accountApp = realm.getClientNameMap().get(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
-        for (String r : accountApp.getDefaultRoles()) {
-            adminUser.grantRole(accountApp.getRole(r));
+                ClientModel accountApp = realm.getClientNameMap().get(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
+                for (String r : accountApp.getDefaultRoles()) {
+                    adminUser.grantRole(accountApp.getRole(r));
+                }
+            }
+            session.getTransaction().commit();
+            return true;
+        } finally {
+            session.close();
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index a85c9d2..2028610 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -148,7 +148,7 @@ public class UsersResource {
                 attrsToRemove = Collections.emptySet();
             }
 
-            if (rep.isEnabled()) {
+            if (rep.isEnabled() != null && rep.isEnabled()) {
                 UsernameLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, rep.getUsername());
                 if (failureModel != null) {
                     failureModel.clearFailures();
@@ -219,9 +219,9 @@ public class UsersResource {
         user.setFirstName(rep.getFirstName());
         user.setLastName(rep.getLastName());
 
-        user.setEnabled(rep.isEnabled());
-        user.setOtpEnabled(rep.isTotp());
-        user.setEmailVerified(rep.isEmailVerified());
+        if (rep.isEnabled() != null) user.setEnabled(rep.isEnabled());
+        if (rep.isTotp() != null) user.setOtpEnabled(rep.isTotp());
+        if (rep.isEmailVerified() != null) user.setEmailVerified(rep.isEmailVerified());
 
         List<String> reqActions = rep.getRequiredActions();
 
@@ -708,7 +708,7 @@ public class UsersResource {
         } catch (ModelReadOnlyException mre) {
             throw new BadRequestException("Can't reset password as account is read only");
         }
-        if (pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+        if (pass.isTemporary() != null && pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
 
         adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index a07999d..125b6ee 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -2,6 +2,7 @@ package org.keycloak.services.resources;
 
 import org.codehaus.jackson.JsonNode;
 import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.core.Dispatcher;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
@@ -11,9 +12,11 @@ import org.keycloak.migration.MigrationModelManager;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.PostMigrationEvent;
-import org.keycloak.offlineconfig.AdminRecovery;
+import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.services.DefaultKeycloakSessionFactory;
 import org.keycloak.services.managers.ApplianceBootstrap;
 import org.keycloak.services.managers.BruteForceProtector;
@@ -36,10 +39,7 @@ import javax.ws.rs.core.UriInfo;
 import java.io.*;
 import java.net.URI;
 import java.net.URL;
-import java.util.HashSet;
-import java.util.Properties;
-import java.util.Set;
-import java.util.StringTokenizer;
+import java.util.*;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -81,7 +81,7 @@ public class KeycloakApplication extends Application {
 
         singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false"))));
 
-        setupDefaultRealm(context.getContextPath());
+        boolean defaultRealmCreated = ApplianceBootstrap.setupDefaultRealm(sessionFactory, context.getContextPath());
 
         migrateModel();
         sessionFactory.publish(new PostMigrationEvent());
@@ -89,7 +89,11 @@ public class KeycloakApplication extends Application {
         new ExportImportManager().checkExportImport(this.sessionFactory, context.getContextPath());
         importRealms(context);
 
-        AdminRecovery.recover(sessionFactory);
+        importAddUser();
+
+        if (defaultRealmCreated) {
+            ApplianceBootstrap.setupDefaultUser(sessionFactory);
+        }
 
         setupScheduledTasks(sessionFactory);
     }
@@ -153,10 +157,6 @@ public class KeycloakApplication extends Application {
         }
     }
 
-    protected void setupDefaultRealm(String contextPath) {
-        new ApplianceBootstrap().bootstrap(sessionFactory, contextPath);
-    }
-
     public static KeycloakSessionFactory createSessionFactory() {
         DefaultKeycloakSessionFactory factory = new DefaultKeycloakSessionFactory();
         factory.init();
@@ -254,6 +254,44 @@ public class KeycloakApplication extends Application {
         }
     }
 
+    public void importAddUser() {
+        String configDir = System.getProperty("jboss.server.config.dir");
+        if (configDir != null) {
+            File addUserFile = new File(configDir + File.separator + "keycloak-add-user.json");
+            if (addUserFile.isFile()) {
+                log.info("Importing users from '" + addUserFile + "'");
+
+                KeycloakSession session = sessionFactory.create();
+                try {
+                    session.getTransaction().begin();
+
+                    List<RealmRepresentation> realms = JsonSerialization.readValue(new FileInputStream(addUserFile), new TypeReference<List<RealmRepresentation>>() {});
+                    for (RealmRepresentation r : realms) {
+                        RealmModel realm = session.realms().getRealmByName(r.getRealm());
+                        if (realm == null) {
+                            throw new Exception("Realm '" + r.getRealm() + "' not found");
+                        }
+
+                        for (UserRepresentation u : r.getUsers()) {
+                            RepresentationToModel.createUser(session, realm, u, realm.getClientNameMap());
+                        }
+                    }
+
+                    session.getTransaction().commit();
+
+                    if (!addUserFile.delete()) {
+                        log.error("Failed to delete '" + addUserFile + "'");
+                    }
+                } catch (Throwable t) {
+                    session.getTransaction().rollback();
+                    log.error("Failed to import users from '" + addUserFile + "'", t);
+                } finally {
+                    session.close();
+                }
+            }
+        }
+    }
+
     private static <T> T loadJson(InputStream is, Class<T> type) {
         try {
             return JsonSerialization.readValue(is, type);
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index a14a006..2865040 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -32,6 +32,10 @@
             <artifactId>keycloak-admin-client</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-wildfly-adduser</artifactId>
+        </dependency>
+        <dependency>
             <groupId>log4j</groupId>
             <artifactId>log4j</artifactId>
         </dependency>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
new file mode 100644
index 0000000..c3f68db
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
@@ -0,0 +1,64 @@
+package org.keycloak.testsuite.adduser;
+
+import org.junit.*;
+import org.junit.rules.TemporaryFolder;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.models.Constants;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.KeycloakServer;
+import org.keycloak.wildfly.adduser.AddUser;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AddUserTest {
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
+    private File dir;
+
+    @Before
+    public void before() throws IOException {
+        dir = folder.newFolder();
+        System.setProperty("jboss.server.config.user.dir", dir.getAbsolutePath());
+        System.setProperty("jboss.server.config.dir", dir.getAbsolutePath());
+    }
+
+    @After
+    public void after() {
+        System.getProperties().remove("jboss.server.config.user.dir");
+        System.getProperties().remove("jboss.server.config.dir");
+    }
+
+    @Test
+    public void addUserTest() throws Throwable {
+        AddUser.main(new String[]{"-u", "addusertest-admin", "-p", "password"});
+        Assert.assertEquals(1, dir.listFiles().length);
+
+        KeycloakServer server = new KeycloakServer();
+        try {
+            server.start();
+
+            Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "addusertest-admin", "password", Constants.ADMIN_CONSOLE_CLIENT_ID);
+            keycloak.realms().findAll();
+
+            RealmRepresentation testRealm = new RealmRepresentation();
+            testRealm.setEnabled(true);
+            testRealm.setId("test");
+            testRealm.setRealm("test");
+
+            keycloak.realms().create(testRealm);
+
+            keycloak.close();
+
+            Assert.assertEquals(0, dir.listFiles().length);
+        } finally {
+            server.stop();
+        }
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
index 74df6c8..8fc4cd6 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
@@ -22,6 +22,8 @@
 package org.keycloak.testsuite.rule;
 
 import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
 import org.keycloak.Config;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/users/UserAttributesForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/users/UserAttributesForm.java
index 85b32e6..e508e71 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/users/UserAttributesForm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/users/UserAttributesForm.java
@@ -130,8 +130,8 @@ public class UserAttributesForm extends Form {
         setEmail(user.getEmail());
         setFirstName(user.getFirstName());
         setLastName(user.getLastName());
-        setEnabled(user.isEnabled());
-        setEmailVerified(user.isEmailVerified());
+        if (user.isEnabled() != null) setEnabled(user.isEnabled());
+        if (user.isEmailVerified() != null) setEmailVerified(user.isEmailVerified());
         setRequiredActions(user.getRequiredActions());
     }
 
diff --git a/wildfly/adduser/pom.xml b/wildfly/adduser/pom.xml
new file mode 100755
index 0000000..8a2bc8b
--- /dev/null
+++ b/wildfly/adduser/pom.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+~ Copyright 2013 JBoss Inc
+~
+~ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.keycloak</groupId>
+        <artifactId>keycloak-wildfly-parent</artifactId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>keycloak-wildfly-adduser</artifactId>
+    <name>Keycloak WildFly Add User Script</name>
+    <description/>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.wildfly.core</groupId>
+            <artifactId>wildfly-domain-management</artifactId>
+            <version>${wildfly.core.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.aesh</groupId>
+            <artifactId>aesh</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java b/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java
new file mode 100644
index 0000000..7be53c6
--- /dev/null
+++ b/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java
@@ -0,0 +1,292 @@
+package org.keycloak.wildfly.adduser;
+
+import org.codehaus.jackson.type.TypeReference;
+import org.jboss.aesh.cl.CommandDefinition;
+import org.jboss.aesh.cl.Option;
+import org.jboss.aesh.cl.parser.ParserGenerator;
+import org.jboss.aesh.console.command.Command;
+import org.jboss.aesh.console.command.CommandNotFoundException;
+import org.jboss.aesh.console.command.CommandResult;
+import org.jboss.aesh.console.command.container.CommandContainer;
+import org.jboss.aesh.console.command.invocation.CommandInvocation;
+import org.jboss.aesh.console.command.registry.AeshCommandRegistryBuilder;
+import org.jboss.aesh.console.command.registry.CommandRegistry;
+import org.keycloak.common.util.Base64;
+import org.keycloak.models.Constants;
+import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.util.JsonSerialization;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AddUser {
+
+    private static final String COMMAND_NAME = "add-user";
+    private static final int DEFAULT_HASH_ITERATIONS = 100000;
+
+    public static void main(String[] args) throws Exception {
+        AddUserCommand command = new AddUserCommand();
+        try {
+            ParserGenerator.parseAndPopulate(command, COMMAND_NAME, args);
+        } catch (Exception e) {
+            System.err.println(e.getMessage());
+            System.exit(1);
+        }
+
+        if (command.isContainer()) {
+            List<String> l = new LinkedList<>(Arrays.asList(args));
+            l.remove("--container");
+            args = l.toArray(new String[l.size()]);
+
+            org.jboss.as.domain.management.security.adduser.AddUser.main(args);
+        } else  if (command.isHelp()) {
+            printHelp(command);
+        } else {
+            try {
+                checkRequired(command, "user");
+                checkRequired(command, "password");
+
+                File addUserFile = getAddUserFile(command);
+
+                createUser(addUserFile, command.getRealm(), command.getUser(), command.getPassword(), command.getRoles(), command.getIterations());
+            } catch (Exception e) {
+                System.err.println(e.getMessage());
+                System.exit(1);
+            }
+        }
+    }
+
+    private static File getAddUserFile(AddUserCommand command) throws Exception {
+        File configDir;
+        if (command.isDomain()) {
+            if (command.getDc() != null) {
+                configDir = new File(command.getDc());
+            } else if (System.getProperty("jboss.domain.config.user.dir") != null) {
+                configDir = new File(System.getProperty("jboss.domain.config.user.dir"));
+            } else if (System.getenv("JBOSS_HOME") != null) {
+                configDir = new File(System.getenv("JBOSS_HOME") + File.separator + "domain" + File.separator + "configuration");
+            } else {
+                throw new Exception("Could not find domain configuration directory");
+            }
+        } else {
+            if (command.getSc() != null) {
+                configDir = new File(command.getSc());
+            } else if (System.getProperty("jboss.server.config.user.dir") != null) {
+                configDir = new File(System.getProperty("jboss.server.config.user.dir"));
+            } else if (System.getenv("JBOSS_HOME") != null) {
+                configDir = new File(System.getenv("JBOSS_HOME") + File.separator + "standalone" + File.separator + "configuration");
+            } else {
+                throw new Exception("Could not find standalone configuration directory");
+            }
+        }
+
+        if (!configDir.isDirectory()) {
+            throw new Exception("'" + configDir + "' does not exist or is not a directory");
+        }
+
+        File addUserFile = new File(configDir, "keycloak-add-user.json");
+        return addUserFile;
+    }
+
+    private static void createUser(File addUserFile, String realmName, String userName, String password, String rolesString, int iterations) throws Exception {
+        List<RealmRepresentation> realms;
+        if (addUserFile.isFile()) {
+            realms = JsonSerialization.readValue(new FileInputStream(addUserFile), new TypeReference<List<RealmRepresentation>>() {});
+        } else {
+            realms = new LinkedList<>();
+        }
+
+        if (realmName == null) {
+            realmName = "master";
+        }
+
+        RealmRepresentation realm = null;
+        for (RealmRepresentation r : realms) {
+            if (r.getRealm().equals(realmName)) {
+                realm = r;
+            }
+        }
+
+        if (realm == null) {
+            realm = new RealmRepresentation();
+            realm.setRealm(realmName);
+            realms.add(realm);
+            realm.setUsers(new LinkedList<UserRepresentation>());
+        }
+
+        for (UserRepresentation u : realm.getUsers()) {
+            if (u.getUsername().equals(userName)) {
+                throw new Exception("User with username '" + userName + "' already added to '" + addUserFile + "'");
+            }
+        }
+
+        UserRepresentation user = new UserRepresentation();
+        user.setEnabled(true);
+        user.setUsername(userName);
+        user.setCredentials(new LinkedList<CredentialRepresentation>());
+
+        byte[] salt = Pbkdf2PasswordEncoder.getSalt();
+        iterations = iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS;
+
+        CredentialRepresentation credentials = new CredentialRepresentation();
+        credentials.setType(CredentialRepresentation.PASSWORD);
+        credentials.setHashIterations(iterations);
+        credentials.setSalt(Base64.encodeBytes(salt));
+        credentials.setHashedSaltedValue(new Pbkdf2PasswordEncoder(salt).encode(password, iterations));
+
+        user.getCredentials().add(credentials);
+
+        String[] roles;
+        if (rolesString != null) {
+            roles = rolesString.split(",");
+        } else {
+            if (realmName.equals("master")) {
+                roles = new String[] { "admin" };
+            } else {
+                roles = new String[] { "realm-management/realm-admin" };
+            }
+        }
+
+        for (String r : roles) {
+            if (r.indexOf('/') != -1) {
+                String[] cr = r.split("/");
+                String client = cr[0];
+                String clientRole = cr[1];
+
+                if (user.getClientRoles() == null) {
+                    user.setClientRoles(new HashMap<String, List<String>>());
+                }
+
+                if (user.getClientRoles().get(client) == null) {
+                    user.getClientRoles().put(client, new LinkedList<String>());
+                }
+
+                user.getClientRoles().get(client).add(clientRole);
+            } else {
+                if (user.getRealmRoles() == null) {
+                    user.setRealmRoles(new LinkedList<String>());
+                }
+                user.getRealmRoles().add(r);
+            }
+        }
+
+        realm.getUsers().add(user);
+
+        JsonSerialization.writeValuePrettyToStream(new FileOutputStream(addUserFile), realms);
+        System.out.println("Added '" + userName + "' to '" + addUserFile + "', restart server to load user");
+    }
+
+    private static void checkRequired(Command command, String field) throws Exception {
+        Method m = command.getClass().getMethod("get" + Character.toUpperCase(field.charAt(0)) + field.substring(1));
+        if (m.invoke(command) == null) {
+            Option option = command.getClass().getDeclaredField(field).getAnnotation(Option.class);
+            String optionName;
+            if (option != null && option.shortName() != '\u0000') {
+                optionName = "-" + option.shortName() + ", --" + field;
+            } else {
+                optionName = "--" + field;
+            }
+            throw new Exception("Option: " + optionName + " is required");
+        }
+    }
+
+    private static void printHelp(Command command) throws CommandNotFoundException {
+        CommandRegistry registry = new AeshCommandRegistryBuilder().command(command).create();
+        CommandContainer commandContainer = registry.getCommand(command.getClass().getAnnotation(CommandDefinition.class).name(), null);
+        String help = commandContainer.printHelp(null);
+        System.out.println(help);
+    }
+
+    @CommandDefinition(name= COMMAND_NAME, description = "[options...]")
+    public static class AddUserCommand implements Command {
+
+        @Option(shortName = 'r', hasValue = true, description = "Name of realm to add user to")
+        private String realm;
+
+        @Option(shortName = 'u', hasValue = true, description = "Name of the user")
+        private String user;
+
+        @Option(shortName = 'p', hasValue = true, description = "Password of the user")
+        private String password;
+
+        @Option(hasValue = true, description = "Roles to add to the user")
+        private String roles;
+
+        @Option(hasValue = true, description = "Hash iterations")
+        private int iterations;
+
+        @Option(hasValue = false, description = "Enable domain mode")
+        private boolean domain;
+
+        @Option(hasValue = false, description = "Add user to underlying container. For usage use '--container --help'")
+        private boolean container;
+
+        @Option(hasValue = true, description = "Define the location of the server config directory")
+        private String sc;
+
+        @Option(hasValue = true, description = "Define the location of the domain config directory")
+        private String dc;
+
+        @Option(shortName = 'h', hasValue = false, description = "Display this help and exit")
+        private boolean help;
+
+        @Override
+        public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException {
+            return CommandResult.SUCCESS;
+        }
+
+        public String getRealm() {
+            return realm;
+        }
+
+        public String getUser() {
+            return user;
+        }
+
+        public String getPassword() {
+            return password;
+        }
+
+        public String getRoles() {
+            return roles;
+        }
+
+        public int getIterations() {
+            return iterations;
+        }
+
+        public boolean isDomain() {
+            return domain;
+        }
+
+        public boolean isContainer() {
+            return container;
+        }
+
+        public String getSc() {
+            return sc;
+        }
+
+        public String getDc() {
+            return dc;
+        }
+
+        public boolean isHelp() {
+            return help;
+        }
+    }
+
+}
\ No newline at end of file

wildfly/pom.xml 22(+22 -0)

diff --git a/wildfly/pom.xml b/wildfly/pom.xml
new file mode 100755
index 0000000..0d24ee8
--- /dev/null
+++ b/wildfly/pom.xml
@@ -0,0 +1,22 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+
+    <name>Keycloak WildFly Integration</name>
+    <description/>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-wildfly-parent</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>adduser</module>
+        <module>extensions</module>
+        <module>server-subsystem</module>
+        <module>server-eap6-subsystem</module>
+    </modules>
+</project>