keycloak-aplcache

Changes

testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java 398(+0 -398)

testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosCredDelegServlet.java 112(+0 -112)

testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java 249(+0 -249)

testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KeycloakSPNegoSchemeFactory.java 130(+0 -130)

testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestConfiguration.java 149(+0 -149)

testsuite/integration/src/test/resources/kerberos/http.keytab 0(+0 -0)

testsuite/integration/src/test/resources/kerberos/kerberos-ldap-connection.properties 34(+0 -34)

testsuite/integration/src/test/resources/kerberos/kerberos-standalone-connection.properties 23(+0 -23)

testsuite/integration/src/test/resources/kerberos/test-krb5.conf 19(+0 -19)

testsuite/integration/src/test/resources/kerberos/users-kerberos.ldif 104(+0 -104)

testsuite/integration/src/test/resources/kerberos-test/kerberos-app-keycloak.json 10(+0 -10)

testsuite/integration/src/test/resources/kerberos-test/kerberosrealm.json 53(+0 -53)

testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/kerberos-portal/META-INF/context.xml 20(+0 -20)

testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/kerberos-portal/WEB-INF/jetty-web.xml 27(+0 -27)

testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/kerberos-portal/WEB-INF/keycloak.json 10(+0 -10)

testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/kerberos-portal/WEB-INF/keycloak-relative.json 9(+0 -9)

testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/kerberos-portal/WEB-INF/web.xml 57(+0 -57)

Details

diff --git a/distribution/server-overlay/assembly.xml b/distribution/server-overlay/assembly.xml
index 6b86e25..26532d7 100755
--- a/distribution/server-overlay/assembly.xml
+++ b/distribution/server-overlay/assembly.xml
@@ -84,7 +84,7 @@
             <outputDirectory></outputDirectory>
         </fileSet>
         <fileSet>
-            <directory>src/main/cli</directory>
+            <directory>${project.build.directory}/cli</directory>
             <includes>
                 <include>*.cli</include>
             </includes>
@@ -107,11 +107,6 @@
             <outputDirectory>bin</outputDirectory>
             <destName>add-user-keycloak.bat</destName>
         </file>
-        <file>
-            <source>${project.build.directory}/cli/default-keycloak-subsys-config.cli</source>
-            <outputDirectory>bin</outputDirectory>
-            <destName>default-keycloak-subsys-config.cli</destName>
-        </file>
     </files>
 
 </assembly>
diff --git a/distribution/server-overlay/pom.xml b/distribution/server-overlay/pom.xml
index 5b8151b..b69386d 100755
--- a/distribution/server-overlay/pom.xml
+++ b/distribution/server-overlay/pom.xml
@@ -129,6 +129,43 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <inherited>false</inherited>
+                <executions>
+                    <execution>
+                        <id>merge-cli</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                        <configuration>
+                            <target>
+                                <concat destfile="${project.build.directory}/cli/keycloak-install.cli" force="yes">
+                                    <fileset dir="${project.build.directory}/cli">
+                                        <include name="keycloak-install-base.cli"/>
+                                    </fileset>
+                                    <fileset dir="${project.build.directory}/cli">
+                                        <include name="default-keycloak-subsys-config.cli"/>
+                                    </fileset>
+                                </concat>
+                                <concat destfile="${project.build.directory}/cli/keycloak-install-ha.cli" force="yes">
+                                    <fileset dir="${project.build.directory}/cli">
+                                        <include name="keycloak-install-ha-base.cli"/>
+                                    </fileset>
+                                    <fileset dir="${project.build.directory}/cli">
+                                        <include name="default-keycloak-subsys-config.cli"/>
+                                    </fileset>
+                                </concat>
+                                <delete file="${project.build.directory}/cli/default-keycloak-subsys-config.cli"/>
+                                <delete file="${project.build.directory}/cli/keycloak-install-base.cli"/>
+                                <delete file="${project.build.directory}/cli/keycloak-install-ha-base.cli"/>
+                            </target>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java
index 4689190..d9c3d20 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java
@@ -18,7 +18,9 @@
 package org.keycloak.federation.kerberos;
 
 import org.keycloak.common.constants.KerberosConstants;
+import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.component.ComponentModel;
+import org.keycloak.representations.idm.ComponentRepresentation;
 
 import java.util.Map;
 
@@ -29,31 +31,39 @@ import java.util.Map;
  */
 public abstract class CommonKerberosConfig {
 
-    protected ComponentModel componentModel;
+    protected MultivaluedHashMap<String, String> userStorageConfig;
 
     public CommonKerberosConfig(ComponentModel componentModel) {
-        this.componentModel = componentModel;
+        this.userStorageConfig = componentModel.getConfig();
+    }
+
+    public CommonKerberosConfig(ComponentRepresentation componentRep) {
+        this.userStorageConfig = componentRep.getConfig();
+    }
+
+    protected MultivaluedHashMap<String, String> getConfig() {
+        return userStorageConfig;
     }
 
     // Should be always true for KerberosFederationProvider
     public boolean isAllowKerberosAuthentication() {
-        return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION));
+        return Boolean.valueOf(getConfig().getFirst(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION));
     }
 
     public String getKerberosRealm() {
-        return componentModel.getConfig().getFirst(KerberosConstants.KERBEROS_REALM);
+        return getConfig().getFirst(KerberosConstants.KERBEROS_REALM);
     }
 
     public String getServerPrincipal() {
-        return componentModel.getConfig().getFirst(KerberosConstants.SERVER_PRINCIPAL);
+        return getConfig().getFirst(KerberosConstants.SERVER_PRINCIPAL);
     }
 
     public String getKeyTab() {
-        return componentModel.getConfig().getFirst(KerberosConstants.KEYTAB);
+        return getConfig().getFirst(KerberosConstants.KEYTAB);
     }
 
     public boolean isDebug() {
-        return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.DEBUG));
+        return Boolean.valueOf(getConfig().getFirst(KerberosConstants.DEBUG));
     }
 
 
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
index c36694c..1acf907 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
@@ -99,7 +99,12 @@ public class KerberosUsernamePasswordAuthenticator {
     }
 
     protected void checkKerberosServerAvailable(LoginException le) {
-        if (le.getMessage().contains("Port Unreachable")) {
+        String message = le.getMessage().toUpperCase();
+        if (message.contains("PORT UNREACHABLE") ||
+            message.contains("CANNOT LOCATE") ||
+            message.contains("CANNOT CONTACT") ||
+            message.contains("CANNOT FIND") ||
+            message.contains("UNKNOWN ERROR")) {
             throw new ModelException("Kerberos unreachable", le);
         }
     }
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java
index 26badf9..010f2c3 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java
@@ -20,6 +20,7 @@ package org.keycloak.federation.kerberos;
 import org.keycloak.common.constants.KerberosConstants;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.models.LDAPConstants;
+import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.storage.UserStorageProvider.EditMode;
 
 /**
@@ -33,8 +34,12 @@ public class KerberosConfig extends CommonKerberosConfig {
         super(component);
     }
 
+    public KerberosConfig(ComponentRepresentation component) {
+        super(component);
+    }
+
     public EditMode getEditMode() {
-        String editModeString = componentModel.getConfig().getFirst(LDAPConstants.EDIT_MODE);
+        String editModeString = getConfig().getFirst(LDAPConstants.EDIT_MODE);
         if (editModeString == null) {
             return EditMode.UNSYNCED;
         } else {
@@ -43,11 +48,11 @@ public class KerberosConfig extends CommonKerberosConfig {
     }
 
     public boolean isAllowPasswordAuthentication() {
-        return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION));
+        return Boolean.valueOf(getConfig().getFirst(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION));
     }
 
     public boolean isUpdateProfileFirstLogin() {
-        return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN));
+        return Boolean.valueOf(getConfig().getFirst(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN));
     }
 
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/kerberos/LDAPProviderKerberosConfig.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/kerberos/LDAPProviderKerberosConfig.java
index 87bfcaf..80b666b 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/kerberos/LDAPProviderKerberosConfig.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/kerberos/LDAPProviderKerberosConfig.java
@@ -20,6 +20,7 @@ package org.keycloak.storage.ldap.kerberos;
 import org.keycloak.common.constants.KerberosConstants;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.federation.kerberos.CommonKerberosConfig;
+import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.storage.ldap.LDAPStorageProvider;
 
 /**
@@ -33,7 +34,11 @@ public class LDAPProviderKerberosConfig extends CommonKerberosConfig {
         super(componentModel);
     }
 
+    public LDAPProviderKerberosConfig(ComponentRepresentation componentRep) {
+        super(componentRep);
+    }
+
     public boolean isUseKerberosForPasswordAuthentication() {
-        return Boolean.valueOf(componentModel.getConfig().getFirst(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION));
+        return Boolean.valueOf(getConfig().getFirst(KerberosConstants.USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION));
     }
 }
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java b/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
index 3a5867b..7e7daa5 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
@@ -21,6 +21,7 @@ import org.keycloak.saml.common.PicketLinkLogger;
 import org.keycloak.saml.common.PicketLinkLoggerFactory;
 import org.keycloak.saml.common.constants.GeneralConstants;
 import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.SecurityActions;
 import org.keycloak.saml.common.util.StaxParserUtil;
 import org.keycloak.saml.common.util.SystemPropertiesUtil;
 
@@ -32,8 +33,9 @@ import javax.xml.stream.events.Characters;
 import javax.xml.stream.events.XMLEvent;
 import javax.xml.stream.util.EventReaderDelegate;
 import java.io.InputStream;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
+import javax.xml.transform.Source;
+import javax.xml.transform.dom.DOMSource;
+import org.w3c.dom.Node;
 
 /**
  * Base class for parsers
@@ -45,26 +47,33 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
 
     protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
 
-    /**
-     * Get the JAXP {@link XMLInputFactory}
-     *
-     * @return
-     */
-    protected XMLInputFactory getXMLInputFactory() {
-        boolean tccl_jaxp = SystemPropertiesUtil.getSystemProperty(GeneralConstants.TCCL_JAXP, "false")
-                .equalsIgnoreCase("true");
-        ClassLoader prevTCCL = getTCCL();
-        try {
-            if (tccl_jaxp) {
-                setTCCL(getClass().getClassLoader());
-            }
-            return XMLInputFactory.newInstance();
-        } finally {
-            if (tccl_jaxp) {
-                setTCCL(prevTCCL);
+    private static final ThreadLocal<XMLInputFactory> XML_INPUT_FACTORY = new ThreadLocal<XMLInputFactory>() {
+        @Override
+        protected XMLInputFactory initialValue() {
+            return getXMLInputFactory();
+        }
+
+        /**
+         * Get the JAXP {@link XMLInputFactory}
+         *
+         * @return
+         */
+        private XMLInputFactory getXMLInputFactory() {
+            boolean tccl_jaxp = SystemPropertiesUtil.getSystemProperty(GeneralConstants.TCCL_JAXP, "false")
+                    .equalsIgnoreCase("true");
+            ClassLoader prevTCCL = SecurityActions.getTCCL();
+            try {
+                if (tccl_jaxp) {
+                    SecurityActions.setTCCL(AbstractParser.class.getClassLoader());
+                }
+                return XMLInputFactory.newInstance();
+            } finally {
+                if (tccl_jaxp) {
+                    SecurityActions.setTCCL(prevTCCL);
+                }
             }
         }
-    }
+    };
 
     /**
      * Parse an InputStream for payload
@@ -81,6 +90,15 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
         return parse(xmlEventReader);
     }
 
+    public Object parse(Source source) throws ParsingException {
+        XMLEventReader xmlEventReader = createEventReader(source);
+        return parse(xmlEventReader);
+    }
+
+    public Object parse(Node node) throws ParsingException {
+        return parse(new DOMSource(node));
+    }
+
     public XMLEventReader createEventReader(InputStream configStream) throws ParsingException {
         if (configStream == null)
             throw logger.nullArgumentError("InputStream");
@@ -96,33 +114,23 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
         return xmlEventReader;
     }
 
-    private ClassLoader getTCCL() {
-        if (System.getSecurityManager() != null) {
-            return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
-                public ClassLoader run() {
-                    return Thread.currentThread().getContextClassLoader();
-                }
-            });
-        } else {
-            return Thread.currentThread().getContextClassLoader();
-        }
-    }
+    public XMLEventReader createEventReader(Source source) throws ParsingException {
+        if (source == null)
+            throw logger.nullArgumentError("Source");
 
-    private void setTCCL(final ClassLoader paramCl) {
-        if (System.getSecurityManager() != null) {
-            AccessController.doPrivileged(new PrivilegedAction<Void>() {
-                public Void run() {
-                    Thread.currentThread().setContextClassLoader(paramCl);
-                    return null;
-                }
-            });
-        } else {
-            Thread.currentThread().setContextClassLoader(paramCl);
+        XMLEventReader xmlEventReader = StaxParserUtil.getXMLEventReader(source);
+
+        try {
+            xmlEventReader = filterWhitespaces(xmlEventReader);
+        } catch (XMLStreamException e) {
+            throw logger.parserException(e);
         }
+
+        return xmlEventReader;
     }
 
     protected XMLEventReader filterWhitespaces(XMLEventReader xmlEventReader) throws XMLStreamException {
-        XMLInputFactory xmlInputFactory = getXMLInputFactory();
+        XMLInputFactory xmlInputFactory = XML_INPUT_FACTORY.get();
 
         xmlEventReader = xmlInputFactory.createFilteredReader(xmlEventReader, new EventFilter() {
             public boolean accept(XMLEvent xmlEvent) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
index d9dd4e0..b5f2232 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
@@ -97,10 +97,9 @@ public class DocumentUtil {
      * @throws ParserConfigurationException
      */
     public static Document createDocument() throws ConfigurationException {
-        DocumentBuilderFactory factory = getDocumentBuilderFactory();
         DocumentBuilder builder;
         try {
-            builder = factory.newDocumentBuilder();
+            builder = getDocumentBuilder();
         } catch (ParserConfigurationException e) {
             throw new ConfigurationException(e);
         }
@@ -118,8 +117,7 @@ public class DocumentUtil {
      */
     public static Document createDocumentWithBaseNamespace(String baseNamespace, String localPart) throws ProcessingException {
         try {
-            DocumentBuilderFactory factory = getDocumentBuilderFactory();
-            DocumentBuilder builder = factory.newDocumentBuilder();
+            DocumentBuilder builder = getDocumentBuilder();
             return builder.getDOMImplementation().createDocument(baseNamespace, localPart, null);
         } catch (DOMException e) {
             throw logger.processingError(e);
@@ -157,8 +155,7 @@ public class DocumentUtil {
      */
     public static Document getDocument(Reader reader) throws ConfigurationException, ProcessingException, ParsingException {
         try {
-            DocumentBuilderFactory factory = getDocumentBuilderFactory();
-            DocumentBuilder builder = factory.newDocumentBuilder();
+            DocumentBuilder builder = getDocumentBuilder();
             return builder.parse(new InputSource(reader));
         } catch (ParserConfigurationException e) {
             throw logger.configurationError(e);
@@ -181,9 +178,8 @@ public class DocumentUtil {
      * @throws SAXException
      */
     public static Document getDocument(File file) throws ConfigurationException, ProcessingException, ParsingException {
-        DocumentBuilderFactory factory = getDocumentBuilderFactory();
         try {
-            DocumentBuilder builder = factory.newDocumentBuilder();
+            DocumentBuilder builder = getDocumentBuilder();
             return builder.parse(file);
         } catch (ParserConfigurationException e) {
             throw logger.configurationError(e);
@@ -206,9 +202,8 @@ public class DocumentUtil {
      * @throws SAXException
      */
     public static Document getDocument(InputStream is) throws ConfigurationException, ProcessingException, ParsingException {
-        DocumentBuilderFactory factory = getDocumentBuilderFactory();
         try {
-            DocumentBuilder builder = factory.newDocumentBuilder();
+            DocumentBuilder builder = getDocumentBuilder();
             return builder.parse(is);
         } catch (ParserConfigurationException e) {
             throw logger.configurationError(e);
@@ -502,6 +497,25 @@ public class DocumentUtil {
         }
     }
 
+    private static final ThreadLocal<DocumentBuilder> XML_DOCUMENT_BUILDER = new ThreadLocal<DocumentBuilder>() {
+        @Override
+        protected DocumentBuilder initialValue() {
+            DocumentBuilderFactory factory = getDocumentBuilderFactory();
+            try {
+                return factory.newDocumentBuilder();
+            } catch (ParserConfigurationException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+    };
+
+    private static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException {
+        DocumentBuilder res = XML_DOCUMENT_BUILDER.get();
+        res.reset();
+        return res;
+    }
+
     /**
      * <p> Creates a namespace aware {@link DocumentBuilderFactory}. The returned instance is cached and shared between
      * different threads. </p>
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/SecurityActions.java b/saml-core/src/main/java/org/keycloak/saml/common/util/SecurityActions.java
index c9c18a4..b36adb4 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/SecurityActions.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/SecurityActions.java
@@ -26,7 +26,7 @@ import java.security.PrivilegedAction;
  * @author Anil.Saldhana@redhat.com
  * @since Dec 9, 2008
  */
-class SecurityActions {
+public class SecurityActions {
 
     /**
      * <p> Loads a {@link Class} using the <code>fullQualifiedName</code> supplied. This method tries first to load from
@@ -186,7 +186,7 @@ class SecurityActions {
      *
      * @return
      */
-    static ClassLoader getTCCL() {
+    public static ClassLoader getTCCL() {
         if (System.getSecurityManager() != null) {
             return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
                 public ClassLoader run() {
@@ -203,7 +203,7 @@ class SecurityActions {
      *
      * @param paramCl
      */
-    static void setTCCL(final ClassLoader paramCl) {
+    public static void setTCCL(final ClassLoader paramCl) {
         if (System.getSecurityManager() != null) {
             AccessController.doPrivileged(new PrivilegedAction<Void>() {
                 public Void run() {
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java
index 1b06e9d..f2fa728 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java
@@ -24,6 +24,8 @@ import org.keycloak.saml.common.constants.JBossSAMLConstants;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.common.exceptions.ConfigurationException;
 import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
@@ -47,6 +49,7 @@ import javax.xml.validation.Schema;
 import javax.xml.validation.SchemaFactory;
 import javax.xml.validation.Validator;
 import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Utility for the stax based parser
@@ -122,7 +125,6 @@ public class StaxParserUtil {
      */
     public static String getAttributeValue(Attribute attribute) {
         String str = trim(attribute.getValue());
-        str = StringUtil.getSystemPropertyAsString(str);
         return str;
     }
 
@@ -224,7 +226,6 @@ public class StaxParserUtil {
         String str = null;
         try {
             str = xmlEventReader.getElementText().trim();
-            str = StringUtil.getSystemPropertyAsString(str);
         } catch (XMLStreamException e) {
             throw logger.parserException(e);
         }
@@ -250,6 +251,34 @@ public class StaxParserUtil {
         return xmlEventReader;
     }
 
+    private static AtomicBoolean XML_EVENT_READER_ON_SOURCE_SUPPORTED = new AtomicBoolean(true);
+
+    /**
+     * Get the XML event reader
+     *
+     * @param source
+     *
+     * @return
+     */
+    public static XMLEventReader getXMLEventReader(Source source) {
+        XMLInputFactory xmlInputFactory = XML_INPUT_FACTORY.get();
+        try {
+            if (XML_EVENT_READER_ON_SOURCE_SUPPORTED.get()) {
+                try {
+                    // The following method is optional per specification
+                    return xmlInputFactory.createXMLEventReader(source);
+                } catch (UnsupportedOperationException ex) {
+                    XML_EVENT_READER_ON_SOURCE_SUPPORTED.set(false);
+                    return getXMLEventReader(source);
+                }
+            } else {
+                return getXMLEventReader(DocumentUtil.getSourceAsStream(source));
+            }
+        } catch (ConfigurationException | ProcessingException | XMLStreamException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
     /**
      * Given a {@code Location}, return a formatted string [lineNum,colNum]
      *
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java
index f582ae0..23fb45f 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java
@@ -59,6 +59,9 @@ public class StringUtil {
         return str == null || str.isEmpty();
     }
 
+    private static final Pattern PROPERTY_REPLACEMENT = Pattern.compile("(.*?)"    + "\\$\\{(.*?)"   + "(?:::(.*?))?\\}");
+                                                                      // 1: PREFIX | START  2: NAME  |       3: OPTIONAL DEFAULT VALUE
+
     /**
      * <p>
      * Get the system property value if the string is of the format ${sysproperty}
@@ -84,37 +87,25 @@ public class StringUtil {
     public static String getSystemPropertyAsString(String str) {
         if (str == null)
             throw logger.nullArgumentError("str");
-        if (str.contains("${")) {
-            Pattern pattern = Pattern.compile("\\$\\{([^}]+)}");
-            Matcher matcher = pattern.matcher(str);
-
-            StringBuffer buffer = new StringBuffer();
-            String sysPropertyValue = null;
-
-            while (matcher.find()) {
-                String subString = matcher.group(1);
-                String defaultValue = "";
-
-                // Look for default value
-                if (subString.contains("::")) {
-                    int index = subString.indexOf("::");
-                    defaultValue = subString.substring(index + 2);
-                    subString = subString.substring(0, index);
-                }
-                sysPropertyValue = SecurityActions.getSystemProperty(subString, defaultValue);
-                if (sysPropertyValue.isEmpty()) {
-                    throw logger.systemPropertyMissingError(matcher.group(1));
-                }else{
-                    // sanitize the value before we use append-and-replace
-                    sysPropertyValue = Matcher.quoteReplacement(sysPropertyValue);
-                }
-                matcher.appendReplacement(buffer, sysPropertyValue);
+
+        Matcher m = PROPERTY_REPLACEMENT.matcher(str);
+        StringBuilder sb = new StringBuilder();
+        int lastPosition = 0;
+        while (m.find()) {
+            String propertyName = m.group(2);
+            String defaultValue = m.group(3);
+
+            String sysPropertyValue = SecurityActions.getSystemProperty(propertyName, defaultValue);
+            if (sysPropertyValue.isEmpty()) {
+                throw logger.systemPropertyMissingError(propertyName);
             }
 
-            matcher.appendTail(buffer);
-            str = buffer.toString();
+            sb.append(m.group(1)).append(sysPropertyValue);
+
+            lastPosition = m.end();
         }
-        return str;
+
+        return sb.append(str.substring(lastPosition)).toString();
     }
 
     /**
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java
index 9830482..2bfa41f 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java
@@ -165,7 +165,7 @@ public class SAML2Request {
 
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlDocument);
-        SAML2Object requestType = (SAML2Object) samlParser.parse(DocumentUtil.getNodeAsStream(samlDocument));
+        SAML2Object requestType = (SAML2Object) samlParser.parse(samlDocument);
 
         samlDocumentHolder = new SAMLDocumentHolder(requestType, samlDocument);
         return requestType;
@@ -192,7 +192,7 @@ public class SAML2Request {
 
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlDocument);
-        RequestAbstractType requestType = (RequestAbstractType) samlParser.parse(DocumentUtil.getNodeAsStream(samlDocument));
+        RequestAbstractType requestType = (RequestAbstractType) samlParser.parse(samlDocument);
 
         samlDocumentHolder = new SAMLDocumentHolder(requestType, samlDocument);
         return requestType;
@@ -220,7 +220,7 @@ public class SAML2Request {
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlDocument);
 
-        AuthnRequestType requestType = (AuthnRequestType) samlParser.parse(DocumentUtil.getNodeAsStream(samlDocument));
+        AuthnRequestType requestType = (AuthnRequestType) samlParser.parse(samlDocument);
         samlDocumentHolder = new SAMLDocumentHolder(requestType, samlDocument);
         return requestType;
     }
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java
index 76155fe..a650c7a 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java
@@ -376,7 +376,7 @@ public class SAML2Response {
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlDocument);
 
-        return (EncryptedAssertionType) samlParser.parse(DocumentUtil.getNodeAsStream(samlDocument));
+        return (EncryptedAssertionType) samlParser.parse(samlDocument);
 
     }
 
@@ -398,7 +398,7 @@ public class SAML2Response {
 
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlDocument);
-        return (AssertionType) samlParser.parse(DocumentUtil.getNodeAsStream(samlDocument));
+        return (AssertionType) samlParser.parse(samlDocument);
     }
 
     /**
@@ -429,7 +429,7 @@ public class SAML2Response {
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlResponseDocument);
 
-        ResponseType responseType = (ResponseType) samlParser.parse(DocumentUtil.getNodeAsStream(samlResponseDocument));
+        ResponseType responseType = (ResponseType) samlParser.parse(samlResponseDocument);
 
         samlDocumentHolder = new SAMLDocumentHolder(responseType, samlResponseDocument);
         return responseType;
@@ -460,8 +460,7 @@ public class SAML2Response {
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlResponseDocument);
 
-        InputStream responseStream = DocumentUtil.getNodeAsStream(samlResponseDocument);
-        SAML2Object responseType = (SAML2Object) samlParser.parse(responseStream);
+        SAML2Object responseType = (SAML2Object) samlParser.parse(samlResponseDocument);
 
         samlDocumentHolder = new SAMLDocumentHolder(responseType, samlResponseDocument);
         return responseType;
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/XMLTimeUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/XMLTimeUtil.java
index 237d6a5..31916ff 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/XMLTimeUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/XMLTimeUtil.java
@@ -51,15 +51,11 @@ public class XMLTimeUtil {
      *
      * @throws org.keycloak.saml.common.exceptions.ConfigurationException
      */
-    public static XMLGregorianCalendar add(XMLGregorianCalendar value, long milis) throws ConfigurationException {
+    public static XMLGregorianCalendar add(XMLGregorianCalendar value, long milis) {
         XMLGregorianCalendar newVal = (XMLGregorianCalendar) value.clone();
 
         Duration duration;
-        try {
-            duration = newDatatypeFactory().newDuration(milis);
-        } catch (DatatypeConfigurationException e) {
-            throw logger.configurationError(e);
-        }
+        duration = DATATYPE_FACTORY.get().newDuration(milis);
         newVal.add(duration);
         return newVal;
     }
@@ -74,7 +70,7 @@ public class XMLTimeUtil {
      *
      * @throws ConfigurationException
      */
-    public static XMLGregorianCalendar subtract(XMLGregorianCalendar value, long milis) throws ConfigurationException {
+    public static XMLGregorianCalendar subtract(XMLGregorianCalendar value, long milis) {
         if (milis < 0)
             throw logger.invalidArgumentError("milis should be a positive value");
         return add(value, -1 * milis);
@@ -91,14 +87,10 @@ public class XMLTimeUtil {
      *
      * @throws ConfigurationException
      */
-    public static XMLGregorianCalendar getIssueInstant(String timezone) throws ConfigurationException {
+    public static XMLGregorianCalendar getIssueInstant(String timezone) {
         TimeZone tz = TimeZone.getTimeZone(timezone);
         DatatypeFactory dtf;
-        try {
-            dtf = newDatatypeFactory();
-        } catch (DatatypeConfigurationException e) {
-            throw logger.configurationError(e);
-        }
+        dtf = DATATYPE_FACTORY.get();
 
         GregorianCalendar gc = new GregorianCalendar(tz);
         XMLGregorianCalendar xgc = dtf.newXMLGregorianCalendar(gc);
@@ -188,13 +180,7 @@ public class XMLTimeUtil {
             PicketLinkLoggerFactory.getLogger().nullArgumentError("duration time");
         }
 
-        DatatypeFactory factory = null;
-
-        try {
-            factory = newDatatypeFactory();
-        } catch (DatatypeConfigurationException e) {
-            throw logger.parserError(e);
-        }
+        DatatypeFactory factory = DATATYPE_FACTORY.get();
 
         try {
             // checks if it is a ISO 8601 period. If not it must be a numeric value.
@@ -218,15 +204,20 @@ public class XMLTimeUtil {
      * @throws ParsingException
      */
     public static XMLGregorianCalendar parse(String timeString) throws ParsingException {
-        DatatypeFactory factory = null;
-        try {
-            factory = newDatatypeFactory();
-        } catch (DatatypeConfigurationException e) {
-            throw logger.parserError(e);
-        }
+        DatatypeFactory factory = DATATYPE_FACTORY.get();
         return factory.newXMLGregorianCalendar(timeString);
     }
 
+    private static final ThreadLocal<DatatypeFactory> DATATYPE_FACTORY = new ThreadLocal<DatatypeFactory>() {
+        @Override
+        protected DatatypeFactory initialValue() {
+            try {
+                return newDatatypeFactory();
+            } catch (DatatypeConfigurationException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    };
 
     /**
      * Create a new {@link DatatypeFactory}
@@ -235,7 +226,7 @@ public class XMLTimeUtil {
      *
      * @throws DatatypeConfigurationException
      */
-    public static DatatypeFactory newDatatypeFactory() throws DatatypeConfigurationException {
+    private static DatatypeFactory newDatatypeFactory() throws DatatypeConfigurationException {
         boolean tccl_jaxp = SystemPropertiesUtil.getSystemProperty(GeneralConstants.TCCL_JAXP, "false")
                 .equalsIgnoreCase("true");
         ClassLoader prevTCCL = SecurityActions.getTCCL();
diff --git a/saml-core/src/test/java/org/keycloak/saml/common/util/StringUtilTest.java b/saml-core/src/test/java/org/keycloak/saml/common/util/StringUtilTest.java
new file mode 100644
index 0000000..dff97b7
--- /dev/null
+++ b/saml-core/src/test/java/org/keycloak/saml/common/util/StringUtilTest.java
@@ -0,0 +1,47 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.keycloak.saml.common.util;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.assertThat;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class StringUtilTest {
+
+    public StringUtilTest() {
+    }
+
+    @Test
+    public void testGetSystemPropertyAsString() {
+        System.setProperty("StringUtilTest.prop1", "value1");
+        System.setProperty("StringUtilTest.prop2", "value2");
+
+        assertThat(StringUtil.getSystemPropertyAsString("a"), is("a"));
+        assertThat(StringUtil.getSystemPropertyAsString("a ${StringUtilTest.prop1}"), is("a value1"));
+        assertThat(
+          StringUtil.getSystemPropertyAsString("a" + "${StringUtilTest.prop1}" + "StringUtilTest.prop1"),
+          is("a" + "value1" + "StringUtilTest.prop1")
+        );
+        assertThat(
+          StringUtil.getSystemPropertyAsString("a" + "${StringUtilTest.prop1}" + "StringUtilTest.prop1" + "${StringUtilTest.prop2}"),
+          is("a" + "value1" + "StringUtilTest.prop1" + "value2")
+        );
+        assertThat(
+          StringUtil.getSystemPropertyAsString("a" + "${StringUtilTest.prop1}" + "StringUtilTest.prop1" + "${StringUtilTest.prop2}" + "${StringUtilTest.prop3::abc}"),
+          is("a" + "value1" + "StringUtilTest.prop1" + "value2" + "abc")
+        );
+        assertThat(
+          StringUtil.getSystemPropertyAsString("a" + "${StringUtilTest.prop1}" + "StringUtilTest.prop1" + "${StringUtilTest.prop2}" + "${StringUtilTest.prop3::abc}" + "end"),
+          is("a" + "value1" + "StringUtilTest.prop1" + "value2" + "abc" + "end")
+        );
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 0fa958c..dd82b2c 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -2218,7 +2218,13 @@ public class RepresentationToModel {
                     }
                 }
                 if (!hasResource && !"".equals(resourceId)) {
-                    policy.addResource(storeFactory.getResourceStore().findById(resourceId, policy.getResourceServer().getId()));
+                    Resource resource = storeFactory.getResourceStore().findById(resourceId, policy.getResourceServer().getId());
+
+                    if (resource == null) {
+                        throw new RuntimeException("Resource [" + resourceId + "] not found.");
+                    }
+
+                    policy.addResource(resource);
                 }
             }
 
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
index d02b827..52d6a38 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
@@ -74,8 +74,15 @@ public class ResourceServerService {
 
     public void create() {
         this.auth.requireManage();
+
+        UserModel serviceAccount = this.session.users().getServiceAccount(client);
+
+        if (serviceAccount == null) {
+            throw new RuntimeException("Client does not have a service account.");
+        }
+
         this.resourceServer = this.authorization.getStoreFactory().getResourceServerStore().create(this.client.getId());
-        createDefaultRoles();
+        createDefaultRoles(serviceAccount);
         createDefaultPermission(createDefaultResource(), createDefaultPolicy());
     }
 
@@ -215,15 +222,13 @@ public class ResourceServerService {
         return defaultResource;
     }
 
-    private void createDefaultRoles() {
+    private void createDefaultRoles(UserModel serviceAccount) {
         RoleModel umaProtectionRole = client.getRole(Constants.AUTHZ_UMA_PROTECTION);
 
         if (umaProtectionRole == null) {
             umaProtectionRole = client.addRole(Constants.AUTHZ_UMA_PROTECTION);
         }
 
-        UserModel serviceAccount = this.session.users().getServiceAccount(client);
-
         if (!serviceAccount.hasRole(umaProtectionRole)) {
             serviceAccount.grantRole(umaProtectionRole);
         }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index c97a8f5..7c1139c 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -154,8 +154,12 @@ public class ClientResource {
     }
 
     public void updateClientFromRep(ClientRepresentation rep, ClientModel client, KeycloakSession session) throws ModelDuplicateException {
-        if (TRUE.equals(rep.isServiceAccountsEnabled()) && !client.isServiceAccountsEnabled()) {
-            new ClientManager(new RealmManager(session)).enableServiceAccount(client);
+        if (TRUE.equals(rep.isServiceAccountsEnabled())) {
+            UserModel serviceAccount = this.session.users().getServiceAccount(client);
+
+            if (serviceAccount == null) {
+                new ClientManager(new RealmManager(session)).enableServiceAccount(client);
+            }
         }
 
         if (!rep.getClientId().equals(client.getClientId())) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
index 2cd6d47..77e0d6b 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
@@ -16,20 +16,26 @@
  */
 package org.keycloak.services.resources.admin;
 
+import static java.lang.Boolean.TRUE;
+
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.authorization.admin.AuthorizationService;
+import org.keycloak.common.Profile;
 import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.services.ErrorResponse;
 import org.keycloak.services.ErrorResponseException;
 import org.keycloak.services.managers.ClientManager;
+import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.validation.ClientValidator;
 import org.keycloak.services.validation.PairwiseClientValidator;
 import org.keycloak.services.validation.ValidationMessages;
@@ -93,7 +99,17 @@ public class ClientsResource {
             boolean view = auth.hasView();
             for (ClientModel clientModel : clientModels) {
                 if (view) {
-                    rep.add(ModelToRepresentation.toRepresentation(clientModel));
+                    ClientRepresentation representation = ModelToRepresentation.toRepresentation(clientModel);
+
+                    if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
+                        AuthorizationService authorizationService = getAuthorizationService(clientModel);
+
+                        if (authorizationService.isEnabled()) {
+                            representation.setAuthorizationServicesEnabled(true);
+                        }
+                    }
+
+                    rep.add(representation);
                 } else {
                     ClientRepresentation client = new ClientRepresentation();
                     client.setId(clientModel.getId());
@@ -111,6 +127,10 @@ public class ClientsResource {
         return rep;
     }
 
+    private AuthorizationService getAuthorizationService(ClientModel clientModel) {
+        return new AuthorizationService(session, clientModel, auth);
+    }
+
     /**
      * Create a new client
      *
@@ -138,6 +158,20 @@ public class ClientsResource {
         try {
             ClientModel clientModel = ClientManager.createClient(session, realm, rep, true);
 
+            if (TRUE.equals(rep.isServiceAccountsEnabled())) {
+                UserModel serviceAccount = session.users().getServiceAccount(clientModel);
+
+                if (serviceAccount == null) {
+                    new ClientManager(new RealmManager(session)).enableServiceAccount(clientModel);
+                }
+            }
+
+            if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
+                if (TRUE.equals(rep.getAuthorizationServicesEnabled())) {
+                    getAuthorizationService(clientModel).enable();
+                }
+            }
+
             adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientModel.getId()).representation(rep).success();
 
             return Response.created(uriInfo.getAbsolutePathBuilder().path(clientModel.getId()).build()).build();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestConfiguration.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestConfiguration.java
index b254a1a..f4cbc22 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestConfiguration.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestConfiguration.java
@@ -84,9 +84,9 @@ public class LDAPTestConfiguration {
         DEFAULT_VALUES.put(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "false");
         DEFAULT_VALUES.put(KerberosConstants.KERBEROS_REALM, "KEYCLOAK.ORG");
         DEFAULT_VALUES.put(KerberosConstants.SERVER_PRINCIPAL, "HTTP/localhost@KEYCLOAK.ORG");
-        URL keytabUrl = LDAPTestConfiguration.class.getResource("/kerberos/http.keytab");
-        String keyTabPath = new File(keytabUrl.getFile()).getAbsolutePath();
-        DEFAULT_VALUES.put(KerberosConstants.KEYTAB, keyTabPath);
+//        URL keytabUrl = LDAPTestConfiguration.class.getResource("/kerberos/http.keytab");
+//        String keyTabPath = new File(keytabUrl.getFile()).getAbsolutePath();
+//        DEFAULT_VALUES.put(KerberosConstants.KEYTAB, keyTabPath);
         DEFAULT_VALUES.put(KerberosConstants.DEBUG, "true");
         DEFAULT_VALUES.put(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION, "true");
         DEFAULT_VALUES.put(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN, "true");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlEcpProfileTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlEcpProfileTest.java
index 778085c..bc6d316 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlEcpProfileTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlEcpProfileTest.java
@@ -146,7 +146,7 @@ public class SamlEcpProfileTest {
 
         assertNotNull(samlResponse);
 
-        ResponseType responseType = (ResponseType) new SAMLParser().parse(DocumentUtil.getNodeAsStream(samlResponse));
+        ResponseType responseType = (ResponseType) new SAMLParser().parse(samlResponse);
         StatusCodeType statusCode = responseType.getStatus().getStatusCode();
 
         assertEquals(statusCode.getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
@@ -229,7 +229,7 @@ public class SamlEcpProfileTest {
 
         assertNotNull(samlResponse);
 
-        StatusResponseType responseType = (StatusResponseType) new SAMLParser().parse(DocumentUtil.getNodeAsStream(samlResponse));
+        StatusResponseType responseType = (StatusResponseType) new SAMLParser().parse(samlResponse);
         StatusCodeType statusCode = responseType.getStatus().getStatusCode();
 
         assertNotEquals(statusCode.getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index 28e685e..cfcef5a 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -82,6 +82,8 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import static org.keycloak.exportimport.ExportImportConfig.PROVIDER;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -656,6 +658,13 @@ public class TestingResourceProvider implements RealmResourceProvider {
         return session.getContext().getRealm().getIdentityProviderByAlias(alias).getConfig();
     }
 
+    @PUT
+    @Path("/set-krb5-conf-file")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void setKrb5ConfFile(@QueryParam("krb5-conf-file") String krb5ConfFile) {
+        System.setProperty("java.security.krb5.conf", krb5ConfFile);
+    }
+
     private RealmModel getRealmByName(String realmName) {
         RealmProvider realmProvider = session.getProvider(RealmProvider.class);
         return realmProvider.getRealmByName(realmName);
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
index bf71b43..691e01a 100755
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
@@ -88,6 +88,16 @@ module.controller('AlbumCtrl', function ($scope, $http, $routeParams, $location,
             $location.path('/');
         });
     };
+
+    $scope.createWithInvalidUser = function () {
+        var newAlbum = new Album($scope.album);
+        newAlbum.$save({user: 'invalidUser'}, function (data) {
+            document.getElementById("output").innerHTML = 'Request was successful'
+        },
+        function (response) {
+            document.getElementById("output").innerHTML = response.data;
+        });
+    };
 });
 
 module.controller('ProfileCtrl', function ($scope, $http, $routeParams, $location, Profile) {
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/album/create.html b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/album/create.html
index d9ddd25..403adfa 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/album/create.html
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/album/create.html
@@ -4,4 +4,5 @@
     Name: <input type="text" id="album.name" ng-model="album.name"/>
 
     <button ng-click="create()" id="save-album">Save</button>
+    <button ng-click="createWithInvalidUser()" id="save-album-invalid">Save with invalid user</button>
 </form>
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
index 81c5a53..cd4fdba 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
@@ -22,6 +22,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
@@ -54,17 +55,18 @@ public class AlbumService {
 
     @POST
     @Consumes("application/json")
-    public Response create(Album newAlbum) {
+    public Response create(Album newAlbum, @QueryParam("user") String username) {
         newAlbum.setId(++nextId);
 
-        Principal userPrincipal = request.getUserPrincipal();
-
-        newAlbum.setUserId(userPrincipal.getName());
+        if (username == null) {
+            username = request.getUserPrincipal().getName();
+        }
 
+        newAlbum.setUserId(username);
         Query queryDuplicatedAlbum = this.entityManager.createQuery("from Album where name = :name and userId = :userId");
 
         queryDuplicatedAlbum.setParameter("name", newAlbum.getName());
-        queryDuplicatedAlbum.setParameter("userId", userPrincipal.getName());
+        queryDuplicatedAlbum.setParameter("userId", username);
 
         if (!queryDuplicatedAlbum.getResultList().isEmpty()) {
             throw new ErrorResponse("Name [" + newAlbum.getName() + "] already taken. Choose another one.", Status.CONFLICT);
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
index b721166..2285be0 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
@@ -53,23 +53,34 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
     protected ConsentPage consentPage;
 
     @FindBy(xpath = "//a[@ng-click = 'Identity.logout()']")
-    WebElement signOutButton;
+    private WebElement signOutButton;
     
     @FindBy(id = "entitlement")
-    WebElement entitlement;
+    private WebElement entitlement;
     
     @FindBy(id = "entitlements")
-    WebElement entitlements;
+    private WebElement entitlements;
+
+    @FindBy(id = "output")
+    private WebElement output;
     
     public void createAlbum(String name) {
+        createAlbum(name, "save-album");
+    }
+
+    public void createAlbum(String name, String buttonId) {
         navigateTo();
         this.driver.findElement(By.id("create-album")).click();
         Form.setInputValue(this.driver.findElement(By.id("album.name")), name);
         pause(200); // We need to wait a bit for the form to "accept" the input (otherwise it registers the input as empty)
-        this.driver.findElement(By.id("save-album")).click();
+        this.driver.findElement(By.id(buttonId)).click();
         pause(WAIT_AFTER_OPERATION);
     }
 
+    public void createAlbumWithInvalidUser(String name) {
+        createAlbum(name, "save-album-invalid");
+    }
+
     @Override
     public URL getInjectedUrl() {
         return this.url;
@@ -137,6 +148,10 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
         pause(WAIT_AFTER_OPERATION);
     }
 
+    public WebElement getOutput() {
+        return output;
+    }
+
     @Override
     public void navigateTo(boolean waitForMatch) {
         super.navigateTo(waitForMatch);
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
index bed2d32..9bb73d3 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
@@ -256,4 +256,9 @@ public interface TestingResource {
     @Path("/component")
     @Produces(MediaType.APPLICATION_JSON)
     MultivaluedHashMap<String, String> getComponentConfig(@QueryParam("componentId") String componentId);
+
+    @PUT
+    @Path("/set-krb5-conf-file")
+    @Consumes(MediaType.APPLICATION_JSON)
+    void setKrb5ConfFile(@QueryParam("krb5-conf-file") String krb5ConfFile);
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LDAPTestConfiguration.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LDAPTestConfiguration.java
index 772ae2d..b148a82 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LDAPTestConfiguration.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LDAPTestConfiguration.java
@@ -28,6 +28,7 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -88,7 +89,7 @@ public class LDAPTestConfiguration {
         DEFAULT_VALUES.put(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION, "false");
         DEFAULT_VALUES.put(KerberosConstants.KERBEROS_REALM, "KEYCLOAK.ORG");
         DEFAULT_VALUES.put(KerberosConstants.SERVER_PRINCIPAL, "HTTP/localhost@KEYCLOAK.ORG");
-        String keyTabPath =  getResource("http.keytab");
+        String keyTabPath =  getResource("/kerberos/http.keytab");
         DEFAULT_VALUES.put(KerberosConstants.KEYTAB, keyTabPath);
         DEFAULT_VALUES.put(KerberosConstants.DEBUG, "true");
         DEFAULT_VALUES.put(KerberosConstants.ALLOW_PASSWORD_AUTHENTICATION, "true");
@@ -102,8 +103,10 @@ public class LDAPTestConfiguration {
         return ldapTestConfiguration;
     }
     
-    public static String getResource(String resourceName) {
-        return new File(PROJECT_BUILD_DIRECTORY, "dependency/kerberos/" + resourceName).getAbsolutePath();
+    public static String getResource(String resourcePath) {
+        URL urlPath = LDAPTestConfiguration.class.getResource(resourcePath);
+        String absolutePath = new File(urlPath.getFile()).getAbsolutePath();
+        return absolutePath;
     }
 
     protected void loadConnectionProperties(String connectionPropertiesLocation) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
index d6f9134..332dd2f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
@@ -59,6 +59,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.keycloak.testsuite.util.IOUtil.loadJson;
 import static org.keycloak.testsuite.util.IOUtil.loadRealm;
+import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -142,6 +143,22 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
     }
 
     @Test
+    public void createAlbumWithInvalidUser() {
+        try {
+            this.deployer.deploy(RESOURCE_SERVER_ID);
+
+            loginToClientPage("alice", "alice");
+
+            clientPage.createAlbumWithInvalidUser("Alice Family Album");
+
+            waitUntilElement(clientPage.getOutput()).text().not().contains("Request was successful");
+            waitUntilElement(clientPage.getOutput()).text().contains("Could not register protected resource");
+        } finally {
+            this.deployer.undeploy(RESOURCE_SERVER_ID);
+        }
+    }
+
+    @Test
     public void testOnlyOwnerCanDeleteAlbum() throws Exception {
         try {
             this.deployer.deploy(RESOURCE_SERVER_ID);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AuthorizationDisabledInPreviewTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AuthorizationDisabledInPreviewTest.java
index 072aa5f..8bb9bcb 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AuthorizationDisabledInPreviewTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AuthorizationDisabledInPreviewTest.java
@@ -26,6 +26,7 @@ import javax.ws.rs.ServerErrorException;
 import javax.ws.rs.core.Response;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -44,7 +45,9 @@ public class AuthorizationDisabledInPreviewTest extends AbstractClientTest {
             testRealmResource().clients().get(id).authorization().getSettings();
         } catch (ServerErrorException e) {
             assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), e.getResponse().getStatus());
+            return;
         }
+        fail("Feature Authorization should be disabled.");
     }
 
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationDisabledTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationDisabledTest.java
new file mode 100644
index 0000000..ef1c1c3
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationDisabledTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.admin;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.ws.rs.ServerErrorException;
+import javax.ws.rs.core.Response;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import org.junit.Assume;
+import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
+
+/**
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class ImpersonationDisabledTest extends AbstractAdminTest {
+
+    @BeforeClass
+    public static void enabled() {
+        Assume.assumeTrue("impersonation".equals(System.getProperty("feature.name")) 
+                && "disabled".equals(System.getProperty("feature.value")));
+    }
+
+    @Test
+    public void testImpersonationDisabled() {
+        String impersonatedUserId = adminClient.realm(TEST).users().search("test-user@localhost", 0, 1).get(0).getId();
+        
+        try {
+            adminClient.realms().realm("test").users().get(impersonatedUserId).impersonate();
+        } catch (ServerErrorException e) {
+            assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), e.getResponse().getStatus());
+            return;
+        }
+        fail("Feature impersonation should be disabled.");
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
index ca01045..26b1b16 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
@@ -47,6 +47,8 @@ import javax.ws.rs.core.Response;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import org.junit.Assume;
+import org.junit.BeforeClass;
 
 /**
  * Tests Undertow Adapter
@@ -80,6 +82,12 @@ public class ImpersonationTest extends AbstractKeycloakTest {
 
         testRealms.add(realm.build());
     }
+    
+    @BeforeClass
+    public static void enabled() {
+        Assume.assumeFalse("impersonation".equals(System.getProperty("feature.name")) 
+                && "disabled".equals(System.getProperty("feature.value")));
+    }
 
     @Test
     public void testImpersonateByMasterAdmin() {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
new file mode 100644
index 0000000..2164ccd
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.kerberos;
+
+import java.net.URI;
+import java.security.Principal;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.security.sasl.Sasl;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.ietf.jgss.GSSCredential;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.resteasy.client.jaxrs.ResteasyClient;
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.adapters.HttpClientBuilder;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.common.constants.KerberosConstants;
+import org.keycloak.common.util.KerberosSerializationUtils;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.events.Details;
+import org.keycloak.federation.kerberos.CommonKerberosConfig;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.testsuite.AbstractAuthTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.auth.page.AuthRealm;
+import org.keycloak.testsuite.pages.AccountPasswordPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.util.OAuthClient;
+import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
+import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractKerberosTest extends AbstractAuthTest {
+
+    protected KeycloakSPNegoSchemeFactory spnegoSchemeFactory;
+
+    protected ResteasyClient client;
+
+    @Page
+    protected LoginPage loginPage;
+
+    @Rule
+    public AssertEvents events = new AssertEvents(this);
+
+    @Page
+    protected AccountPasswordPage changePasswordPage;
+
+    protected abstract CommonKerberosConfig getKerberosConfig();
+
+    protected abstract ComponentRepresentation getUserStorageConfiguration();
+
+    protected abstract void setKrb5ConfPath();
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        RealmRepresentation realmRep = loadJson(getClass().getResourceAsStream("/kerberos/kerberosrealm.json"), RealmRepresentation.class);
+        testRealms.add(realmRep);
+    }
+
+
+    @Before
+    public void beforeAbstractKeycloakTest() throws Exception {
+        super.beforeAbstractKeycloakTest();
+
+        testRealmPage.setAuthRealm(AuthRealm.TEST);
+        changePasswordPage.realm(AuthRealm.TEST);
+
+        setKrb5ConfPath();
+
+        spnegoSchemeFactory = new KeycloakSPNegoSchemeFactory(getKerberosConfig());
+        initHttpClient(true);
+        removeAllUsers();
+
+        oauth.clientId("kerberos-app");
+
+        ComponentRepresentation rep = getUserStorageConfiguration();
+        testRealmResource().components().add(rep);
+    }
+
+    @After
+    public void afterAbstractKeycloakTest() {
+        cleanupApacheHttpClient();
+
+        super.afterAbstractKeycloakTest();
+    }
+
+    private void cleanupApacheHttpClient() {
+        client.close();
+        client = null;
+    }
+
+//    @Test
+//    public void sleepTest() throws Exception {
+//        String kcLoginPageLocation = oauth.getLoginFormUrl();
+//        Thread.sleep(10000000);
+//    }
+
+
+    @Test
+    public void spnegoNotAvailableTest() throws Exception {
+        initHttpClient(false);
+
+        String kcLoginPageLocation = oauth.getLoginFormUrl();
+
+        Response response = client.target(kcLoginPageLocation).request().get();
+        Assert.assertEquals(401, response.getStatus());
+        Assert.assertEquals(KerberosConstants.NEGOTIATE, response.getHeaderString(HttpHeaders.WWW_AUTHENTICATE));
+        String responseText = response.readEntity(String.class);
+        response.close();
+    }
+
+
+    protected OAuthClient.AccessTokenResponse spnegoLoginTestImpl() throws Exception {
+        Response spnegoResponse = spnegoLogin("hnelson", "secret");
+        Assert.assertEquals(302, spnegoResponse.getStatus());
+
+        List<UserRepresentation> users = testRealmResource().users().search("hnelson", 0, 1);
+        String userId = users.get(0).getId();
+        events.expectLogin()
+                .client("kerberos-app")
+                .user(userId)
+                .detail(Details.USERNAME, "hnelson")
+                .assertEvent();
+
+        String codeUrl = spnegoResponse.getLocation().toString();
+
+        return assertAuthenticationSuccess(codeUrl);
+    }
+
+
+    protected abstract boolean isCaseSensitiveLogin();
+
+    // KEYCLOAK-2102
+    @Test
+    public void spnegoCaseInsensitiveTest() throws Exception {
+        Response spnegoResponse = spnegoLogin(isCaseSensitiveLogin() ? "MyDuke" : "myduke", "theduke");
+        Assert.assertEquals(302, spnegoResponse.getStatus());
+        List<UserRepresentation> users = testRealmResource().users().search("myduke", 0, 1);
+        String userId = users.get(0).getId();
+        events.expectLogin()
+                .client("kerberos-app")
+                .user(userId)
+                .detail(Details.USERNAME, "myduke")
+                .assertEvent();
+
+        String codeUrl = spnegoResponse.getLocation().toString();
+
+        assertAuthenticationSuccess(codeUrl);
+    }
+
+    @Test
+    public void usernamePasswordLoginTest() throws Exception {
+        // Change editMode to READ_ONLY
+        updateProviderEditMode(UserStorageProvider.EditMode.READ_ONLY);
+
+        // Login with username/password from kerberos
+        changePasswordPage.open();
+        loginPage.assertCurrent();
+        loginPage.login("jduke", "theduke");
+        changePasswordPage.assertCurrent();
+
+        // Bad existing password
+        changePasswordPage.changePassword("theduke-invalid", "newPass", "newPass");
+        Assert.assertTrue(driver.getPageSource().contains("Invalid existing password."));
+
+        // Change password is not possible as editMode is READ_ONLY
+        changePasswordPage.changePassword("theduke", "newPass", "newPass");
+        Assert.assertTrue(
+                driver.getPageSource().contains("You can't update your password as your account is read only"));
+
+        // Change editMode to UNSYNCED
+        updateProviderEditMode(UserStorageProvider.EditMode.UNSYNCED);
+
+        // Successfully change password now
+        changePasswordPage.changePassword("theduke", "newPass", "newPass");
+        Assert.assertTrue(driver.getPageSource().contains("Your password has been updated."));
+        changePasswordPage.logout();
+
+        // Login with old password doesn't work, but with new password works
+        loginPage.login("jduke", "theduke");
+        loginPage.assertCurrent();
+        loginPage.login("jduke", "newPass");
+        changePasswordPage.assertCurrent();
+        changePasswordPage.logout();
+
+        // Assert SPNEGO login still with the old password as mode is unsynced
+        events.clear();
+        Response spnegoResponse = spnegoLogin("jduke", "theduke");
+        Assert.assertEquals(302, spnegoResponse.getStatus());
+        List<UserRepresentation> users = testRealmResource().users().search("jduke", 0, 1);
+        String userId = users.get(0).getId();
+        events.expectLogin()
+                .client("kerberos-app")
+                .user(userId)
+                .detail(Details.USERNAME, "jduke")
+                .assertEvent();
+
+        String codeUrl = spnegoResponse.getLocation().toString();
+
+        assertAuthenticationSuccess(codeUrl);
+    }
+
+
+    @Test
+    public void credentialDelegationTest() throws Exception {
+        // Add kerberos delegation credential mapper
+        ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
+                KerberosConstants.GSS_DELEGATION_CREDENTIAL,
+                KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String",
+                true, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
+                true, false);
+        ProtocolMapperRepresentation protocolMapperRep = ModelToRepresentation.toRepresentation(protocolMapper);
+        ClientResource clientResource = findClientByClientId(testRealmResource(), "kerberos-app");
+        Response response = clientResource.getProtocolMappers().createMapper(protocolMapperRep);
+        String protocolMapperId = ApiUtil.getCreatedId(response);
+        response.close();
+
+        // SPNEGO login
+        OAuthClient.AccessTokenResponse tokenResponse = spnegoLoginTestImpl();
+
+        // Assert kerberos ticket in the accessToken can be re-used to authenticate against other 3rd party kerberos service (ApacheDS Server in this case)
+        String accessToken = tokenResponse.getAccessToken();
+        AccessToken token = oauth.verifyToken(accessToken);
+
+        String serializedGssCredential = (String) token.getOtherClaims().get(KerberosConstants.GSS_DELEGATION_CREDENTIAL);
+        Assert.assertNotNull(serializedGssCredential);
+        GSSCredential gssCredential = KerberosSerializationUtils.deserializeCredential(serializedGssCredential);
+        String ldapResponse = invokeLdap(gssCredential, token.getPreferredUsername());
+        Assert.assertEquals("Horatio Nelson", ldapResponse);
+
+        // Logout
+        oauth.openLogout();
+
+        // Remove protocolMapper
+        clientResource.getProtocolMappers().delete(protocolMapperId);
+
+        // Login and assert delegated credential not anymore
+        tokenResponse = spnegoLoginTestImpl();
+        accessToken = tokenResponse.getAccessToken();
+        token = oauth.verifyToken(accessToken);
+        Assert.assertFalse(token.getOtherClaims().containsKey(KerberosConstants.GSS_DELEGATION_CREDENTIAL));
+
+        events.clear();
+    }
+
+    private String invokeLdap(GSSCredential gssCredential, String username) throws NamingException {
+        Hashtable env = new Hashtable(11);
+        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+        env.put(Context.PROVIDER_URL, "ldap://localhost:10389");
+
+        if (gssCredential != null) {
+            env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+            env.put(Sasl.CREDENTIALS, gssCredential);
+        }
+
+        DirContext ctx = new InitialDirContext(env);
+        try {
+            Attributes attrs = ctx.getAttributes("uid=" + username + ",ou=People,dc=keycloak,dc=org");
+            String cn = (String) attrs.get("cn").get();
+            String sn = (String) attrs.get("sn").get();
+            return cn + " " + sn;
+        } finally {
+            ctx.close();
+        }
+    }
+
+
+    protected Response spnegoLogin(String username, String password) {
+        String kcLoginPageLocation = oauth.getLoginFormUrl();
+
+        // Request for SPNEGO login sent with Resteasy client
+        spnegoSchemeFactory.setCredentials(username, password);
+        Response response = client.target(kcLoginPageLocation).request().get();
+        if (response.getStatus() == 302) {
+            if (response.getLocation() == null)
+                return response;
+            String uri = response.getLocation().toString();
+            if (uri.contains("login-actions/required-action")) {
+                response = client.target(uri).request().get();
+            }
+        }
+        return response;
+
+    }
+
+
+    protected void initHttpClient(boolean useSpnego) {
+        if (client != null) {
+            cleanupApacheHttpClient();
+        }
+
+        DefaultHttpClient httpClient = (DefaultHttpClient) new HttpClientBuilder().build();
+        httpClient.getAuthSchemes().register(AuthPolicy.SPNEGO, spnegoSchemeFactory);
+
+        if (useSpnego) {
+            Credentials fake = new Credentials() {
+
+                public String getPassword() {
+                    return null;
+                }
+
+                public Principal getUserPrincipal() {
+                    return null;
+                }
+
+            };
+
+            httpClient.getCredentialsProvider().setCredentials(
+                    new AuthScope(null, -1, null),
+                    fake);
+        }
+        ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient);
+        client = new ResteasyClientBuilder().httpEngine(engine).build();
+    }
+
+
+    protected void removeAllUsers() {
+        RealmResource realm = testRealmResource();
+        List<UserRepresentation> users = realm.users().search("", 0, Integer.MAX_VALUE);
+        for (UserRepresentation user : users) {
+            if (!user.getUsername().equals(AssertEvents.DEFAULT_USERNAME)) {
+                realm.users().get(user.getId()).remove();
+            }
+        }
+        Assert.assertEquals(1, realm.users().search("", 0, Integer.MAX_VALUE).size());
+    }
+
+
+    protected void assertUser(String expectedUsername, String expectedEmail, String expectedFirstname,
+                              String expectedLastname, boolean updateProfileActionExpected) {
+        try {
+            UserRepresentation user = ApiUtil.findUserByUsername(testRealmResource(), expectedUsername);
+            Assert.assertNotNull(user);
+            Assert.assertEquals(expectedEmail, user.getEmail());
+            Assert.assertEquals(expectedFirstname, user.getFirstName());
+            Assert.assertEquals(expectedLastname, user.getLastName());
+
+            if (updateProfileActionExpected) {
+                Assert.assertEquals(UserModel.RequiredAction.UPDATE_PROFILE.toString(),
+                        user.getRequiredActions().iterator().next());
+            } else {
+                Assert.assertTrue(user.getRequiredActions().isEmpty());
+            }
+        } finally {
+        }
+    }
+
+
+    protected OAuthClient.AccessTokenResponse assertAuthenticationSuccess(String codeUrl) throws Exception {
+        List<NameValuePair> pairs = URLEncodedUtils.parse(new URI(codeUrl), "UTF-8");
+        String code = null;
+        String state = null;
+        for (NameValuePair pair : pairs) {
+            if (pair.getName().equals(OAuth2Constants.CODE)) {
+                code = pair.getValue();
+            } else if (pair.getName().equals(OAuth2Constants.STATE)) {
+                state = pair.getValue();
+            }
+        }
+        Assert.assertNotNull(code);
+        Assert.assertNotNull(state);
+        OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+        Assert.assertNotNull(response.getAccessToken());
+        events.clear();
+        return response;
+    }
+
+
+    protected void updateProviderEditMode(UserStorageProvider.EditMode editMode) {
+        List<ComponentRepresentation> reps = testRealmResource().components().query("test", UserStorageProvider.class.getName());
+        Assert.assertEquals(1, reps.size());
+        ComponentRepresentation kerberosProvider = reps.get(0);
+        kerberosProvider.getConfig().putSingle(LDAPConstants.EDIT_MODE, editMode.toString());
+        testRealmResource().components().component(kerberosProvider.getId()).update(kerberosProvider);
+    }
+
+    public RealmResource testRealmResource() {
+        return adminClient.realm("test");
+    }
+
+
+    // TODO: Use LDAPTestUtils.toComponentConfig once it's migrated to new testsuite
+    public static MultivaluedHashMap<String, String> toComponentConfig(Map<String, String> ldapConfig) {
+        MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+        for (Map.Entry<String, String> entry : ldapConfig.entrySet()) {
+            config.add(entry.getKey(), entry.getValue());
+
+        }
+        return config;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
new file mode 100644
index 0000000..8cdb9bf
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.kerberos;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+import javax.ws.rs.core.Response;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.keycloak.common.constants.KerberosConstants;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.federation.kerberos.CommonKerberosConfig;
+import org.keycloak.federation.kerberos.KerberosConfig;
+import org.keycloak.federation.kerberos.KerberosFederationProviderFactory;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.testsuite.util.KerberosRule;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KerberosStandaloneTest extends AbstractKerberosTest {
+
+    private static final String PROVIDER_CONFIG_LOCATION = "classpath:kerberos/kerberos-standalone-connection.properties";
+
+    @ClassRule
+    public static KerberosRule kerberosRule = new KerberosRule(PROVIDER_CONFIG_LOCATION);
+
+    @Override
+    protected CommonKerberosConfig getKerberosConfig() {
+        return new KerberosConfig(getUserStorageConfiguration());
+    }
+
+    @Override
+    protected ComponentRepresentation getUserStorageConfiguration() {
+        Map<String,String> kerberosConfig = kerberosRule.getConfig();
+        MultivaluedHashMap<String, String> config = toComponentConfig(kerberosConfig);
+
+        UserStorageProviderModel model = new UserStorageProviderModel();
+        model.setLastSync(0);
+        model.setChangedSyncPeriod(-1);
+        model.setFullSyncPeriod(-1);
+        model.setName("kerberos-standalone");
+        model.setPriority(0);
+        model.setProviderId(KerberosFederationProviderFactory.PROVIDER_NAME);
+        model.setConfig(config);
+
+        ComponentRepresentation rep = ModelToRepresentation.toRepresentationWithoutConfig(model);
+        return rep;
+    }
+
+
+    @Override
+    protected boolean isCaseSensitiveLogin() {
+        return kerberosRule.isCaseSensitiveLogin();
+    }
+
+
+    @Override
+    protected void setKrb5ConfPath() {
+        kerberosRule.setKrb5ConfPath(testingClient.testing());
+    }
+
+    @Test
+    public void spnegoLoginTest() throws Exception {
+        spnegoLoginTestImpl();
+
+        // Assert user was imported and hasn't any required action on him. Profile info is synced from LDAP
+        assertUser("hnelson", "hnelson@keycloak.org", null, null, false);
+    }
+
+
+    @Test
+    public void updateProfileEnabledTest() throws Exception {
+        // Switch updateProfileOnFirstLogin to on
+        List<ComponentRepresentation> reps = testRealmResource().components().query("test", UserStorageProvider.class.getName());
+        org.keycloak.testsuite.Assert.assertEquals(1, reps.size());
+        ComponentRepresentation kerberosProvider = reps.get(0);
+        kerberosProvider.getConfig().putSingle(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN, "true");
+        testRealmResource().components().component(kerberosProvider.getId()).update(kerberosProvider);
+
+        // Assert update profile page is displayed
+        Response spnegoResponse = spnegoLogin("hnelson", "secret");
+        Assert.assertEquals(200, spnegoResponse.getStatus());
+        String responseText = spnegoResponse.readEntity(String.class);
+        Assert.assertTrue(responseText.contains("You need to update your user profile to activate your account."));
+        Assert.assertTrue(responseText.contains("hnelson@keycloak.org"));
+        spnegoResponse.close();
+
+        // Assert user was imported and has required action on him
+        assertUser("hnelson", "hnelson@keycloak.org", null, null, true);
+
+        // Switch updateProfileOnFirstLogin to off
+        kerberosProvider.getConfig().putSingle(KerberosConstants.UPDATE_PROFILE_FIRST_LOGIN, "false");
+        testRealmResource().components().component(kerberosProvider.getId()).update(kerberosProvider);
+    }
+
+
+    /**
+     * KEYCLOAK-3451
+     *
+     * Test that if there is no User Storage Provider that can handle kerberos we can still login
+     *
+     * @throws Exception
+     */
+    @Test
+    public void noProvider() throws Exception {
+        List<ComponentRepresentation> reps = testRealmResource().components().query("test", UserStorageProvider.class.getName());
+        org.keycloak.testsuite.Assert.assertEquals(1, reps.size());
+        ComponentRepresentation kerberosProvider = reps.get(0);
+        testRealmResource().components().component(kerberosProvider.getId()).remove();
+
+        /*
+         To do this we do a valid kerberos login.  The authenticator will obtain a valid token, but there will
+         be no user storage provider that can process it.  This means we should be on the login page.
+         We do this through a JAX-RS client request.  We extract the action URL from the login page, and stuff it
+         into selenium then just perform a regular login.
+         */
+        Response spnegoResponse = spnegoLogin("hnelson", "secret");
+        String context = spnegoResponse.readEntity(String.class);
+        spnegoResponse.close();
+        Pattern pattern = Pattern.compile("action=\"([^\"]+)\"");
+        Matcher m = pattern.matcher(context);
+        Assert.assertTrue(m.find());
+        String url = m.group(1);
+        driver.navigate().to(url);
+        Assert.assertTrue(loginPage.isCurrent());
+        loginPage.login("test-user@localhost", "password");
+        String pageSource = driver.getPageSource();
+        assertAuthenticationSuccess(driver.getCurrentUrl());
+
+        events.clear();
+        testRealmResource().components().add(kerberosProvider);
+    }
+
+
+    /**
+     * KEYCLOAK-4178
+     *
+     * Assert it's handled when kerberos realm is unreachable
+     *
+     * @throws Exception
+     */
+    @Test
+    public void handleUnknownKerberosRealm() throws Exception {
+        // Switch kerberos realm to "unavailable"
+        List<ComponentRepresentation> reps = testRealmResource().components().query("test", UserStorageProvider.class.getName());
+        org.keycloak.testsuite.Assert.assertEquals(1, reps.size());
+        ComponentRepresentation kerberosProvider = reps.get(0);
+        kerberosProvider.getConfig().putSingle(KerberosConstants.KERBEROS_REALM, "unavailable");
+        testRealmResource().components().component(kerberosProvider.getId()).update(kerberosProvider);
+
+        // Try register new user and assert it failed
+        UserRepresentation john = new UserRepresentation();
+        john.setUsername("john");
+        Response response = testRealmResource().users().create(john);
+        Assert.assertEquals(500, response.getStatus());
+        response.close();
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
index a3ff894..cf041ed 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
@@ -19,6 +19,8 @@ package org.keycloak.testsuite.migration;
 import java.util.HashSet;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.arquillian.migration.Migration;
@@ -135,8 +137,34 @@ public class MigrationTest extends AbstractKeycloakTest {
      */
     private void testMigrationTo2_3_0() {
         testUpdateProtocolMappers(masterRealm, migrationRealm);
+        testExtractRealmKeys(masterRealm, migrationRealm);
     }
-    
+
+    private void testExtractRealmKeys(RealmResource masterRealm, RealmResource migrationRealm) {
+        String expectedMasterRealmKey = "MIIEowIBAAKCAQEAiU54OXoCbHy0L0gHn1yasctcnKHRU1pHFIJnWvaI7rClJydet9dDJaiYXOxMKseiBm3eYznfN3cPyU8udYmRnMuKjiocZ77LT2IEttAjXb6Ggazx7loriFHRy0IOJeX4KxXhAPWmxqa3mkFNfLBEvFqVaBgUDHQ60cmnPvNSHYudBTW9K80s8nvmP2pso7HTwWJ1+Xatj1Ey/gTmB3CXlyqBegGWC9TeuErEYpYhdh+11TVWasgMBZyUCtL3NRPaBuhaPg1LpW8lWGk05nS+YM6dvTk3Mppv+z2RygEpxyO09oT3b4G+Zfwit1STqn0AvDTGzINdoKcNtFScV0j8TwIDAQABAoIBAHcbPKsPLZ8SJfOF1iblW8OzFulAbaaSf2pJHIMJrQrw7LKkMkPjVXoLX+/rgr7xYZmWIP2OLBWfEHCeYTzQUyHiZpSf7vgHx7Fa45/5uVQOe/ttHIiYa37bCtP4vvEdJkOpvP7qGPvljwsebqsk9Ns28LfVez66bHOjK5Mt2yOIulbTeEs7ch//h39YwKJv96vc+CHbV2O6qoOxZessO6y+287cOBvbFXmS2GaGle5Nx/EwncBNS4b7czoetmm70+9ht3yX+kxaP311YUT31KQjuaJt275kOiKsrXr27PvgO++bsIyGuSzqyS7G7fmxF2zUyphEqEpalyDGMKMnrAECgYEA1fCgFox03rPDjm0MhW/ThoS2Ld27sbWQ6reS+PBMdUTJZVZIU1D2//h6VXDnlddhk6avKjA4smdy1aDKzmjz3pt9AKn+kgkXqtTC2fD3wp+fC9hND0z+rQPGe/Gk7ZUnTdsqnfyowxr+woIgzdnRukOUrG+xQiP3RUUT7tt6NQECgYEApEz2xvgqMm+9/f/YxjLdsFUfLqc4WlafB863stYEVqlCYy5ujyo0VQ0ahKSKJkLDnf52+aMUqPOpwaGePpu3O6VkvpcKfPY2MUlZW7/6Sa9et9hxNkdTS7Gui2d1ELpaCBe1Bc62sk8EA01iHXE1PpvyUqDWrhNh+NrDICA9oU8CgYBgGDYACtTP11TmW2r9YK5VRLUDww30k4ZlN1GnyV++aMhBYVEZQ0u+y+A/EnijIFwu0vbo70H4OGknNZMCxbeMbLDoJHM5KyZbUDe5ZvgSjloFGwH59m6KTiDQOUkIgi9mVCQ/VGaFRFHcElEjxUvj60kTbxPijn8ZuR5r8l9hAQKBgQCQ9jL5pHWeoIayN20smi6M6N2lTPbkhe60dcgQatHTIG2pkosLl8IqlHAkPgSB84AiwyR351JQKwRJCm7TcJI/dxMnMZ6YWKfB3qSP1hdfsfJRJQ/mQxIUBAYrizF3e+P5peka4aLCOgMhYsJBlePThMZN7wja99EGPwXQL4IQ8wKBgB8Nis1lQK6Z30GCp9u4dYleGfEP71Lwqvk/eJb89/uz0fjF9CTpJMULFc+nA5u4yHP3LFnRg3zCU6aEwfwUyk4GH9lWGV/qIAisQtgrCEraVe4qxz0DVE59C7qjO26IhU2U66TEzPAqvQ3zqey+woDn/cz/JMWK1vpcSk+TKn3K";
+        String expectedMigrationRealmKey = "MIIEpAIBAAKCAQEApt6gCllWkVTZ7fy/oRIx6Bxjt9x3eKKyKGFXvN4iaafrNqpYU9lcqPngWJ9DyXGqUf8RpjPaQWiLWLxjw3xGBqLk2E1/Frb9e/dy8rj//fHGq6bujN1iguzyFwxPGT5Asd7jflRI3qU04M8JE52PArqPhGL2Fn+FiSK5SWRIGm+hVL7Ck/E/tVxM25sFG1/UTQqvrROm4q76TmP8FsyZaTLVf7cCwW2QPIX0N5HTVb3QbBb5KIsk4kKmk/g7uUxS9r42tu533LISzRr5CTyWZAL2XFRuF2RrKdE8gwqkEubw6sDmB2mE0EoPdY1DUhBQgVP/5rwJrCtTsUBR2xdEYQIDAQABAoIBAFbbsNBSOlZBpYJUOmcb8nBQPrOYhXN8tGGCccn0klMOvcdhmcJjdPDbyCQ5Gm7DxJUTwNsTSHsdcNMKlJ9Pk5+msJnKlOl87KrXXbTsCQvlCrWUmb0nCzz9GvJWTOHl3oT3cND0DE4gDksqWR4luCgCdevCGzgQvrBoK6wBD+r578uEW3iw10hnJ0+wnGiw8IvPzE1a9xbY4HD8/QrYdaLxuLb/aC1PDuzrz0cOjnvPkrws5JrbUSnbFygJiOv1z4l2Q00uGIxlHtXdwQBnTZZjVi4vOec2BYSHffgwDYEZIglw1mnrV7y0N1nnPbtJK/cegIkXoBQHXm8Q99TrWMUCgYEA9au86qcwrXZZg5H4BpR5cpy0MSkcKDbA1aRL1cAyTCqJxsczlAtLhFADF+NhnlXj4y7gwDEYWrz064nF73I+ZGicvCiyOy+tCTugTyTGS+XR948ElDMS6PCUUXsotS3dKa0b3c9wd2mxeddTjq/ArfgEVZJ6fE1KtjLt9dtfA+8CgYEAreK3JsvjR5b/Xct28TghYUU7Qnasombb/shqqy8FOMjYUr5OUm/OjNIgoCqhOlE8oQDJ4dOZofNSa7tL+oM8Gmbal+E3fRzxnx/9/EC4QV6sVaPLTIyk7EPfKTcZuzH7+BNZtAziTxJw9d6YJQRbkpg92EZIEoR8iDj2Xs5xrK8CgYEAwMVWwwYX8zT3vn7ukTM2LRH7bsvkVUXJgJqgCwT6Mrv6SmkK9vL5+cPS+Y6pjdW1sRGauBSOGL1Grf/4ug/6F03jFt4UJM8fRyxreU7Q7sNSQ6AMpsGA6BnHODycz7ZCYa59PErG5FyiL4of/cm5Nolz1TXQOPNpWZiTEqVlZC8CgYA4YPbjVF4nuxSnU64H/hwMjsbtAM9uhI016cN0J3W4+J3zDhMU9X1x+Tts0wWdg/N1fGz4lIQOl3cUyRCUc/KL2OdtMS+tmDHbVyMho9ZaE5kq10W2Vy+uDz+O/HeSU12QDK4cC8Vgv+jyPy7zaZtLR6NduUPrBRvfiyCOkr8WrwKBgQCY0h4RCdNFhr0KKLLmJipAtV8wBCGcg1jY1KoWKQswbcykfBKwHbF6EooVqkRW0ITjWB7ZZCf8TnSUxe0NXCUAkVBrhzS4DScgtoSZYOOUaSHgOxpfwgnQ3oYotKi98Yg3IsaLs1j4RuPG5Sp1z6o+ELP1uvr8azyn9YlLa+523Q==";
+
+        List<ComponentRepresentation> components = masterRealm.components().query(MASTER, KeyProvider.class.getName());
+        assertEquals(2, components.size());
+
+        components = masterRealm.components().query(MASTER, KeyProvider.class.getName(), "rsa");
+        assertEquals(1, components.size());
+        assertEquals(expectedMasterRealmKey, testingClient.testing(MASTER).getComponentConfig(components.get(0).getId()).getFirst("privateKey"));
+
+        components = masterRealm.components().query(MASTER, KeyProvider.class.getName(), "hmac-generated");
+        assertEquals(1, components.size());
+
+        components = migrationRealm.components().query(MIGRATION, KeyProvider.class.getName());
+        assertEquals(2, components.size());
+
+        components = migrationRealm.components().query(MIGRATION, KeyProvider.class.getName(), "rsa");
+        assertEquals(1, components.size());
+        assertEquals(expectedMigrationRealmKey, testingClient.testing(MIGRATION).getComponentConfig(components.get(0).getId()).getFirst("privateKey"));
+
+        components = migrationRealm.components().query(MIGRATION, KeyProvider.class.getName(), "hmac-generated");
+        assertEquals(1, components.size());
+    }
+
     private void testMigrationTo2_5_0() {
         //TODO org.keycloak.migration.migrators.MigrateTo2_5_0
         
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
index 8dc077d..33f290f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
@@ -68,6 +68,7 @@
                 ${adapter.test.props}
                 ${migration.import.properties}
                 ${auth.server.profile}
+                ${auth.server.feature}
             </property>
             <property name="javaVmArguments">
                 ${auth.server.memory.settings}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-2.2.1.Final.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-2.2.1.Final.json
index db1c768..a739c46 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-2.2.1.Final.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-2.2.1.Final.json
@@ -28,10 +28,10 @@
   "quickLoginCheckMilliSeconds" : 1000,
   "maxDeltaTimeSeconds" : 43200,
   "failureFactor" : 30,
-  "privateKey" : "MIIEogIBAAKCAQEA0jK5KDa59d5WGINVY/pRZTaz57PjqgPsCGtLvLHan9FWLqz/DP+zZMJdu5HsPR7ZTMCZPeF5xzmM5I05XdSPkt9yNEDIXt04D+IZFMWaNLRp6fG2F8VRrZit6JXM/LvnsLC+JS2JY3xl2c8SiYyCRqz+2R2Bp62nAJqf7OJPYb2rHO4iAX9ZcHuNBqONl6BIgznlZccKCB+Fxbd1Qax1OnB95Q4yVjA2++sWfsRoUYVmUoUgGT1YPY/GTGFbNucGNbHzWsFIuy20cAv0IWbrV1KiN9VEd+MC7ebLPgsW0JtFSdsUKZt5PZnQ8tOOEvv/uJ7QvVBba6c8F0E8ffhGiQIDAQABAoIBACuVkujwuAxwJp/8k3cgj0ISXqlblohJOebhAJpGspNS37Db9CKo3nS1DKR49GgT/nC4us2m9A5IM5usYMnA6mhE5rRI/oMVEavRcOvqsTfedNuqX9x78JyzPNQuQ4vgFoF/lF23HHbBc4j/M9PLi7PF6S3xvVGcYzVv2ltO7bDRN+4lj+utg4fVGEPQvt3FDSoNdG1Ol+cshJ/tTB5kYq/nUXFYrk71fWKkpo38Q87UQJeKIlKGZ3L4TwlwKBeQzeCe2OIMoYi4sh0JRUOBFCPvjjWSJ1tNvNMkLw1tDC5FA4S4TrOMhiaSuyIckZ4a2rk8DKNJiTt9HruSYlStqRECgYEA7o1WL4xgTlgUpGPs63DCaWdUBkL5JXCGFyBgCwwoI2hJ+u5IVT4I5W6gmcAkXAKPLraXW4TnTb/UjrItA+miWAdWb//uIDhbwpRCj4/qJ0+gCOj1zN4c9J6XaRY5+zsy9hGi4rwN1Y3mWHDEsD0PDCCZ1c+p7JZt+bekRU2vwZUCgYEA4ZJ8RY0AKaUw7w+yCvEyU7M8px0rzNwW06t3LRoY0vlak02xUSqx2jSxCrObn/cFcymQVIhqk2KqIVvJuPLJotUHe5OJ5vIKM085Mf2PFW6em7K/zPNU1R4aYuXI+hBAWz5m4u30PbEAzzT+idb4v3JKKJZPcc8YLJoHq5GRHCUCgYB33wXD9uId/BzDToMq/Ap1NLsAOn8/1P9Md7RLWNyCjlE+iqZGT1nhEGobWHXs1kxkyNPd5Tbl1tNUsSv1n104ptdEiHJ8NlXFvQKFaWfcQSLF12pMfABI27hx0f1oxSKii9Ix5RV+jkOFhYUQml4JVgh3bFaS0DSLG0CsopEC0QKBgFPfU7XIp7bUZU1+WIUZCc11Z6f7gL27WAF8T+PFQFWxwavjx3qWlWq9DNcd0U+5WZ1oZvIHhxkmJnwg1PCf1bll3FJmmW/1rzVEz892qC0f5HmgDRD3qSNU/hqntgLE6CkFGtxl/dqyZcqlub8m33bP2CMfQIrgpexvmUqB5aLBAoGAb37J1Nowx+lLxukkCvXvfqeT6n/xikK7JhUBRXH9K6+mUpxeSzuDqaqsoUTsyd5aw10iJevNXmi0bu+gZOOzM2n+9YKUDRMJ7neA30Aa7Q+KTQYukwmXZ7GHVQzQgFFHt9CLMGRHxG4M1UHbiOtGlAf6SRRW9UUo0FygMeKFTV4=",
-  "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0jK5KDa59d5WGINVY/pRZTaz57PjqgPsCGtLvLHan9FWLqz/DP+zZMJdu5HsPR7ZTMCZPeF5xzmM5I05XdSPkt9yNEDIXt04D+IZFMWaNLRp6fG2F8VRrZit6JXM/LvnsLC+JS2JY3xl2c8SiYyCRqz+2R2Bp62nAJqf7OJPYb2rHO4iAX9ZcHuNBqONl6BIgznlZccKCB+Fxbd1Qax1OnB95Q4yVjA2++sWfsRoUYVmUoUgGT1YPY/GTGFbNucGNbHzWsFIuy20cAv0IWbrV1KiN9VEd+MC7ebLPgsW0JtFSdsUKZt5PZnQ8tOOEvv/uJ7QvVBba6c8F0E8ffhGiQIDAQAB",
-  "certificate" : "MIICmzCCAYMCBgFXuEgtnjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMTYxMDEyMDk0MzQwWhcNMjYxMDEyMDk0NTIwWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSMrkoNrn13lYYg1Vj+lFlNrPns+OqA+wIa0u8sdqf0VYurP8M/7Nkwl27kew9HtlMwJk94XnHOYzkjTld1I+S33I0QMhe3TgP4hkUxZo0tGnp8bYXxVGtmK3olcz8u+ewsL4lLYljfGXZzxKJjIJGrP7ZHYGnracAmp/s4k9hvasc7iIBf1lwe40Go42XoEiDOeVlxwoIH4XFt3VBrHU6cH3lDjJWMDb76xZ+xGhRhWZShSAZPVg9j8ZMYVs25wY1sfNawUi7LbRwC/QhZutXUqI31UR34wLt5ss+CxbQm0VJ2xQpm3k9mdDy044S+/+4ntC9UFtrpzwXQTx9+EaJAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABweR6V1xLtSuQp1VZcz7f5Y4Sop/REW61YfOfJ2jO6JUQKZIS1fypfS8H0Y6E4DWiq6K/Eiv/5Ph94DABlMxtpyVoU7YRNmRfaZYZPKzOiuUv4sdCXORTaF6SSr+Lxyh6XvIpB66g1i/KqFsBQ2w9pbpWj4QAyOW0bACkH3S1dTVWDTXzv07nxssVNq6TmT6mzKuPaaO2EX7fBaVNUesOzvDQcyOF+7oapjtMj+J87S/YiyLTgCywC8mFExO4YBXM/c6KRv4al40SLCkEii9NLlNxq/cLVdUPdkbelzIuqs9EOllvHDYB/QkWo3tvNvbPsca2MUNbvXqPMubTjzXwo=",
-  "codeSecret" : "766580d5-5599-42b3-8e4d-003043f39415",
+  "privateKey" : "MIIEowIBAAKCAQEAiU54OXoCbHy0L0gHn1yasctcnKHRU1pHFIJnWvaI7rClJydet9dDJaiYXOxMKseiBm3eYznfN3cPyU8udYmRnMuKjiocZ77LT2IEttAjXb6Ggazx7loriFHRy0IOJeX4KxXhAPWmxqa3mkFNfLBEvFqVaBgUDHQ60cmnPvNSHYudBTW9K80s8nvmP2pso7HTwWJ1+Xatj1Ey/gTmB3CXlyqBegGWC9TeuErEYpYhdh+11TVWasgMBZyUCtL3NRPaBuhaPg1LpW8lWGk05nS+YM6dvTk3Mppv+z2RygEpxyO09oT3b4G+Zfwit1STqn0AvDTGzINdoKcNtFScV0j8TwIDAQABAoIBAHcbPKsPLZ8SJfOF1iblW8OzFulAbaaSf2pJHIMJrQrw7LKkMkPjVXoLX+/rgr7xYZmWIP2OLBWfEHCeYTzQUyHiZpSf7vgHx7Fa45/5uVQOe/ttHIiYa37bCtP4vvEdJkOpvP7qGPvljwsebqsk9Ns28LfVez66bHOjK5Mt2yOIulbTeEs7ch//h39YwKJv96vc+CHbV2O6qoOxZessO6y+287cOBvbFXmS2GaGle5Nx/EwncBNS4b7czoetmm70+9ht3yX+kxaP311YUT31KQjuaJt275kOiKsrXr27PvgO++bsIyGuSzqyS7G7fmxF2zUyphEqEpalyDGMKMnrAECgYEA1fCgFox03rPDjm0MhW/ThoS2Ld27sbWQ6reS+PBMdUTJZVZIU1D2//h6VXDnlddhk6avKjA4smdy1aDKzmjz3pt9AKn+kgkXqtTC2fD3wp+fC9hND0z+rQPGe/Gk7ZUnTdsqnfyowxr+woIgzdnRukOUrG+xQiP3RUUT7tt6NQECgYEApEz2xvgqMm+9/f/YxjLdsFUfLqc4WlafB863stYEVqlCYy5ujyo0VQ0ahKSKJkLDnf52+aMUqPOpwaGePpu3O6VkvpcKfPY2MUlZW7/6Sa9et9hxNkdTS7Gui2d1ELpaCBe1Bc62sk8EA01iHXE1PpvyUqDWrhNh+NrDICA9oU8CgYBgGDYACtTP11TmW2r9YK5VRLUDww30k4ZlN1GnyV++aMhBYVEZQ0u+y+A/EnijIFwu0vbo70H4OGknNZMCxbeMbLDoJHM5KyZbUDe5ZvgSjloFGwH59m6KTiDQOUkIgi9mVCQ/VGaFRFHcElEjxUvj60kTbxPijn8ZuR5r8l9hAQKBgQCQ9jL5pHWeoIayN20smi6M6N2lTPbkhe60dcgQatHTIG2pkosLl8IqlHAkPgSB84AiwyR351JQKwRJCm7TcJI/dxMnMZ6YWKfB3qSP1hdfsfJRJQ/mQxIUBAYrizF3e+P5peka4aLCOgMhYsJBlePThMZN7wja99EGPwXQL4IQ8wKBgB8Nis1lQK6Z30GCp9u4dYleGfEP71Lwqvk/eJb89/uz0fjF9CTpJMULFc+nA5u4yHP3LFnRg3zCU6aEwfwUyk4GH9lWGV/qIAisQtgrCEraVe4qxz0DVE59C7qjO26IhU2U66TEzPAqvQ3zqey+woDn/cz/JMWK1vpcSk+TKn3K",
+  "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiU54OXoCbHy0L0gHn1yasctcnKHRU1pHFIJnWvaI7rClJydet9dDJaiYXOxMKseiBm3eYznfN3cPyU8udYmRnMuKjiocZ77LT2IEttAjXb6Ggazx7loriFHRy0IOJeX4KxXhAPWmxqa3mkFNfLBEvFqVaBgUDHQ60cmnPvNSHYudBTW9K80s8nvmP2pso7HTwWJ1+Xatj1Ey/gTmB3CXlyqBegGWC9TeuErEYpYhdh+11TVWasgMBZyUCtL3NRPaBuhaPg1LpW8lWGk05nS+YM6dvTk3Mppv+z2RygEpxyO09oT3b4G+Zfwit1STqn0AvDTGzINdoKcNtFScV0j8TwIDAQAB",
+  "certificate" : "MIICmzCCAYMCBgFXt/Tg9TANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMTYxMDEyMDgxMjQxWhcNMjYxMDEyMDgxNDIxWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCJTng5egJsfLQvSAefXJqxy1ycodFTWkcUgmda9ojusKUnJ16310MlqJhc7Ewqx6IGbd5jOd83dw/JTy51iZGcy4qOKhxnvstPYgS20CNdvoaBrPHuWiuIUdHLQg4l5fgrFeEA9abGpreaQU18sES8WpVoGBQMdDrRyac+81Idi50FNb0rzSzye+Y/amyjsdPBYnX5dq2PUTL+BOYHcJeXKoF6AZYL1N64SsRiliF2H7XVNVZqyAwFnJQK0vc1E9oG6Fo+DUulbyVYaTTmdL5gzp29OTcymm/7PZHKASnHI7T2hPdvgb5l/CK3VJOqfQC8NMbMg12gpw20VJxXSPxPAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAC54wFHL8tmrksq4OzatzNUM+R+3Hu/VXX3T44dwg0EvXzGW45sME+gKCuleU1PabIrr6oFm0bBMTdxgE2hbLWpYbU3OcsjArpCeCsOlxrAkqhVQN161J+tp77JkDMgArFdwe3wh5bhvLaOZSt6Fsq+oo16CXG1obe1feyaK3+sU3YuDUIHE01UYtvwtfDsYBC+VDyTdNDbB15WcdRoGljJY/JiT0JHdmAfq8qdGDuxGocIV0lSB8bO5JwF/WCmKqMrnh5j1NfGcE1g26Hbz2RmDs17X0K10Okzs/qz1YZqDjPVYiU//VFQQro71/D35dPOJv8mQMjhjNaXScL44h7w=",
+  "codeSecret" : "4c59c2db-d9c3-4023-8cd5-8808fe854e98",
   "roles" : {
     "realm" : [ {
       "id" : "40dd3051-9581-479d-9ae0-80abd28b3f94",
@@ -1533,10 +1533,10 @@
   "quickLoginCheckMilliSeconds" : 1000,
   "maxDeltaTimeSeconds" : 43200,
   "failureFactor" : 30,
-  "privateKey" : "MIIEowIBAAKCAQEAvXWOP4hFWeEmCiB6gEeoZfUt+2lRE19tvZ3eyTEdnJoSUAA3VjUNcZUk/sjT1hrgfzaiX7D7yMkDrmxS2aHyJvzpUhZ6CVFQ3c9S2oneU8tN4zi52tzC1i3AaljhO3eUyYa97FY27bNmwAF0MSSsYtRVA7woDN1PLj5NIWxQhs2kbhU6A6JTrsQonFQ7rStmldI8Odx/FQHPA8CnH7v44HWAaO7sdV7BdNWGkqtGzynEXSbqgdbWw2Nb0R5mNYvgP8visSFg+Uni5Nx6FbO305+qBf1xNxr27siTFDuUttfastFtCVf3HuuCDGGqQfNdpjdf/2n4T0aBlxQ/GPzctwIDAQABAoIBABPXDqlqjNCnukMPOaTbXDApJ8KPOdVMhgJ4WM863H38yHQmQaU+cwT0+GXSBBUExYOs5f4dW5CgNEl8wIRX6DLbA1f8UJVPNae0wVlvXJ/rx2FHHHdl7OVe7ypz2kpO8H7WJGduc9wakGUFAfTvgTnsQGbeulb2igcusQcR/7DOBAtNIwo0RBds0mQSJlLsRKYz/kna/oRDicSWq3VFWBkcT0jjvx3W7Qd42RSBZ2PrxdaiESEXz1oNlzHemiBAxi4jGqaLh2rg8A2O6mU75gHeaGtdin/uhxcPyPrau0iX5D3V6bsnLYnMQFSQ+AzE89GqA2o6hOtUrYlmlsOJqSECgYEA3/dSjdurt90IlFZc7FydGyJSMMcpHkRlhmpY9jbGmQAx3iPFi1E/uVg561vKNRt1N6+fj5oydkLnYBjTrpGzWJeJ+VZ+Au7rHMvq8lkB2l/uj/E9jCcH95cydHX/tV13WAMg7MJvpym6J+xPQn0+I1HTz9O6YKK+PwlzG+jWCwcCgYEA2I69EIWKkpR8qJG+Ext9ticx163jONr6bmsPbxzHEdMLVMmeNRp+8Exu0u35WzYH0LzmhR/Id9uTn4fh02XRM+GnYSRlJl8bHIsy9W9lKgpfz6xOk/OmQ0iO/lQW3T+FQjPEqPhttsL0ntdlwEtnSHswTtjEZrPlS3chyLowRNECgYBLm0Z3NS0nCKsYyH7eG/W9bzyn0tQCr7xGQCiAFcW9aN9syk6p1WfZvfrvhTEh9H8BqXwIElnuqm9JdZggWndc9OtTDOvFObqNIhnSblG90pzKYvoQEi0SXCxgPGVyaHcdcxDuXEUs/V0mlRO8smMDsV/7zNIiRU5MIXiGd3eqiQKBgBFLhQn2wFRoi3Qi/50jCu0rQC3Lc2QtCOccocTr9XIU+siwuXPgQ9cJI6iw3MNOE122WGq1WRtJbAbHoV9yuDx+fxGhCHsCCXZEH7O+S8QJxBEf376tLNAe0Vy6doEA+7GH1OLxxEFeXI77AONUolEFlZwWb8kMAPn/77DKPeShAoGBAL//+BGQnXuvywrmwUF3u+FDqwpkVi72ttSFFoF1pCR7KLD16CW/bw2tck0OvKte+VFBDzfpfiXH56wPOpGf2nVvUnkHgyy9CS4zc+Jj266QGKZ/SMv2aCofwdLdla4qLM+luxsMtFuAj3Z9G8thAfj5zHRA5eK28Wq4qacDg+IW",
-  "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXWOP4hFWeEmCiB6gEeoZfUt+2lRE19tvZ3eyTEdnJoSUAA3VjUNcZUk/sjT1hrgfzaiX7D7yMkDrmxS2aHyJvzpUhZ6CVFQ3c9S2oneU8tN4zi52tzC1i3AaljhO3eUyYa97FY27bNmwAF0MSSsYtRVA7woDN1PLj5NIWxQhs2kbhU6A6JTrsQonFQ7rStmldI8Odx/FQHPA8CnH7v44HWAaO7sdV7BdNWGkqtGzynEXSbqgdbWw2Nb0R5mNYvgP8visSFg+Uni5Nx6FbO305+qBf1xNxr27siTFDuUttfastFtCVf3HuuCDGGqQfNdpjdf/2n4T0aBlxQ/GPzctwIDAQAB",
-  "certificate" : "MIICoTCCAYkCBgFXuEp3fDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlNaWdyYXRpb24wHhcNMTYxMDEyMDk0NjEwWhcNMjYxMDEyMDk0NzUwWjAUMRIwEAYDVQQDDAlNaWdyYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dY4/iEVZ4SYKIHqAR6hl9S37aVETX229nd7JMR2cmhJQADdWNQ1xlST+yNPWGuB/NqJfsPvIyQOubFLZofIm/OlSFnoJUVDdz1Laid5Ty03jOLna3MLWLcBqWOE7d5TJhr3sVjbts2bAAXQxJKxi1FUDvCgM3U8uPk0hbFCGzaRuFToDolOuxCicVDutK2aV0jw53H8VAc8DwKcfu/jgdYBo7ux1XsF01YaSq0bPKcRdJuqB1tbDY1vRHmY1i+A/y+KxIWD5SeLk3HoVs7fTn6oF/XE3GvbuyJMUO5S219qy0W0JV/ce64IMYapB812mN1//afhPRoGXFD8Y/Ny3AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA6Sbqa4oxGXli57CF6vcI0NmqHkDQkrjAUNwfr98UR1/WGQc1+qabdcj8dyL2g3PTrI98CXHdxlvOOQfbniaB59usF1IKKqo7W6SzcmVVlQXP+g7WN0ldS4QYtqkA+LsWj6rBgz561WnflK0sRj0V+5QmrwifQ62syhnJ2FpYRwDHUyE9ezR3nSpkr1NO1S1Sh7VzLftm8YoZYC1Zy35FyK+JF3Utf3IRV1oe+fBtKWwfmw+Xh94sXIYpxAZrspSJLXEIP/dony4kt88KrFWnOotUT4ctECW/l57qczeKEP5BzrqlZCw65NVz55fiIh7sELp6ZW/aFPj1gNqhpK2j8=",
-  "codeSecret" : "1102419e-9b67-4ad7-8e93-5fa12153b022",
+  "privateKey" : "MIIEpAIBAAKCAQEApt6gCllWkVTZ7fy/oRIx6Bxjt9x3eKKyKGFXvN4iaafrNqpYU9lcqPngWJ9DyXGqUf8RpjPaQWiLWLxjw3xGBqLk2E1/Frb9e/dy8rj//fHGq6bujN1iguzyFwxPGT5Asd7jflRI3qU04M8JE52PArqPhGL2Fn+FiSK5SWRIGm+hVL7Ck/E/tVxM25sFG1/UTQqvrROm4q76TmP8FsyZaTLVf7cCwW2QPIX0N5HTVb3QbBb5KIsk4kKmk/g7uUxS9r42tu533LISzRr5CTyWZAL2XFRuF2RrKdE8gwqkEubw6sDmB2mE0EoPdY1DUhBQgVP/5rwJrCtTsUBR2xdEYQIDAQABAoIBAFbbsNBSOlZBpYJUOmcb8nBQPrOYhXN8tGGCccn0klMOvcdhmcJjdPDbyCQ5Gm7DxJUTwNsTSHsdcNMKlJ9Pk5+msJnKlOl87KrXXbTsCQvlCrWUmb0nCzz9GvJWTOHl3oT3cND0DE4gDksqWR4luCgCdevCGzgQvrBoK6wBD+r578uEW3iw10hnJ0+wnGiw8IvPzE1a9xbY4HD8/QrYdaLxuLb/aC1PDuzrz0cOjnvPkrws5JrbUSnbFygJiOv1z4l2Q00uGIxlHtXdwQBnTZZjVi4vOec2BYSHffgwDYEZIglw1mnrV7y0N1nnPbtJK/cegIkXoBQHXm8Q99TrWMUCgYEA9au86qcwrXZZg5H4BpR5cpy0MSkcKDbA1aRL1cAyTCqJxsczlAtLhFADF+NhnlXj4y7gwDEYWrz064nF73I+ZGicvCiyOy+tCTugTyTGS+XR948ElDMS6PCUUXsotS3dKa0b3c9wd2mxeddTjq/ArfgEVZJ6fE1KtjLt9dtfA+8CgYEAreK3JsvjR5b/Xct28TghYUU7Qnasombb/shqqy8FOMjYUr5OUm/OjNIgoCqhOlE8oQDJ4dOZofNSa7tL+oM8Gmbal+E3fRzxnx/9/EC4QV6sVaPLTIyk7EPfKTcZuzH7+BNZtAziTxJw9d6YJQRbkpg92EZIEoR8iDj2Xs5xrK8CgYEAwMVWwwYX8zT3vn7ukTM2LRH7bsvkVUXJgJqgCwT6Mrv6SmkK9vL5+cPS+Y6pjdW1sRGauBSOGL1Grf/4ug/6F03jFt4UJM8fRyxreU7Q7sNSQ6AMpsGA6BnHODycz7ZCYa59PErG5FyiL4of/cm5Nolz1TXQOPNpWZiTEqVlZC8CgYA4YPbjVF4nuxSnU64H/hwMjsbtAM9uhI016cN0J3W4+J3zDhMU9X1x+Tts0wWdg/N1fGz4lIQOl3cUyRCUc/KL2OdtMS+tmDHbVyMho9ZaE5kq10W2Vy+uDz+O/HeSU12QDK4cC8Vgv+jyPy7zaZtLR6NduUPrBRvfiyCOkr8WrwKBgQCY0h4RCdNFhr0KKLLmJipAtV8wBCGcg1jY1KoWKQswbcykfBKwHbF6EooVqkRW0ITjWB7ZZCf8TnSUxe0NXCUAkVBrhzS4DScgtoSZYOOUaSHgOxpfwgnQ3oYotKi98Yg3IsaLs1j4RuPG5Sp1z6o+ELP1uvr8azyn9YlLa+523Q==",
+  "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApt6gCllWkVTZ7fy/oRIx6Bxjt9x3eKKyKGFXvN4iaafrNqpYU9lcqPngWJ9DyXGqUf8RpjPaQWiLWLxjw3xGBqLk2E1/Frb9e/dy8rj//fHGq6bujN1iguzyFwxPGT5Asd7jflRI3qU04M8JE52PArqPhGL2Fn+FiSK5SWRIGm+hVL7Ck/E/tVxM25sFG1/UTQqvrROm4q76TmP8FsyZaTLVf7cCwW2QPIX0N5HTVb3QbBb5KIsk4kKmk/g7uUxS9r42tu533LISzRr5CTyWZAL2XFRuF2RrKdE8gwqkEubw6sDmB2mE0EoPdY1DUhBQgVP/5rwJrCtTsUBR2xdEYQIDAQAB",
+  "certificate" : "MIICoTCCAYkCBgFXt/t9TjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlNaWdyYXRpb24wHhcNMTYxMDEyMDgxOTU0WhcNMjYxMDEyMDgyMTM0WjAUMRIwEAYDVQQDDAlNaWdyYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCm3qAKWVaRVNnt/L+hEjHoHGO33Hd4orIoYVe83iJpp+s2qlhT2Vyo+eBYn0PJcapR/xGmM9pBaItYvGPDfEYGouTYTX8Wtv1793LyuP/98carpu6M3WKC7PIXDE8ZPkCx3uN+VEjepTTgzwkTnY8Cuo+EYvYWf4WJIrlJZEgab6FUvsKT8T+1XEzbmwUbX9RNCq+tE6birvpOY/wWzJlpMtV/twLBbZA8hfQ3kdNVvdBsFvkoiyTiQqaT+Du5TFL2vja27nfcshLNGvkJPJZkAvZcVG4XZGsp0TyDCqQS5vDqwOYHaYTQSg91jUNSEFCBU//mvAmsK1OxQFHbF0RhAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFtPdVUB65dAP6v2wu8idPkkqRaP5gmcOuOt2+8/slx7RvO/FFwzFvAqroqmpaKJ53daewZwIG4Wzu4lziqYnD3F3YoqxqUY8ID58SLm9a6XF6aYka7TxXJnZgmy7v1ZWcbbTinvUC7S1m23imT7779cWj5NkkXSM/R+RWB8ZAQCpy9pg7iElAMTlqAp31pCntNG3l1O13A6t5eN3Af474T0FjVaXIEG/PLcRmF/5kTwmkYy5Av1v2vmyLBYXKNUrWwjeTGEEX0+j9AkcF79D1GpdKZpvuC0wxOrOgHLiR9DpGucMJajx+RA8zbAAj5C1A5JfkKBZPh2jMQ06c2eAAM=",
+  "codeSecret" : "be7e5acb-ad90-4c01-8dfe-c78cc492b752",
   "roles" : {
     "realm" : [ {
       "id" : "a3e9f038-0c6d-4024-8a2a-ce3958c7afbb",
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 9b669ae..642087d 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -64,6 +64,7 @@
         
         <auth.server.remote>false</auth.server.remote>
         <auth.server.profile/>
+        <auth.server.feature/>
     
         <adapter.test.props/>
         <migration.import.properties/>
@@ -165,6 +166,7 @@
                             <auth.server.adapter.impl.class>${auth.server.adapter.impl.class}</auth.server.adapter.impl.class>
                             
                             <auth.server.profile>${auth.server.profile}</auth.server.profile>
+                            <auth.server.feature>${auth.server.feature}</auth.server.feature>
                             
                             <frontend.console.output>${frontend.console.output}</frontend.console.output>
                             <backends.console.output>${backend.console.output}</backends.console.output>
@@ -263,6 +265,41 @@
             </properties>
         </profile>
 
+        <!--
+            profile that enables/disables specified feature, for more details see 
+            https://keycloak.gitbooks.io/server-installation-and-configuration/content/topics/profiles.html 
+        -->
+        <profile>
+            <id>auth-server-enable-disable-feature</id>
+            <properties>
+                <auth.server.feature>-Dkeycloak.profile.feature.${feature.name}=${feature.value}</auth.server.feature>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-enforcer-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>enforce</goal>
+                                </goals>
+                                <configuration>
+                                    <rules>
+                                        <requireProperty>
+                                            <property>feature.name</property>
+                                        </requireProperty>
+                                        <requireProperty>
+                                            <property>feature.value</property>
+                                        </requireProperty>
+                                    </rules>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
         <profile>
             <id>auth-server-cluster</id>
             <properties>
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
index 2a86b93..ff7d80f 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js
@@ -890,7 +890,12 @@ module.controller('ResourceServerPolicyResourceDetailCtrl', function($scope, $ro
         },
 
         onUpdate : function() {
-            $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]);
+            if ($scope.policy.config.resources && $scope.policy.config.resources._id) {
+                $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]);
+            } else {
+                delete $scope.policy.config.resources
+            }
+
             var policies = [];
 
             for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) {
@@ -920,7 +925,11 @@ module.controller('ResourceServerPolicyResourceDetailCtrl', function($scope, $ro
         },
 
         onCreate : function() {
-            $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]);
+            if ($scope.policy.config.resources && $scope.policy.config.resources._id) {
+                $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]);
+            } else {
+                delete $scope.policy.config.resources
+            }
 
             var policies = [];
 
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 12d37aa..471c4e5 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -1717,7 +1717,7 @@ module.controller('RealmAdminEventsModalCtrl', function($scope, $filter, event) 
     $scope.event = event;
 });
 
-module.controller('RealmBruteForceCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications, TimeUnit) {
+module.controller('RealmBruteForceCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications, TimeUnit, $route) {
     console.log('RealmBruteForceCtrl');
 
     $scope.realm = realm;
@@ -1780,8 +1780,7 @@ module.controller('RealmBruteForceCtrl', function($scope, Realm, realm, $http, $
     };
 
     $scope.reset = function() {
-        $scope.realm = angular.copy(oldCopy);
-        $scope.changed = false;
+        $route.reload();
     };
 });
 
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_ca.properties b/themes/src/main/resources/theme/base/login/messages/messages_ca.properties
index da73218..0e8458b 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_ca.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_ca.properties
@@ -67,7 +67,7 @@ country=Pa\u00EDs
 emailVerified=Email verificat
 gssDelegationCredential=GSS Delegation Credential
 
-loginTotpStep1=Instal\u00B7la <a href=\"https://fedorahosted.org/freeotp/\" target=\"_blank\">FreeOTP</a> o Google Authenticator al teu tel\u00E8fon m\u00F2bil. Les dues aplicacions estan disponibles a <a href=\"https://play.google.com\">Google Play</a> i en l''App Store d''Apple.
+loginTotpStep1=Instal\u00B7la <a href=\"https://freeotp.github.io/\" target=\"_blank\">FreeOTP</a> o Google Authenticator al teu tel\u00E8fon m\u00F2bil. Les dues aplicacions estan disponibles a <a href=\"https://play.google.com\">Google Play</a> i en l''App Store d''Apple.
 loginTotpStep2=Obre l''aplicaci\u00F3 i escaneja el codi o introdueix la clau.
 loginTotpStep3=Introdueix el codi \u00FAnic que et mostra l''aplicaci\u00F3 d''autenticaci\u00F3 i fes clic a Envia per finalitzar la configuraci\u00F3
 loginTotpOneTime=Codi d''un sol \u00FAs
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_de.properties b/themes/src/main/resources/theme/base/login/messages/messages_de.properties
index 3a48f70..ee5f3b9 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_de.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_de.properties
@@ -65,7 +65,7 @@ country=Land
 emailVerified=E-Mail verifiziert
 gssDelegationCredential=GSS delegierte Berechtigung
 
-loginTotpStep1=Installieren Sie <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> oder <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> auf Ihrem Smartphone.
+loginTotpStep1=Installieren Sie <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> oder <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> auf Ihrem Smartphone.
 loginTotpStep2=\u00D6ffnen Sie die Applikation und scannen Sie den Barcode oder geben sie den Code ein.
 loginTotpStep3=Geben Sie den One-time Code welcher die Applikation generiert hat ein und klicken Sie auf Absenden.
 loginTotpOneTime=One-time Code
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
index f69733a..3fc5bc4 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -67,7 +67,7 @@ country=Country
 emailVerified=Email verified
 gssDelegationCredential=GSS Delegation Credential
 
-loginTotpStep1=Install <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> or Google Authenticator on your mobile. Both applications are available in <a href="https://play.google.com">Google Play</a> and Apple App Store.
+loginTotpStep1=Install <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> or Google Authenticator on your mobile. Both applications are available in <a href="https://play.google.com">Google Play</a> and Apple App Store.
 loginTotpStep2=Open the application and scan the barcode or enter the key
 loginTotpStep3=Enter the one-time code provided by the application and click Submit to finish the setup
 loginTotpOneTime=One-time code
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_es.properties b/themes/src/main/resources/theme/base/login/messages/messages_es.properties
index 550a6a7..a2a40d4 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_es.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_es.properties
@@ -67,7 +67,7 @@ country=Pa\u00EDs
 emailVerified=Email verificado
 gssDelegationCredential=GSS Delegation Credential
 
-loginTotpStep1=Instala <a href=\"https://fedorahosted.org/freeotp/\" target=\"_blank\">FreeOTP</a> o Google Authenticator en tu tel\u00E9fono m\u00F3vil. Ambas aplicaciones est\u00E1n disponibles en <a href=\"https://play.google.com\">Google Play</a> y en la App Store de Apple.
+loginTotpStep1=Instala <a href=\"https://freeotp.github.io/\" target=\"_blank\">FreeOTP</a> o Google Authenticator en tu tel\u00E9fono m\u00F3vil. Ambas aplicaciones est\u00E1n disponibles en <a href=\"https://play.google.com\">Google Play</a> y en la App Store de Apple.
 loginTotpStep2=Abre la aplicacvi\u00F3n y escanea el c\u00F3digo o introduce la clave.
 loginTotpStep3=Introduce el c\u00F3digo \u00FAnico que te muestra la aplicaci\u00F3n de autenticaci\u00F3n y haz clic en Enviar para finalizar la configuraci\u00F3n
 loginTotpOneTime=C\u00F3digo de un solo uso
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_fr.properties b/themes/src/main/resources/theme/base/login/messages/messages_fr.properties
index 8dd7926..861d82d 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_fr.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_fr.properties
@@ -67,7 +67,7 @@ country=Pays
 emailVerified=Courriel v\u00e9rifi\u00e9
 gssDelegationCredential=Accr\u00e9ditation de d\u00e9l\u00e9gation GSS
 
-loginTotpStep1=Installez <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur <a href="https://play.google.com">Google Play</a> et Apple App Store.
+loginTotpStep1=Installez <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur <a href="https://play.google.com">Google Play</a> et Apple App Store.
 loginTotpStep2=Ouvrez l''application et scanner le code barre ou entrez la cl\u00e9.
 loginTotpStep3=Entrez le code \u00e0 usage unique fourni par l''application et cliquez sur Sauvegarder pour terminer.
 loginTotpOneTime=Code \u00e0 usage unique
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_it.properties b/themes/src/main/resources/theme/base/login/messages/messages_it.properties
index 9875967..2795c0c 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_it.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_it.properties
@@ -63,7 +63,7 @@ country=Paese
 emailVerified=Email verificata
 gssDelegationCredential=credenziali gss delegation
 
-loginTotpStep1=Installa <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> or <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> sul tuo dispositivo mobile
+loginTotpStep1=Installa <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> or <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> sul tuo dispositivo mobile
 loginTotpStep2=Apri l''applicazione e scansione il barcode o scrivi la chiave
 loginTotpStep3=Scrivi il codice one-time fornito dall''applicazione e premi Invia per finire il setup
 loginTotpOneTime=Codice one-time
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_ja.properties b/themes/src/main/resources/theme/base/login/messages/messages_ja.properties
index e0d1fd8..7a293d4 100644
--- a/themes/src/main/resources/theme/base/login/messages/messages_ja.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_ja.properties
@@ -68,7 +68,7 @@ country=国
 emailVerified=確認済みEメール
 gssDelegationCredential=GSS 代行クレデンシャル
 
-loginTotpStep1=<a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> または Google Authenticator (Google認証システム) をご自身のデバイスにインストールしてください。これらのアプリケーションは <a href="https://play.google.com">Google Play</a> と Apple App Store で入手できます。
+loginTotpStep1=<a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> または Google Authenticator (Google認証システム) をご自身のデバイスにインストールしてください。これらのアプリケーションは <a href="https://play.google.com">Google Play</a> と Apple App Store で入手できます。
 loginTotpStep2=アプリケーションを開きバーコードをスキャンするかキーを入力してください。
 loginTotpStep3=アプリケーションで提供されたワンタイムコードを入力して送信をクリックし、セットアップを完了してください。
 loginTotpOneTime=ワンタイムコード
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_lt.properties b/themes/src/main/resources/theme/base/login/messages/messages_lt.properties
index bffd8ed..0edff1b 100644
--- a/themes/src/main/resources/theme/base/login/messages/messages_lt.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_lt.properties
@@ -67,7 +67,7 @@ country=\u0160alis
 emailVerified=El. pa\u0161to adresas patvirtintas
 gssDelegationCredential=GSS prisijungimo duomen\u0173 delegavimas
 
-loginTotpStep1=\u012Ediekite <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> arba Google Authenticator savo \u012Frenginyje. Program\u0117l\u0117s prieinamos <a href="https://play.google.com">Google Play</a> ir Apple App Store.
+loginTotpStep1=\u012Ediekite <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> arba Google Authenticator savo \u012Frenginyje. Program\u0117l\u0117s prieinamos <a href="https://play.google.com">Google Play</a> ir Apple App Store.
 loginTotpStep2=Atidarykite program\u0117l\u0119 ir nuskenuokite barkod\u0105 arba \u012Fveskite kod\u0105.
 loginTotpStep3=\u012Eveskite program\u0117l\u0117je sugeneruot\u0105 vien\u0105 kart\u0105 galiojant\u012F kod\u0105 ir paspauskite Saugoti nor\u0117dami prisijungti.
 loginTotpOneTime=Vienkartinis kodas
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_no.properties b/themes/src/main/resources/theme/base/login/messages/messages_no.properties
index a90af46..efa23a3 100644
--- a/themes/src/main/resources/theme/base/login/messages/messages_no.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_no.properties
@@ -67,7 +67,7 @@ country=Land
 emailVerified=E-postadresse bekreftet
 gssDelegationCredential=GSS legitimasjons-delegering
 
-loginTotpStep1=Installer <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> eller Google Authenticator p\u00E5 din mobiltelefon. Begge applikasjoner er tilgjengelige p\u00E5 <a href="https://play.google.com">Google Play</a> og Apple App Store.
+loginTotpStep1=Installer <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> eller Google Authenticator p\u00E5 din mobiltelefon. Begge applikasjoner er tilgjengelige p\u00E5 <a href="https://play.google.com">Google Play</a> og Apple App Store.
 loginTotpStep2=\u00C5pne applikasjonen og skann strekkoden eller skriv inn koden
 loginTotpStep3=Skriv inn engangskoden fra applikasjonen og klikk send inn for \u00E5 fullf\u00F8re
 loginTotpOneTime=Engangskode
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties b/themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
index de2e428..cddfe82 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
@@ -67,7 +67,7 @@ country=Pa\u00EDs
 emailVerified=E-mail verificado
 gssDelegationCredential=gss delega\u00E7\u00E3o credencial
 
-loginTotpStep1=Instale <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> em seu celular
+loginTotpStep1=Instale <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> ou <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> em seu celular
 loginTotpStep2=Abra o aplicativo e escaneie o c\u00F3digo de barras ou digite o c\u00F3digo
 loginTotpStep3=Digite o c\u00F3digo fornecido pelo aplicativo e clique em Enviar para concluir a configura\u00E7\u00E3o
 loginTotpOneTime=C\u00F3digo autenticador
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_ru.properties b/themes/src/main/resources/theme/base/login/messages/messages_ru.properties
index 752a3d4..be6ce7c 100644
--- a/themes/src/main/resources/theme/base/login/messages/messages_ru.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_ru.properties
@@ -67,7 +67,7 @@ country=\u0421\u0442\u0440\u0430\u043D\u0430
 emailVerified=Email \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D
 gssDelegationCredential=\u0414\u0435\u043B\u0435\u0433\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u0443\u0447\u0435\u0442\u043D\u044B\u0445 \u0434\u0430\u043D\u043D\u044B\u0445 GSS
 
-loginTotpStep1=\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> \u0438\u043B\u0438 Google Authenticator. \u041E\u0431\u0430 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u0432 <a href="https://play.google.com">Google Play</a> \u0438 Apple App Store.
+loginTotpStep1=\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> \u0438\u043B\u0438 Google Authenticator. \u041E\u0431\u0430 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u0432 <a href="https://play.google.com">Google Play</a> \u0438 Apple App Store.
 loginTotpStep2=\u041E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0438 \u043F\u0440\u043E\u0441\u043A\u0430\u043D\u0438\u0440\u0443\u0439\u0442\u0435 \u0431\u0430\u0440\u043A\u043E\u0434, \u043B\u0438\u0431\u043E \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u043A\u043B\u044E\u0447
 loginTotpStep3=\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043E\u0434\u043D\u043E\u0440\u0430\u0437\u043E\u0432\u044B\u0439 \u043F\u0430\u0440\u043E\u043B\u044C, \u0432\u044B\u0434\u0430\u043D\u043D\u044B\u0439 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435\u043C, \u0438 \u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0434\u043B\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438
 loginTotpOneTime=\u041E\u0434\u043D\u043E\u0440\u0430\u0437\u043E\u0432\u044B\u0439 \u043F\u0430\u0440\u043E\u043B\u044C