Merge pull request #1859 from stianst/reset-pass
KEYCLOAK-1758 add-user script
This commit is contained in:
commit
3685a185d4
82 changed files with 823 additions and 384 deletions
|
@ -57,6 +57,8 @@ public class ClientRegistrationCLI {
|
|||
.create();
|
||||
|
||||
aeshConsole.start();
|
||||
|
||||
|
||||
/*
|
||||
if (args.length > 0) {
|
||||
CommandContainer command = registry.getCommand(args[0], null);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -36,19 +36,27 @@
|
|||
<artifactId>keycloak-dependencies-server-all</artifactId>
|
||||
<type>pom</type>
|
||||
</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>
|
||||
|
|
|
@ -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
|
|
@ -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 \
|
||||
'"$@"'
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -22,11 +22,11 @@
|
|||
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||
-->
|
||||
|
||||
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-wf9-server-subsystem">
|
||||
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-wildfly-server-subsystem">
|
||||
|
||||
<resources>
|
||||
<resource-root path="."/>
|
||||
<artifact name="${org.keycloak:keycloak-wf9-server-subsystem}"/>
|
||||
<artifact name="${org.keycloak:keycloak-wildfly-server-subsystem}"/>
|
||||
</resources>
|
||||
|
||||
<dependencies>
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||
-->
|
||||
|
||||
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-as7-server-subsystem">
|
||||
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-eap6-server-subsystem">
|
||||
|
||||
<resources>
|
||||
<resource-root path="."/>
|
|
@ -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>
|
||||
|
|
|
@ -38,7 +38,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">
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<chapter id="admin-recovery">
|
||||
<title>Recovering the Master Admin User</title>
|
||||
<para>
|
||||
It is possible for the "admin" user in the master realm to become inoperable. This may be because it was
|
||||
accidentally deleted, its role mappings were removed, or the password was simply forgotten.
|
||||
</para>
|
||||
<para>
|
||||
To recover the master admin user, just start the server with the following system properties:
|
||||
<programlisting><![CDATA[
|
||||
bin/standalone.sh -Dkeycloak.recover-admin=true -Dkeycloak.temp-admin-password=temppassword
|
||||
]]></programlisting>
|
||||
Then you can log in to the master admin account with your temporary password. You will then be
|
||||
prompted to immediately change this password.
|
||||
</para>
|
||||
</chapter>
|
|
@ -127,6 +127,25 @@ cd <WILDFLY_HOME>/bin
|
|||
</listitem>
|
||||
</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>
|
||||
|
|
|
@ -17,6 +17,5 @@
|
|||
<module>as7-adapter-spi</module>
|
||||
<module>as7-adapter</module>
|
||||
<module>as7-subsystem</module>
|
||||
<module>as7-server-subsystem</module>
|
||||
</modules>
|
||||
</project>
|
|
@ -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>
|
|
@ -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();
|
||||
|
|
12
pom.xml
12
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>
|
||||
|
@ -945,6 +946,11 @@
|
|||
<artifactId>keycloak-wildfly-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 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.offlineconfig;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||
|
||||
/**
|
||||
* Static utility class that performs recovery on the master admin account.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
*/
|
||||
public class AdminRecovery {
|
||||
private static final Logger log = Logger.getLogger(AdminRecovery.class);
|
||||
|
||||
public static final String RECOVER_ADMIN_ACCOUNT = "keycloak.recover-admin";
|
||||
public static final String TEMP_ADMIN_PASSWORD = "keycloak.temp-admin-password";
|
||||
|
||||
// Don't allow instances
|
||||
private AdminRecovery() {}
|
||||
|
||||
public static void recover(KeycloakSessionFactory sessionFactory) {
|
||||
if (!needRecovery()) return;
|
||||
|
||||
KeycloakSession session = sessionFactory.create();
|
||||
|
||||
session.getTransaction().begin();
|
||||
try {
|
||||
doRecover(session, getTempAdminPassword());
|
||||
session.getTransaction().commit();
|
||||
log.info("*******************************");
|
||||
log.info("Recovered Master Admin account.");
|
||||
log.info("*******************************");
|
||||
} finally {
|
||||
session.close();
|
||||
System.clearProperty(RECOVER_ADMIN_ACCOUNT);
|
||||
System.clearProperty(TEMP_ADMIN_PASSWORD);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needRecovery() {
|
||||
String strNeedRecovery = System.getProperty(RECOVER_ADMIN_ACCOUNT, "false");
|
||||
return Boolean.parseBoolean(strNeedRecovery);
|
||||
}
|
||||
|
||||
private static String getTempAdminPassword() {
|
||||
String tempAdminPassword = System.getProperty(TEMP_ADMIN_PASSWORD);
|
||||
if ((tempAdminPassword == null) || tempAdminPassword.isEmpty()) {
|
||||
throw new OfflineConfigException("Must provide temporary admin password to recover admin account.");
|
||||
}
|
||||
return tempAdminPassword;
|
||||
}
|
||||
|
||||
private static void doRecover(KeycloakSession session, String tempAdminPassword) {
|
||||
RealmProvider realmProvider = session.realms();
|
||||
UserProvider userProvider = session.users();
|
||||
|
||||
String adminRealmName = Config.getAdminRealm();
|
||||
RealmModel realm = realmProvider.getRealmByName(adminRealmName);
|
||||
UserModel adminUser = userProvider.getUserByUsername("admin", realm);
|
||||
|
||||
if (adminUser == null) {
|
||||
adminUser = userProvider.addUser(realm, "admin");
|
||||
}
|
||||
|
||||
ApplianceBootstrap.setupAdminUser(session, realm, adminUser, tempAdminPassword);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 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.offlineconfig;
|
||||
|
||||
/**
|
||||
* Runtime exception thrown when an offline configuration fails. Offline
|
||||
* configuration is defined as any configuration done before the Keycloak Server
|
||||
* starts accepting requests.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
*/
|
||||
public class OfflineConfigException extends IllegalStateException {
|
||||
|
||||
public OfflineConfigException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
|
@ -23,22 +23,14 @@ 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);
|
||||
session.getTransaction().commit();
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void bootstrap(KeycloakSession session, String contextPath) {
|
||||
String adminRealmName = Config.getAdminRealm();
|
||||
if (session.realms().getRealm(adminRealmName) != null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info("Initializing " + adminRealmName + " realm");
|
||||
|
@ -61,15 +53,26 @@ public class ApplianceBootstrap {
|
|||
realm.setRegistrationEmailAsUsername(false);
|
||||
KeycloakModelUtils.generateRealmKeys(realm);
|
||||
|
||||
UserModel adminUser = session.users().addUser(realm, "admin");
|
||||
setupAdminUser(session, realm, adminUser, "admin");
|
||||
session.getTransaction().commit();
|
||||
return true;
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setupAdminUser(KeycloakSession session, RealmModel realm, UserModel adminUser, String password) {
|
||||
public static boolean setupDefaultUser(KeycloakSessionFactory sessionFactory) {
|
||||
KeycloakSession session = sessionFactory.create();
|
||||
session.getTransaction().begin();
|
||||
|
||||
try {
|
||||
RealmModel realm = session.realms().getRealm(Config.getAdminRealm());
|
||||
if (session.users().getUserByUsername("admin", realm) == null) {
|
||||
UserModel adminUser = session.users().addUser(realm, "admin");
|
||||
|
||||
adminUser.setEnabled(true);
|
||||
UserCredentialModel usrCredModel = new UserCredentialModel();
|
||||
usrCredModel.setType(UserCredentialModel.PASSWORD);
|
||||
usrCredModel.setValue(password);
|
||||
usrCredModel.setValue("admin");
|
||||
session.users().updateCredential(realm, adminUser, usrCredModel);
|
||||
adminUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
|
||||
|
@ -81,5 +84,11 @@ public class ApplianceBootstrap {
|
|||
adminUser.grantRole(accountApp.getRole(r));
|
||||
}
|
||||
}
|
||||
session.getTransaction().commit();
|
||||
return true;
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,10 @@
|
|||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-admin-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-wildfly-adduser</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
* Copyright 2015 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.testsuite.offlineconfig;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
import org.keycloak.offlineconfig.AdminRecovery;
|
||||
import org.keycloak.offlineconfig.OfflineConfigException;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
|
||||
/**
|
||||
* Test the AdminRecovery class.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
*/
|
||||
public class AdminRecoveryTest {
|
||||
@ClassRule
|
||||
public static KeycloakRule keycloakRule = new KeycloakRule() {
|
||||
|
||||
@Override
|
||||
protected void after() {
|
||||
|
||||
// Need to reset admin user to default password and remove required action to not break next tests
|
||||
update(new KeycloakSetup() {
|
||||
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
UserModel adminUser = session.users().getUserByUsername("admin", adminstrationRealm);
|
||||
UserCredentialModel password = UserCredentialModel.password("admin");
|
||||
adminUser.updateCredential(password);
|
||||
|
||||
adminUser.removeRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
}
|
||||
});
|
||||
|
||||
super.after();
|
||||
}
|
||||
};
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
// Verifies that system properties were cleared at the end of recovery
|
||||
@After
|
||||
public void verifySysPropsCleared() {
|
||||
Assert.assertNull(System.getProperty(AdminRecovery.RECOVER_ADMIN_ACCOUNT));
|
||||
Assert.assertNull(System.getProperty(AdminRecovery.TEMP_ADMIN_PASSWORD));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdminDeletedRecovery() {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
RealmModel masterRealm = session.realms().getRealmByName("master");
|
||||
UserModel adminUser = session.users().getUserByUsername("admin", masterRealm);
|
||||
session.users().removeUser(masterRealm, adminUser);
|
||||
adminUser = session.users().getUserByUsername("admin", masterRealm);
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
||||
Assert.assertNull(adminUser);
|
||||
|
||||
doAdminRecovery(session);
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
adminUser = session.users().getUserByUsername("admin", masterRealm);
|
||||
Assert.assertNotNull(adminUser);
|
||||
Assert.assertTrue(adminUser.getRequiredActions().contains(RequiredAction.UPDATE_PASSWORD.toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdminPasswordRecovery() {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
RealmModel masterRealm = session.realms().getRealmByName("master");
|
||||
UserModel adminUser = session.users().getUserByUsername("admin", masterRealm);
|
||||
UserCredentialValueModel password = adminUser.getCredentialsDirectly().get(0);
|
||||
password.setValue("forgotten-password");
|
||||
adminUser.updateCredentialDirectly(password);
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
||||
Assert.assertEquals("forgotten-password", getAdminPassword());
|
||||
|
||||
doAdminRecovery(session);
|
||||
|
||||
Assert.assertNotEquals("forgotten-password", getAdminPassword());
|
||||
}
|
||||
|
||||
@Test(expected = OfflineConfigException.class)
|
||||
public void testAdminRecoveryWithoutPassword() {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
System.setProperty(AdminRecovery.RECOVER_ADMIN_ACCOUNT, "true");
|
||||
AdminRecovery.recover(session.getKeycloakSessionFactory());
|
||||
}
|
||||
|
||||
private void doAdminRecovery(KeycloakSession session) {
|
||||
System.setProperty(AdminRecovery.RECOVER_ADMIN_ACCOUNT, "true");
|
||||
System.setProperty(AdminRecovery.TEMP_ADMIN_PASSWORD, "foo");
|
||||
AdminRecovery.recover(session.getKeycloakSessionFactory());
|
||||
}
|
||||
|
||||
private String getAdminPassword() {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
RealmModel masterRealm = session.realms().getRealmByName("master");
|
||||
UserModel adminUser = session.users().getUserByUsername("admin", masterRealm);
|
||||
UserCredentialValueModel password = adminUser.getCredentialsDirectly().get(0);
|
||||
keycloakRule.stopSession(session, true);
|
||||
return password.getValue();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
54
wildfly/adduser/pom.xml
Executable file
54
wildfly/adduser/pom.xml
Executable file
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -19,9 +19,8 @@
|
|||
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<artifactId>keycloak-wildfly-parent</artifactId>
|
||||
<version>1.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>keycloak-wildfly-extensions</artifactId>
|
22
wildfly/pom.xml
Executable file
22
wildfly/pom.xml
Executable file
|
@ -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>
|
|
@ -19,13 +19,12 @@
|
|||
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<artifactId>keycloak-wildfly-parent</artifactId>
|
||||
<version>1.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>keycloak-as7-server-subsystem</artifactId>
|
||||
<name>Keycloak AS7 / EAP 6 Server Subsystem</name>
|
||||
<artifactId>keycloak-eap6-server-subsystem</artifactId>
|
||||
<name>Keycloak EAP 6 Server Subsystem</name>
|
||||
<description/>
|
||||
<packaging>jar</packaging>
|
||||
|
|
@ -19,13 +19,12 @@
|
|||
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<artifactId>keycloak-wildfly-parent</artifactId>
|
||||
<version>1.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>keycloak-wf9-server-subsystem</artifactId>
|
||||
<name>Keycloak Wildfly 9 Server Subsystem</name>
|
||||
<artifactId>keycloak-wildfly-server-subsystem</artifactId>
|
||||
<name>Keycloak WildFly Server Subsystem</name>
|
||||
<description/>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
@ -96,11 +95,5 @@
|
|||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-wildfly-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
Loading…
Reference in a new issue