keycloak-aplcache

Changes

Details

diff --git a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/AbstractAuthOptionsCmd.java b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/AbstractAuthOptionsCmd.java
index 8ad0430..d088f0b 100644
--- a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/AbstractAuthOptionsCmd.java
+++ b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/AbstractAuthOptionsCmd.java
@@ -71,9 +71,9 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
     @Option(shortName = 't', name = "token", description = "Initial / Registration access token to use)", hasValue = true)
     protected String token;
 
-    protected void init(AbstractAuthOptionsCmd parent) {
+    protected void initFromParent(AbstractAuthOptionsCmd parent) {
 
-        super.init(parent);
+        super.initFromParent(parent);
 
         noconfig = parent.noconfig;
         config = parent.config;
@@ -159,7 +159,8 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
                 initConfigData(config);
                 ConfigUtil.setupInMemoryHandler(config);
 
-                ConfigCredentialsCmd login = new ConfigCredentialsCmd(this);
+                ConfigCredentialsCmd login = new ConfigCredentialsCmd();
+                login.initFromParent(this);
                 login.init(config);
                 login.process(commandInvocation);
 
diff --git a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/AbstractGlobalOptionsCmd.java b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/AbstractGlobalOptionsCmd.java
index 0183eee..d1b0db2 100644
--- a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/AbstractGlobalOptionsCmd.java
+++ b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/AbstractGlobalOptionsCmd.java
@@ -17,7 +17,7 @@ public abstract class AbstractGlobalOptionsCmd implements Command {
     @Option(name = "help", description = "Print command specific help", hasValue = false)
     protected boolean help;
 
-    protected void init(AbstractGlobalOptionsCmd parent) {
+    protected void initFromParent(AbstractGlobalOptionsCmd parent) {
         dumpTrace = parent.dumpTrace;
         help = parent.help;
     }
diff --git a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigCmd.java b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigCmd.java
index b4be107..4fb5e34 100644
--- a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigCmd.java
+++ b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigCmd.java
@@ -53,16 +53,24 @@ public class ConfigCmd extends AbstractAuthOptionsCmd implements Command {
             String cmd = args.get(0);
             switch (cmd) {
                 case "credentials": {
-                    return new ConfigCredentialsCmd(this).execute(commandInvocation);
+                    ConfigCredentialsCmd command = new ConfigCredentialsCmd();
+                    command.initFromParent(this);
+                    return command.execute(commandInvocation);
                 }
                 case "truststore": {
-                    return new ConfigTruststoreCmd(this).execute(commandInvocation);
+                    ConfigTruststoreCmd command = new ConfigTruststoreCmd();
+                    command.initFromParent(this);
+                    return command.execute(commandInvocation);
                 }
                 case "initial-token": {
-                    return new ConfigInitialTokenCmd(this).execute(commandInvocation);
+                    ConfigInitialTokenCmd command = new ConfigInitialTokenCmd();
+                    command.initFromParent(this);
+                    return command.execute(commandInvocation);
                 }
                 case "registration-token": {
-                    return new ConfigRegistrationTokenCmd(this).execute(commandInvocation);
+                    ConfigRegistrationTokenCmd command = new ConfigRegistrationTokenCmd();
+                    command.initFromParent(this);
+                    return command.execute(commandInvocation);
                 }
                 default: {
                     if (printHelp()) {
diff --git a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigCredentialsCmd.java b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigCredentialsCmd.java
index 4cc050f..20f7d88 100644
--- a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigCredentialsCmd.java
+++ b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigCredentialsCmd.java
@@ -36,11 +36,6 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Comm
 
     private int sigLifetime = 600;
 
-    public ConfigCredentialsCmd() {}
-
-    public ConfigCredentialsCmd(AbstractAuthOptionsCmd parent) {
-        init(parent);
-    }
 
     public void init(ConfigData configData) {
         if (server == null) {
diff --git a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigInitialTokenCmd.java b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigInitialTokenCmd.java
index 937787e..5610858 100644
--- a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigInitialTokenCmd.java
+++ b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigInitialTokenCmd.java
@@ -33,11 +33,10 @@ public class ConfigInitialTokenCmd extends AbstractAuthOptionsCmd implements Com
     private boolean delete;
     private boolean keepDomain;
 
-    public ConfigInitialTokenCmd() {}
 
-    public ConfigInitialTokenCmd(ConfigCmd parent) {
+    protected void initFromParent(ConfigCmd parent) {
         this.parent = parent;
-        init(parent);
+        super.initFromParent(parent);
     }
 
     @Override
diff --git a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigRegistrationTokenCmd.java b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigRegistrationTokenCmd.java
index 761affd..af6b984 100644
--- a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigRegistrationTokenCmd.java
+++ b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigRegistrationTokenCmd.java
@@ -30,11 +30,10 @@ public class ConfigRegistrationTokenCmd extends AbstractAuthOptionsCmd implement
 
     private boolean delete;
 
-    public ConfigRegistrationTokenCmd() {}
 
-    public ConfigRegistrationTokenCmd(ConfigCmd parent) {
+    protected void initFromParent(ConfigCmd parent) {
         this.parent = parent;
-        init(parent);
+        super.initFromParent(parent);
     }
 
     @Override
diff --git a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigTruststoreCmd.java b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigTruststoreCmd.java
index 49d8975..7ae668a 100644
--- a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigTruststoreCmd.java
+++ b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/commands/ConfigTruststoreCmd.java
@@ -30,11 +30,10 @@ public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd implements Comma
 
     private boolean delete;
 
-    public ConfigTruststoreCmd() {}
 
-    public ConfigTruststoreCmd(ConfigCmd parent) {
+    protected void initFromParent(ConfigCmd parent) {
         this.parent = parent;
-        init(parent);
+        super.initFromParent(parent);
     }
 
     @Override
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index b972cda..c2e654e 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -1746,6 +1746,10 @@ public class RepresentationToModel {
     }
 
     public static void updateComponent(KeycloakSession session, ComponentRepresentation rep, ComponentModel component, boolean internal) {
+        if (rep.getName() != null) {
+            component.setName(rep.getName());
+        }
+
         if (rep.getParentId() != null) {
             component.setParentId(rep.getParentId());
         }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java
index 409a3e1..d5a98f6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java
@@ -144,6 +144,24 @@ public class ComponentsTest extends AbstractAdminTest {
         assertEquals(1, returned.getConfig().size());
     }
 
+
+    @Test
+    public void testRename() {
+        ComponentRepresentation rep = createComponentRepresentation("mycomponent");
+        rep.getConfig().addFirst("required", "foo");
+
+        String id = createComponent(rep);
+        ComponentRepresentation returned = components.component(id).toRepresentation();
+        assertEquals("mycomponent", returned.getName());
+
+        rep.setName("myupdatedcomponent");
+
+        components.component(id).update(rep);
+
+        returned = components.component(id).toRepresentation();
+        assertEquals("myupdatedcomponent", returned.getName());
+    }
+
     @Test
     public void testSecretConfig() throws Exception {
         ComponentRepresentation rep = createComponentRepresentation("mycomponent");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractCliTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractCliTest.java
index ce6cd05..ceec8b8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractCliTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractCliTest.java
@@ -539,17 +539,30 @@ public abstract class AbstractCliTest extends AbstractKeycloakTest {
         Assert.assertEquals("exitCode == " + exitCode, exitCode, exe.exitCode());
         if (stdOutLineCount != -1) {
             try {
-                Assert.assertTrue("stdout output has " + stdOutLineCount + " lines", exe.stdoutLines().size() == stdOutLineCount);
+                assertLineCount("stdout output", exe.stdoutLines(), stdOutLineCount);
             } catch (Throwable e) {
                 throw new AssertionError("STDOUT: " + exe.stdoutString(), e);
             }
         }
         if (stdErrLineCount != -1) {
             try {
-                Assert.assertTrue("stderr output has " + stdErrLineCount + " lines", exe.stderrLines().size() == stdErrLineCount);
+                assertLineCount("stderr output", exe.stderrLines(), stdErrLineCount);
             } catch (Throwable e) {
                 throw new AssertionError("STDERR: " + exe.stderrString(), e);
             }
         }
     }
+
+    void assertLineCount(String label, List<String> lines, int count) {
+        if (lines.size() == count) {
+            return;
+        }
+        // there is some kind of race condition in 'kcreg' that results in intermittent extra empty line
+        if (lines.size() == count + 1) {
+            if ("".equals(lines.get(lines.size()-1))) {
+                return;
+            }
+        }
+        Assert.assertTrue(label + " has " + lines.size() + " lines (expected: " + count + ")", lines.size() == count);
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegCreateTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegCreateTest.java
index 6b6cb27..5db5d86 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegCreateTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegCreateTest.java
@@ -32,13 +32,13 @@ public class KcRegCreateTest extends AbstractCliTest {
             KcRegExec exe = execute("config credentials -x --config '" + configFile.getName() +
                     "' --server " + serverUrl + " --realm master --user admin --password admin");
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
+            assertExitCodeAndStreamSizes(exe, 0, 0, 1);
 
             // use initial token of another realm with server, and realm override
             String token = issueInitialAccessToken("test");
             exe = execute("create --config '" + configFile.getName() + "' --server " + serverUrl + " --realm test -s clientId=my_first_client -t " + token);
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
+            assertExitCodeAndStreamSizes(exe, 0, 0, 1);
         }
     }
 
@@ -57,7 +57,7 @@ public class KcRegCreateTest extends AbstractCliTest {
             KcRegExec exe = execute("config initial-token -x --config '" + configFile.getName() +
                     "' --server " + serverUrl + " --realm " + realm + " " + token);
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
+            assertExitCodeAndStreamSizes(exe, 0, 0, 0);
 
             // check that current server, realm, and initial token are saved in the file
             ConfigData config = handler.loadConfig();
@@ -87,8 +87,7 @@ public class KcRegCreateTest extends AbstractCliTest {
 
                 exe = execute("create --config '" + configFile.getName() + "' -o -f - < '" + tmpFile.getName() + "'");
 
-                Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
-                Assert.assertTrue("stderr is empty", exe.stderrLines().isEmpty());
+                assertExitCodeAndStdErrSize(exe, 0, 0);
 
                 ClientRepresentation client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class);
                 Assert.assertNotNull("id", client.getId());
@@ -114,8 +113,7 @@ public class KcRegCreateTest extends AbstractCliTest {
                         " -s 'name=My Client App II' -s protocol=keycloak-oidc -s 'webOrigins=[\"http://localhost:8980/myapp2\"]'" +
                         " -s baseUrl=http://localhost:8980/myapp2 -s rootUrl=http://localhost:8980/myapp2");
 
-                Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
-                Assert.assertTrue("stderr is empty", exe.stderrLines().isEmpty());
+                assertExitCodeAndStdErrSize(exe, 0, 0);
 
                 ClientRepresentation client2 = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class);
                 Assert.assertNotNull("id", client2.getId());
@@ -138,29 +136,22 @@ public class KcRegCreateTest extends AbstractCliTest {
                 // check that using an invalid attribute key is not ignored
                 exe = execute("create --config '" + configFile.getName() + "' -o -f '" + tmpFile.getName() + "' -s client_id=my_client3");
 
-                Assert.assertEquals("exitCode == 1", 1, exe.exitCode());
-                Assert.assertEquals("stderr has one line", 1, exe.stderrLines().size());
+                assertExitCodeAndStreamSizes(exe, 1, 0, 1);
                 Assert.assertEquals("Failed to set attribute 'client_id' on document type 'default'", exe.stderrLines().get(0));
             }
 
             // simple create, output an id
             exe = execute("create --config '" + configFile.getName() + "' -i -s clientId=my_client3");
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
-            Assert.assertEquals("stderr is empty", 0, exe.stderrLines().size());
-
-            Assert.assertEquals("stdout has 1 line", 1, exe.stdoutLines().size());
+            assertExitCodeAndStreamSizes(exe, 0, 1, 0);
             Assert.assertEquals("only clientId returned", "my_client3", exe.stdoutLines().get(0));
 
             // simple create, default output
             exe = execute("create --config '" + configFile.getName() + "' -s clientId=my_client4");
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
-            Assert.assertEquals("stderr has 1 line", 1, exe.stderrLines().size());
+            assertExitCodeAndStreamSizes(exe, 0, 0, 1);
             Assert.assertEquals("only clientId returned", "Registered new client with client_id 'my_client4'", exe.stderrLines().get(0));
 
-            Assert.assertEquals("stdout is empty", 0, exe.stdoutLines().size());
-
 
 
             // create using oidc endpoint - autodetect format
@@ -178,8 +169,7 @@ public class KcRegCreateTest extends AbstractCliTest {
                         " -s 'redirect_uris=[\"http://localhost:8980/myapp5/*\"]' -s client_uri=http://localhost:8980/myapp5" +
                         " -o -f - < '" + tmpFile.getName() + "'");
 
-                Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
-                Assert.assertTrue("stderr is empty", exe.stderrLines().isEmpty());
+                assertExitCodeAndStdErrSize(exe, 0, 0);
 
                 OIDCClientRepresentation client = JsonSerialization.readValue(exe.stdout(), OIDCClientRepresentation.class);
 
@@ -195,10 +185,8 @@ public class KcRegCreateTest extends AbstractCliTest {
                 // try use incompatible endpoint override
                 exe = execute("create --config '" + configFile.getName() + "' -e default -f '" + tmpFile.getName() + "'");
 
-                Assert.assertEquals("exitCode == 1", 1, exe.exitCode());
-                Assert.assertFalse("stderr not empty", exe.stderrLines().isEmpty());
+                assertExitCodeAndStreamSizes(exe, 1, 0, 1);
                 Assert.assertEquals("Error message", "Attribute 'redirect_uris' not supported on document type 'default'", exe.stderrLines().get(0));
-
             }
 
 
@@ -208,8 +196,7 @@ public class KcRegCreateTest extends AbstractCliTest {
 
             exe = execute("create --config '" + configFile.getName() + "' -o -f - < '" + samlSpMetaFile.getAbsolutePath() + "'");
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
-            Assert.assertTrue("stderr is empty", exe.stderrLines().isEmpty());
+            assertExitCodeAndStdErrSize(exe, 0, 0);
 
             ClientRepresentation client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class);
             Assert.assertNotNull("id", client.getId());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java
index ae80a94..f936d6f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java
@@ -33,7 +33,7 @@ public class KcRegTest extends AbstractCliTest {
          */
         KcRegExec exe = execute("");
 
-        Assert.assertEquals("exitCode", 1, exe.exitCode());
+        assertExitCodeAndStdErrSize(exe, 1, 0);
 
         List<String> lines = exe.stdoutLines();
         Assert.assertTrue("stdout output not empty", lines.size() > 0);
@@ -41,8 +41,6 @@ public class KcRegTest extends AbstractCliTest {
         Assert.assertEquals("stdout one but last line", "Use '" + KcRegExec.CMD + " help <command>' for more information about a given command.", lines.get(lines.size() - 2));
         Assert.assertEquals("stdout last line", "", lines.get(lines.size() - 1));
 
-        lines = exe.stderrLines();
-        Assert.assertTrue("stderr output empty", lines.size() == 0);
 
         /*
          * Test commands without arguments
@@ -79,7 +77,7 @@ public class KcRegTest extends AbstractCliTest {
         Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
 
         exe = execute("help");
-        Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         lines = exe.stdoutLines();
         Assert.assertTrue("stdout output not empty", lines.size() > 0);
         Assert.assertEquals("stdout first line", "Keycloak Client Registration CLI", lines.get(0));
@@ -93,69 +91,57 @@ public class KcRegTest extends AbstractCliTest {
          *  Test --help for all commands
          */
         KcRegExec exe = execute("--help");
-        Assert.assertEquals("exit code", 0, exe.exitCode());
-        Assert.assertEquals("no stderr", 0, exe.stderrLines().size());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         Assert.assertEquals("stdout first line", "Keycloak Client Registration CLI", exe.stdoutLines().get(0));
 
         exe = execute("create --help");
-        Assert.assertEquals("exit code", 0, exe.exitCode());
-        Assert.assertEquals("no stderr", 0, exe.stderrLines().size());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         Assert.assertEquals("stdout first line", "Usage: " + CMD + " create [ARGUMENTS]", exe.stdoutLines().get(0));
 
         exe = execute("get --help");
-        Assert.assertEquals("exit code", 0, exe.exitCode());
-        Assert.assertEquals("no stderr", 0, exe.stderrLines().size());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         Assert.assertEquals("stdout first line", "Usage: " + CMD + " get CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
 
         exe = execute("update --help");
-        Assert.assertEquals("exit code", 0, exe.exitCode());
-        Assert.assertEquals("no stderr", 0, exe.stderrLines().size());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         Assert.assertEquals("stdout first line", "Usage: " + CMD + " update CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
 
         exe = execute("delete --help");
-        Assert.assertEquals("exit code", 0, exe.exitCode());
-        Assert.assertEquals("no stderr", 0, exe.stderrLines().size());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         Assert.assertEquals("stdout first line", "Usage: " + CMD + " delete CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
 
         exe = execute("attrs --help");
-        Assert.assertEquals("exit code", 0, exe.exitCode());
-        Assert.assertEquals("no stderr", 0, exe.stderrLines().size());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         Assert.assertEquals("stdout first line", "Usage: " + CMD + " attrs [ATTRIBUTE] [ARGUMENTS]", exe.stdoutLines().get(0));
 
         exe = execute("update-token --help");
-        Assert.assertEquals("exit code", 0, exe.exitCode());
-        Assert.assertEquals("no stderr", 0, exe.stderrLines().size());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         Assert.assertEquals("stdout first line", "Usage: " + CMD + " update-token CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
 
         exe = execute("config --help");
-        Assert.assertEquals("exit code", 0, exe.exitCode());
-        Assert.assertEquals("no stderr", 0, exe.stderrLines().size());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         Assert.assertEquals("stdout first line", "Usage: " + OsUtil.CMD + " config SUB_COMMAND [ARGUMENTS]", exe.stdoutLines().get(0));
 
         exe = execute("config credentials --help");
-        Assert.assertEquals("exit code", 0, exe.exitCode());
-        Assert.assertEquals("no stderr", 0, exe.stderrLines().size());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         Assert.assertEquals("stdout first line",
                 "Usage: " + CMD + " config credentials --server SERVER_URL --realm REALM [ARGUMENTS]",
                 exe.stdoutLines().get(0));
 
         exe = execute("config initial-token --help");
-        Assert.assertEquals("exit code", 0, exe.exitCode());
-        Assert.assertEquals("no stderr", 0, exe.stderrLines().size());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         Assert.assertEquals("stdout first line",
                 "Usage: " + CMD + " config initial-token --server SERVER --realm REALM [--delete | TOKEN] [ARGUMENTS]",
                 exe.stdoutLines().get(0));
 
         exe = execute("config registration-token --help");
-        Assert.assertEquals("exit code", 0, exe.exitCode());
-        Assert.assertEquals("no stderr", 0, exe.stderrLines().size());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         Assert.assertEquals("stdout first line",
                 "Usage: " + CMD + " config registration-token --server SERVER --realm REALM --client CLIENT [--delete | TOKEN] [ARGUMENTS]",
                 exe.stdoutLines().get(0));
 
         exe = execute("config truststore --help");
-        Assert.assertEquals("exit code", 0, exe.exitCode());
-        Assert.assertEquals("no stderr", 0, exe.stderrLines().size());
+        assertExitCodeAndStdErrSize(exe, 0, 0);
         Assert.assertEquals("stdout first line",
                 "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWOD] [ARGUMENTS]",
                 exe.stdoutLines().get(0));
@@ -379,7 +365,7 @@ public class KcRegTest extends AbstractCliTest {
             KcRegExec exe = execute("config credentials --server " + serverUrl +
                     " --realm master --user admin --password admin --config '" + configFile.getName() + "'");
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
+            assertExitCodeAndStreamSizes(exe, 0, 0, 1);
 
             // remember the state of config file
             ConfigData config1 = handler.loadConfig();
@@ -389,7 +375,7 @@ public class KcRegTest extends AbstractCliTest {
 
             exe = execute("create --config '" + configFile.getName() + "' -s clientId=test-client -o");
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
+            assertExitCodeAndStdErrSize(exe, 0, 0);
 
             // check changes to config file
             ConfigData config2 = handler.loadConfig();
@@ -619,12 +605,17 @@ public class KcRegTest extends AbstractCliTest {
             KcRegExec exe = execute("create " + (useConfig ? ("--config '" + configFile.getAbsolutePath()) + "'" : "--no-config")
                     + " --server " + serverUrl + " --realm " + realm + " -s clientId=test-client -o");
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
+            assertExitCodeAndStdErrSize(exe, 0, 0);
 
             ClientRepresentation client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class);
 
             Assert.assertEquals("clientId", "test-client", client.getClientId());
             Assert.assertNotNull("registrationAccessToken", client.getRegistrationAccessToken());
+
+            exe = execute("delete test-client " + (useConfig ? ("--config '" + configFile.getAbsolutePath()) + "'" : "--no-config")
+                    + " --server " + serverUrl + " --realm " + realm + " -t " + client.getRegistrationAccessToken());
+
+            assertExitCodeAndStreamSizes(exe, 0, 0, 0);
         }
     }
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTest.java
index cebc5fc..fba1806 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTest.java
@@ -35,7 +35,8 @@ public class KcRegUpdateTest extends AbstractCliTest {
             // create an object so we can update it
             KcRegExec exe = execute("create --config '" + configFile.getName() + "' -o -s clientId=my_client");
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
+            assertExitCodeAndStdErrSize(exe, 0, 0);
+
             ClientRepresentation client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class);
 
             Assert.assertEquals("enabled", true, client.isEnabled());
@@ -48,8 +49,7 @@ public class KcRegUpdateTest extends AbstractCliTest {
             exe = execute("update my_client --config '" + configFile.getName() + "' -o " +
                         " -s enabled=false -s 'redirectUris=[\"http://localhost:8980/myapp/*\"]'");
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
-            Assert.assertTrue("stderr is empty", exe.stderrLines().isEmpty());
+            assertExitCodeAndStdErrSize(exe, 0, 0);
 
             client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class);
             Assert.assertEquals("enabled", false, client.isEnabled());
@@ -60,8 +60,7 @@ public class KcRegUpdateTest extends AbstractCliTest {
             // Another merge update - test deleting an attribute, deleting a list item and adding a list item
             exe = execute("update my_client --config '" + configFile.getName() + "' -o -d redirectUris -s webOrigins+=http://localhost:8980/myapp -s webOrigins+=http://localhost:8981/myapp -d webOrigins[0]");
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
-            Assert.assertTrue("stderr is empty", exe.stderrLines().isEmpty());
+            assertExitCodeAndStdErrSize(exe, 0, 0);
 
             client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class);
 
@@ -76,8 +75,7 @@ public class KcRegUpdateTest extends AbstractCliTest {
             exe = execute("update my_client --config '" + configFile.getName() + "' -o -s 'protocolMappers[0].config.\"id.token.claim\"=false' " +
                     "-s 'protocolMappers[4].config={\"single\": \"true\", \"attribute.nameformat\": \"Basic\", \"attribute.name\": \"Role\"}'");
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
-            Assert.assertTrue("stderr is empty", exe.stderrLines().isEmpty());
+            assertExitCodeAndStdErrSize(exe, 0, 0);
 
             client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class);
             Assert.assertEquals("protocolMapper[0].config.\"id.token.claim\"", "false", client.getProtocolMappers().get(0).getConfig().get("id.token.claim"));
@@ -112,8 +110,7 @@ public class KcRegUpdateTest extends AbstractCliTest {
                     .stdin(new ByteArrayInputStream("{ \"enabled\": false }".getBytes()))
                     .execute();
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
-            Assert.assertTrue("stderr has error", exe.stderrLines().isEmpty());
+            assertExitCodeAndStdErrSize(exe, 0, 0);
 
             client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class);
             // web origin is not sent to the server, thus it retains the current value
@@ -130,8 +127,7 @@ public class KcRegUpdateTest extends AbstractCliTest {
                     .stdin(new ByteArrayInputStream("{ \"webOrigins\": [\"http://localhost:8980/myapp\"] }".getBytes()))
                     .execute();
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
-            Assert.assertTrue("stderr has error", exe.stderrLines().isEmpty());
+            assertExitCodeAndStdErrSize(exe, 0, 0);
 
             client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class);
             Assert.assertEquals("webOrigins", Arrays.asList("http://localhost:8980/myapp"), client.getWebOrigins());
@@ -144,8 +140,7 @@ public class KcRegUpdateTest extends AbstractCliTest {
             exe = execute("config registration-token --config '" + configFile.getName() + "' --server " + serverUrl +
                     " --realm " + realm + " --client my_client -d");
 
-            Assert.assertEquals("exitCode == 0", 0, exe.exitCode());
-            Assert.assertTrue("stderr is empty", exe.stderrLines().isEmpty());
+            assertExitCodeAndStdErrSize(exe, 0, 0);
 
             Assert.assertNull("my_client registration token", handler.loadConfig().ensureRealmConfigData(serverUrl, realm).getClients().get("my_client"));
         }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTokenTest.java
index e2b4193..b2aebf7 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTokenTest.java
@@ -13,7 +13,6 @@ import org.keycloak.util.JsonSerialization;
 
 import java.io.IOException;
 
-import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
 import static org.keycloak.testsuite.cli.KcRegExec.execute;
 
 /**
@@ -59,7 +58,8 @@ public class KcRegUpdateTokenTest extends AbstractCliTest {
 
             // test that the token works
             exe = execute("get reg-cli-secret-direct --no-config --server " + serverUrl + " --realm test -t " + token);
-            Assert.assertEquals("exit code", 0, exe.exitCode());
+
+            assertExitCodeAndStdErrSize(exe, 0, 0);
 
             ClientRepresentation client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class);
             Assert.assertEquals("client representation returned", "reg-cli-secret-direct", client.getClientId());
diff --git a/themes/src/main/resources/theme/base/account/totp.ftl b/themes/src/main/resources/theme/base/account/totp.ftl
index 8ab36fa..4b96dd8 100755
--- a/themes/src/main/resources/theme/base/account/totp.ftl
+++ b/themes/src/main/resources/theme/base/account/totp.ftl
@@ -2,66 +2,66 @@
 <@layout.mainLayout active='totp' bodyClass='totp'; section>
 
     <#if totp.enabled>
-        <h2>${msg("authenticatorTitle")}</h2>
+<h2>${msg("authenticatorTitle")}</h2>
 
-        <table class="table table-bordered table-striped">
-            <thead
-                <tr>
-                   <th colspan="2">${msg("configureAuthenticators")}</th>
-                </tr>
-            </thead>
-            <tbody>
-            <tr>
-                <td class="provider">${msg("mobile")}</td>
-                <td class="action">
-                    <a id="remove-mobile" href="${url.totpRemoveUrl}"><i class="pficon pficon-delete"></i></a>
+<table class="table table-bordered table-striped">
+    <thead
+        <tr>
+            <th colspan="2">${msg("configureAuthenticators")}</th>
+            </tr>
+        </thead>
+    <tbody>
+        <tr>
+            <td class="provider">${msg("mobile")}</td>
+            <td class="action">
+                <a id="remove-mobile" href="${url.totpRemoveUrl}"><i class="pficon pficon-delete"></i></a>
                 </td>
             </tr>
-            </tbody>
-        </table>
+        </tbody>
+    </table>
     <#else>
-        <h2>${msg("authenticatorTitle")}</h2>
+<h2>${msg("authenticatorTitle")}</h2>
 
-        <hr/>
+<hr/>
 
-        <ol>
-            <li>
-                <p>${msg("totpStep1")}</p>
-            </li>
-            <li>
-                <p>${msg("totpStep2")}</p>
-                <img src="data:image/png;base64, ${totp.totpSecretQrCode}" alt="Figure: Barcode"><br/>
-                <span class="code">${totp.totpSecretEncoded}</span>
-            </li>
-            <li>
-                <p>${msg("totpStep3")}</p>
-            </li>
-        </ol>
+<ol>
+    <li>
+        <p>${msg("totpStep1")}</p>
+    </li>
+    <li>
+        <p>${msg("totpStep2")}</p>
+        <p><img src="data:image/png;base64, ${totp.totpSecretQrCode}" alt="Figure: Barcode"></p>
+        <p><span class="code">${totp.totpSecretEncoded}</span></p>
+    </li>
+    <li>
+        <p>${msg("totpStep3")}</p>
+    </li>
+</ol>
 
-        <hr/>
+<hr/>
 
-        <form action="${url.totpUrl}" class="form-horizontal" method="post">
-            <input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker?html}">
-            <div class="form-group">
-                <div class="col-sm-2 col-md-2">
-                    <label for="totp" class="control-label">${msg("authenticatorCode")}</label>
-                </div>
+<form action="${url.totpUrl}" class="form-horizontal" method="post">
+    <input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker?html}">
+    <div class="form-group">
+        <div class="col-sm-2 col-md-2">
+            <label for="totp" class="control-label">${msg("authenticatorCode")}</label>
+            </div>
 
-                <div class="col-sm-10 col-md-10">
-                    <input type="text" class="form-control" id="totp" name="totp" autocomplete="off" autofocus autocomplete="off">
-                    <input type="hidden" id="totpSecret" name="totpSecret" value="${totp.totpSecret}" />
-                </div>
+        <div class="col-sm-10 col-md-10">
+            <input type="text" class="form-control" id="totp" name="totp" autocomplete="off" autofocus autocomplete="off">
+            <input type="hidden" id="totpSecret" name="totpSecret" value="${totp.totpSecret}" />
             </div>
+        </div>
 
-            <div class="form-group">
-                <div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
-                    <div class="">
-                        <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Save">${msg("doSave")}</button>
-                        <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Cancel">${msg("doCancel")}</button>
-                    </div>
+    <div class="form-group">
+        <div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
+            <div class="">
+                <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Save">${msg("doSave")}</button>
+                <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Cancel">${msg("doCancel")}</button>
                 </div>
             </div>
-        </form>
+        </div>
+    </form>
     </#if>
 
 </@layout.mainLayout>
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 6ea2154..ba0917b 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
@@ -1112,12 +1112,10 @@ module.controller('RealmKeysProvidersCtrl', function($scope, Realm, realm, $http
         parent: realm.id,
         type: 'org.keycloak.keys.KeyProvider'
     }, function(data) {
-        console.debug(data);
         $scope.instances = data;
     });
 
     $scope.addProvider = function(provider) {
-        console.log('Add provider: ' + provider.id);
         $location.url("/create/keys/" + realm.realm + "/providers/" + provider.id);
     };
 
@@ -1159,19 +1157,27 @@ module.controller('GenericKeystoreCtrl', function($scope, $location, Notificatio
                 'priority': ["0"]
             }
         }
+    } else {
+        $scope.instance = angular.copy(instance);
+    }
 
-        if (providerFactory.properties) {
-            for (var i = 0; i < providerFactory.properties.length; i++) {
-                var configProperty = providerFactory.properties[i];
+    if (providerFactory.properties) {
+        for (var i = 0; i < providerFactory.properties.length; i++) {
+            var configProperty = providerFactory.properties[i];
+            if (!$scope.instance.config[configProperty.name]) {
                 if (configProperty.defaultValue) {
                     $scope.instance.config[configProperty.name] = [configProperty.defaultValue];
+                    if (!$scope.create) {
+                        instance.config[configProperty.name] = [configProperty.defaultValue];
+                    }
                 } else {
                     $scope.instance.config[configProperty.name] = [''];
+                    if (!$scope.create) {
+                        instance.config[configProperty.name] = [configProperty.defaultValue];
+                    }
                 }
             }
         }
-    } else {
-        $scope.instance = angular.copy(instance);
     }
 
     $scope.$watch('instance', function() {
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-generic.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-generic.html
index 0f9857c..2b66e26 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-generic.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys-generic.html
@@ -41,19 +41,18 @@
             <div class="form-group clearfix">
                 <label class="col-md-2 control-label" for="consoleDisplayName">{{:: 'console-display-name' | translate}} </label>
                 <div class="col-md-6">
-                    <input class="form-control" id="consoleDisplayName" type="text" ng-model="instance.name" placeholder="{{:: 'defaults-to-id' | translate}}">
+                    <input required class="form-control" id="consoleDisplayName" type="text" ng-model="instance.name" placeholder="{{:: 'defaults-to-id' | translate}}">
                 </div>
                 <kc-tooltip>{{:: 'console-display-name.tooltip' | translate}}</kc-tooltip>
             </div>
 
             <kc-component-config realm="realm" config="instance.config" properties="providerFactory.properties"></kc-component-config>
-
         </fieldset>
 
         <div class="form-group">
             <div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageRealm">
-                <button kc-save>{{:: 'save' | translate}}</button>
-                <button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
+                <button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
+                <button kc-cancel data-ng-disabled="!changed" data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
             </div>
         </div>
 
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html
index 2b4a94f..92ec018 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html
@@ -16,7 +16,7 @@
             <div class="row" data-ng-show="access.manageUsers">
                 <div class="col-sm-4 col-sm-offset-4">
                     <div class="form-group">
-                        <select class="selectpicker form-control" ng-model="selectedProvider"
+                        <select class="form-control" ng-model="selectedProvider"
                                 ng-options="p.id for p in providers"
                                 data-ng-change="addProvider(selectedProvider); selectedProvider = null">
                             <option value="" disabled selected>{{:: 'add-provider.placeholder' | translate}}</option>
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html
index b3b536f..63702f9 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html
@@ -2,26 +2,26 @@
     <div data-ng-repeat="option in properties" class="form-group" data-ng-controller="ProviderConfigCtrl">
         <label class="col-md-2 control-label">{{:: option.label | translate}}</label>
 
-        <div class="col-md-6" data-ng-show="option.type == 'String'">
+        <div class="col-md-6" data-ng-if="option.type == 'String'">
             <input class="form-control" type="text" data-ng-model="config[ option.name ][0]" >
         </div>
-        <div class="col-md-6" data-ng-show="option.type == 'Password'">
+        <div class="col-md-6" data-ng-if="option.type == 'Password'">
             <input class="form-control" type="password" data-ng-model="config[ option.name ][0]" >
         </div>
-        <div class="col-md-6" data-ng-show="option.type == 'boolean'">
+        <div class="col-md-6" data-ng-if="option.type == 'boolean'">
             <input ng-model="config[ option.name ][0]" value="'true'" id="option.name" name="option.name" onoffswitchstring on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
         </div>
-        <div class="col-md-6" data-ng-show="option.type == 'List'">
+        <div class="col-md-6" data-ng-if="option.type == 'List'">
             <select ng-model="config[ option.name ][0]" ng-options="data for data in option.options">
                 <option value="" selected> {{:: 'selectOne' | translate}} </option>
             </select>
         </div>
-        <div class="col-md-6" data-ng-show="option.type == 'MultivaluedList'">
+        <div class="col-md-6" data-ng-if="option.type == 'MultivaluedList'">
             <select ui-select2 data-ng-model="config[ option.name ]" data-placeholder="{{:: 'selectMultiple' | translate}}..." multiple>
                 <option ng-repeat="val in option.options" value="{{val}}" ng-selected="true">{{val}}</option>
             </select>
         </div>
-        <div class="col-md-6" data-ng-show="option.type == 'Role'">
+        <div class="col-md-6" data-ng-if="option.type == 'Role'">
             <div class="row">
                 <div class="col-md-8">
                     <input class="form-control" type="text" data-ng-model="config[ option.name ][0]" >
@@ -31,7 +31,7 @@
                 </div>
             </div>
         </div>
-        <div class="col-md-4" data-ng-show="option.type == 'ClientList'">
+        <div class="col-md-4" data-ng-if="option.type == 'ClientList'">
             <select ng-model="config[ option.name ][0]" ng-options="client.clientId as client.clientId for client in clients">
                 <option value="" selected> {{:: 'selectOne' | translate}} </option>
             </select>
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
index 96fb247..daa0b34 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
@@ -22,8 +22,10 @@
             <li data-ng-show="access.viewRealm" data-ng-class="((!path[2]
     || path[2] == 'required-credentials'
     || path[2] == 'login-settings'
+    || path[2] == 'keys'
     || path[2] == 'theme-settings'
     || path[2] == 'token-settings'
+    || path[2] == 'client-registration'
     || path[2] == 'cache-settings'
     || path[2] == 'client-initial-access'
     || path[2] == 'defense'
@@ -33,8 +35,13 @@
             <li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'clients' || path[1] == 'client' || path[3] == 'clients') && 'active'"><a href="#/realms/{{realm.realm}}/clients"><i class="fa fa-cube"></i> {{:: 'clients' | translate}}</a></li>
             <li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'client-templates' || path[1] == 'client-template' || path[3] == 'client-templates') && 'active'"><a href="#/realms/{{realm.realm}}/client-templates"><i class="fa fa-cubes"></i> {{:: 'client-templates' | translate}}</a></li>
             <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'roles' || path[2] == 'default-roles' || (path[1] == 'role' && path[3] != 'clients')) && 'active'"><a href="#/realms/{{realm.realm}}/roles"><i class="fa fa-tasks"></i> {{:: 'roles' | translate}}</a></li>
-            <li data-ng-show="access.viewIdentityProviders" data-ng-class="(path[2] == 'identity-provider-settings' || path[2] == 'identity-provider-mappers') && 'active'"><a href="#/realms/{{realm.realm}}/identity-provider-settings"><i class="fa fa-exchange"></i> {{:: 'identity-providers' | translate}}</a></li>
-            <li data-ng-show="access.viewUsers" data-ng-class="(path[1] == 'user-federation' || path[2] == 'user-federation') && 'active'"><a href="#/realms/{{realm.realm}}/user-federation"><i class="fa fa-database"></i> {{:: 'user-federation' | translate}}</a></li>
+            <li data-ng-show="access.viewIdentityProviders" data-ng-class="(path[2] == 'identity-provider-settings' 
+                        || path[2] == 'identity-provider-mappers'
+                        || path[1] == 'identity-provider-mappers'
+                        || path[1] == 'identity-provider') && 'active'"><a href="#/realms/{{realm.realm}}/identity-provider-settings"><i class="fa fa-exchange"></i> {{:: 'identity-providers' | translate}}</a></li>
+            <li data-ng-show="access.viewUsers" data-ng-class="(path[1] == 'user-federation' 
+                        || path[2] == 'user-federation'
+                        || path[1] == 'user-federation-mappers') && 'active'"><a href="#/realms/{{realm.realm}}/user-federation"><i class="fa fa-database"></i> {{:: 'user-federation' | translate}}</a></li>
             <li data-ng-show="access.viewRealm" data-ng-class="(path[1] == 'authentication' || path[2] == 'authentication') && 'active'"><a href="#/realms/{{realm.realm}}/authentication/flows"><i class="fa fa-lock"></i> {{:: 'authentication' | translate}}</a></li>
         </ul>
     </div>
@@ -42,10 +49,16 @@
     <div class="nav-category" data-ng-show="current.realm">
         <h2>{{:: 'manage' | translate}}</h2>
         <ul class="nav nav-pills nav-stacked">
-            <li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'groups' || path[1] == 'group') && 'active'"><a href="#/realms/{{realm.realm}}/groups"><span class="pficon pficon-users"></span> {{:: 'groups' | translate}}</a></li>
-            <li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users"><span class="pficon pficon-user"></span> {{:: 'users' | translate}}</a></li>
+            <li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'groups' 
+                        || path[1] == 'group' 
+                        || path[2] == 'default-groups') && 'active'"><a href="#/realms/{{realm.realm}}/groups"><span class="pficon pficon-users"></span> {{:: 'groups' | translate}}</a></li>
+            <li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users' 
+                        || path[1] == 'user'
+                        || path[1] == 'federated-identity') && 'active'"><a href="#/realms/{{realm.realm}}/users"><span class="pficon pficon-user"></span> {{:: 'users' | translate}}</a></li>
             <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'sessions') && 'active'"><a href="#/realms/{{realm.realm}}/sessions/realm"><i class="fa fa-clock-o"></i> {{:: 'sessions' | translate}}</a></li>
-            <li data-ng-show="access.viewEvents" data-ng-class="(path[2] == 'events' || path[2] == 'events-settings') && 'active'"><a href="#/realms/{{realm.realm}}/events"><i class="fa fa-calendar"></i> {{:: 'events' | translate}}</a></li>
+            <li data-ng-show="access.viewEvents" data-ng-class="(path[2] == 'events' 
+                        || path[2] == 'events-settings'
+                        || path[2] == 'admin-events') && 'active'"><a href="#/realms/{{realm.realm}}/events"><i class="fa fa-calendar"></i> {{:: 'events' | translate}}</a></li>
             <li data-ng-show="access.manageRealm" ng-class="(path[2] =='partial-import') && 'active'"><a href="#/realms/{{realm.realm}}/partial-import"><span class="pficon pficon-import"></span> {{:: 'import' | translate}}</a></li>
         </ul>
     </div>
diff --git a/themes/src/main/resources/theme/base/login/login-config-totp.ftl b/themes/src/main/resources/theme/base/login/login-config-totp.ftl
index 07a8801..914ecb5 100755
--- a/themes/src/main/resources/theme/base/login/login-config-totp.ftl
+++ b/themes/src/main/resources/theme/base/login/login-config-totp.ftl
@@ -5,41 +5,28 @@
     <#elseif section = "header">
         ${msg("loginTotpTitle")}
     <#elseif section = "form">
-        <form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-totp-settings-form" method="post">
-            <div class="${properties.kcFormGroupClass!}">
-                <div class="${properties.kcLabelWrapperClass!}">
-                    <label for="otp" class="${properties.kcLabelClass!}">${msg("loginTotpOneTime")}</label>
-                </div>
-                <div class="${properties.kcInputWrapperClass!}">
-                    <input type="text" id="totp" name="totp" autocomplete="off" class="${properties.kcInputClass!}" />
-                </div>
-                <input type="hidden" id="totpSecret" name="totpSecret" value="${totp.totpSecret}" />
+<ol id="kc-totp-settings">
+    <li>
+        <p>${msg("loginTotpStep1")}</p>
+        </li>
+    <li>
+        <p>${msg("loginTotpStep2")}</p>
+        <img src="data:image/png;base64, ${totp.totpSecretQrCode}" alt="Figure: Barcode"><br/>
+        <span class="code">${totp.totpSecretEncoded}</span>
+        </li>
+    <li>
+        <p>${msg("loginTotpStep3")}</p>
+        </li>
+    </ol>
+    <form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-totp-settings-form" method="post">
+        <div class="${properties.kcFormGroupClass!}">
+            <div class="${properties.kcInputWrapperClass!}">
+                <input type="text" id="totp" name="totp" autocomplete="off" class="${properties.kcInputClass!}" />
             </div>
+            <input type="hidden" id="totpSecret" name="totpSecret" value="${totp.totpSecret}" />
+        </div>
 
-            <div class="${properties.kcFormGroupClass!}">
-                <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
-                    <div class="${properties.kcFormOptionsWrapperClass!}">
-                    </div>
-                </div>
-
-                <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
-                    <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}"/>
-                </div>
-            </div>
-        </form>
-    <#elseif section = "info" >
-        <ol id="kc-totp-settings">
-            <li>
-                <p>${msg("loginTotpStep1")}</p>
-            </li>
-            <li>
-                <p>${msg("loginTotpStep2")}</p>
-                <img src="data:image/png;base64, ${totp.totpSecretQrCode}" alt="Figure: Barcode"><br/>
-                <span class="code">${totp.totpSecretEncoded}</span>
-            </li>
-            <li>
-                <p>${msg("loginTotpStep3")}</p>
-            </li>
-        </ol>
+        <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}"/>
+    </form>
     </#if>
 </@layout.registrationLayout>
diff --git a/themes/src/main/resources/theme/keycloak/account/resources/css/account.css b/themes/src/main/resources/theme/keycloak/account/resources/css/account.css
index be0c31e..22edb49 100644
--- a/themes/src/main/resources/theme/keycloak/account/resources/css/account.css
+++ b/themes/src/main/resources/theme/keycloak/account/resources/css/account.css
@@ -208,18 +208,6 @@ ol li img {
     border: 1px solid #eee;
 }
 
-ol li span {
-    padding: 15px;
-    background-color: #f5f5f5;
-    border: 1px solid #eee;
-    top: 46px;
-    left: 270px;
-    right: 50px;
-    position: absolute;
-    font-family: courier, ​monospace;
-    font-size: 25px;
-}
-
 hr + .form-horizontal {
     border: none;
     padding-top: 0;
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/lib/filesaver/FileSaver.js b/themes/src/main/resources/theme/keycloak/common/resources/lib/filesaver/FileSaver.js
index 6095017..fb71494 100644
--- a/themes/src/main/resources/theme/keycloak/common/resources/lib/filesaver/FileSaver.js
+++ b/themes/src/main/resources/theme/keycloak/common/resources/lib/filesaver/FileSaver.js
@@ -1,10 +1,11 @@
 /* FileSaver.js
- *  A saveAs() FileSaver implementation.
- *  2014-05-27
+ * A saveAs() FileSaver implementation.
+ * 1.3.2
+ * 2016-06-16 18:25:19
  *
- *  By Eli Grey, http://eligrey.com
- *  License: X11/MIT
- *    See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
+ * By Eli Grey, http://eligrey.com
+ * License: MIT
+ *   See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
  */
 
 /*global self */
@@ -12,16 +13,10 @@
 
 /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
 
-var saveAs = saveAs
-  // IE 10+ (native saveAs)
-  || (typeof navigator !== "undefined" &&
-      navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
-  // Everyone else
-  || (function(view) {
+var saveAs = saveAs || (function(view) {
 	"use strict";
 	// IE <10 is explicitly unsupported
-	if (typeof navigator !== "undefined" &&
-	    /MSIE [1-9]\./.test(navigator.userAgent)) {
+	if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
 		return;
 	}
 	var
@@ -31,36 +26,30 @@ var saveAs = saveAs
 			return view.URL || view.webkitURL || view;
 		}
 		, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
-		, can_use_save_link = !view.externalHost && "download" in save_link
+		, can_use_save_link = "download" in save_link
 		, click = function(node) {
-			var event = doc.createEvent("MouseEvents");
-			event.initMouseEvent(
-				"click", true, false, view, 0, 0, 0, 0, 0
-				, false, false, false, false, 0, null
-			);
+			var event = new MouseEvent("click");
 			node.dispatchEvent(event);
 		}
-		, webkit_req_fs = view.webkitRequestFileSystem
-		, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
+		, is_safari = /constructor/i.test(view.HTMLElement) || view.safari
+		, is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent)
 		, throw_outside = function(ex) {
 			(view.setImmediate || view.setTimeout)(function() {
 				throw ex;
 			}, 0);
 		}
 		, force_saveable_type = "application/octet-stream"
-		, fs_min_size = 0
-		, deletion_queue = []
-		, process_deletion_queue = function() {
-			var i = deletion_queue.length;
-			while (i--) {
-				var file = deletion_queue[i];
+		// the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
+		, arbitrary_revoke_timeout = 1000 * 40 // in ms
+		, revoke = function(file) {
+			var revoker = function() {
 				if (typeof file === "string") { // file is an object URL
 					get_URL().revokeObjectURL(file);
 				} else { // file is a File
 					file.remove();
 				}
-			}
-			deletion_queue.length = 0; // clear queue
+			};
+			setTimeout(revoker, arbitrary_revoke_timeout);
 		}
 		, dispatch = function(filesaver, event_types, event) {
 			event_types = [].concat(event_types);
@@ -76,134 +65,97 @@ var saveAs = saveAs
 				}
 			}
 		}
-		, FileSaver = function(blob, name) {
+		, auto_bom = function(blob) {
+			// prepend BOM for UTF-8 XML and text/* types (including HTML)
+			// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
+			if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
+				return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type});
+			}
+			return blob;
+		}
+		, FileSaver = function(blob, name, no_auto_bom) {
+			if (!no_auto_bom) {
+				blob = auto_bom(blob);
+			}
 			// First try a.download, then web filesystem, then object URLs
 			var
 				  filesaver = this
 				, type = blob.type
-				, blob_changed = false
+				, force = type === force_saveable_type
 				, object_url
-				, target_view
-				, get_object_url = function() {
-					var object_url = get_URL().createObjectURL(blob);
-					deletion_queue.push(object_url);
-					return object_url;
-				}
 				, dispatch_all = function() {
 					dispatch(filesaver, "writestart progress write writeend".split(" "));
 				}
 				// on any filesys errors revert to saving with object URLs
 				, fs_error = function() {
+					if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
+						// Safari doesn't allow downloading of blob urls
+						var reader = new FileReader();
+						reader.onloadend = function() {
+							var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
+							var popup = view.open(url, '_blank');
+							if(!popup) view.location.href = url;
+							url=undefined; // release reference before dispatching
+							filesaver.readyState = filesaver.DONE;
+							dispatch_all();
+						};
+						reader.readAsDataURL(blob);
+						filesaver.readyState = filesaver.INIT;
+						return;
+					}
 					// don't create more object URLs than needed
-					if (blob_changed || !object_url) {
-						object_url = get_object_url(blob);
+					if (!object_url) {
+						object_url = get_URL().createObjectURL(blob);
 					}
-					if (target_view) {
-						target_view.location.href = object_url;
+					if (force) {
+						view.location.href = object_url;
 					} else {
-						window.open(object_url, "_blank");
+						var opened = view.open(object_url, "_blank");
+						if (!opened) {
+							// Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
+							view.location.href = object_url;
+						}
 					}
 					filesaver.readyState = filesaver.DONE;
 					dispatch_all();
+					revoke(object_url);
 				}
-				, abortable = function(func) {
-					return function() {
-						if (filesaver.readyState !== filesaver.DONE) {
-							return func.apply(this, arguments);
-						}
-					};
-				}
-				, create_if_not_found = {create: true, exclusive: false}
-				, slice
 			;
 			filesaver.readyState = filesaver.INIT;
-			if (!name) {
-				name = "download";
-			}
+
 			if (can_use_save_link) {
-				object_url = get_object_url(blob);
-				save_link.href = object_url;
-				save_link.download = name;
-				click(save_link);
-				filesaver.readyState = filesaver.DONE;
-				dispatch_all();
-				return;
-			}
-			// Object and web filesystem URLs have a problem saving in Google Chrome when
-			// viewed in a tab, so I force save with application/octet-stream
-			// http://code.google.com/p/chromium/issues/detail?id=91158
-			if (view.chrome && type && type !== force_saveable_type) {
-				slice = blob.slice || blob.webkitSlice;
-				blob = slice.call(blob, 0, blob.size, force_saveable_type);
-				blob_changed = true;
-			}
-			// Since I can't be sure that the guessed media type will trigger a download
-			// in WebKit, I append .download to the filename.
-			// https://bugs.webkit.org/show_bug.cgi?id=65440
-			if (webkit_req_fs && name !== "download") {
-				name += ".download";
-			}
-			if (type === force_saveable_type || webkit_req_fs) {
-				target_view = view;
-			}
-			if (!req_fs) {
-				fs_error();
+				object_url = get_URL().createObjectURL(blob);
+				setTimeout(function() {
+					save_link.href = object_url;
+					save_link.download = name;
+					click(save_link);
+					dispatch_all();
+					revoke(object_url);
+					filesaver.readyState = filesaver.DONE;
+				});
 				return;
 			}
-			fs_min_size += blob.size;
-			req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
-				fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
-					var save = function() {
-						dir.getFile(name, create_if_not_found, abortable(function(file) {
-							file.createWriter(abortable(function(writer) {
-								writer.onwriteend = function(event) {
-									target_view.location.href = file.toURL();
-									deletion_queue.push(file);
-									filesaver.readyState = filesaver.DONE;
-									dispatch(filesaver, "writeend", event);
-								};
-								writer.onerror = function() {
-									var error = writer.error;
-									if (error.code !== error.ABORT_ERR) {
-										fs_error();
-									}
-								};
-								"writestart progress write abort".split(" ").forEach(function(event) {
-									writer["on" + event] = filesaver["on" + event];
-								});
-								writer.write(blob);
-								filesaver.abort = function() {
-									writer.abort();
-									filesaver.readyState = filesaver.DONE;
-								};
-								filesaver.readyState = filesaver.WRITING;
-							}), fs_error);
-						}), fs_error);
-					};
-					dir.getFile(name, {create: false}, abortable(function(file) {
-						// delete file if it already exists
-						file.remove();
-						save();
-					}), abortable(function(ex) {
-						if (ex.code === ex.NOT_FOUND_ERR) {
-							save();
-						} else {
-							fs_error();
-						}
-					}));
-				}), fs_error);
-			}), fs_error);
+
+			fs_error();
 		}
 		, FS_proto = FileSaver.prototype
-		, saveAs = function(blob, name) {
-			return new FileSaver(blob, name);
+		, saveAs = function(blob, name, no_auto_bom) {
+			return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
 		}
 	;
-	FS_proto.abort = function() {
-		var filesaver = this;
-		filesaver.readyState = filesaver.DONE;
-		dispatch(filesaver, "abort");
-	};
+	// IE 10+ (native saveAs)
+	if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
+		return function(blob, name, no_auto_bom) {
+			name = name || blob.name || "download";
+
+			if (!no_auto_bom) {
+				blob = auto_bom(blob);
+			}
+			return navigator.msSaveOrOpenBlob(blob, name);
+		};
+	}
+
+	FS_proto.abort = function(){};
 	FS_proto.readyState = FS_proto.INIT = 0;
 	FS_proto.WRITING = 1;
 	FS_proto.DONE = 2;
@@ -217,11 +169,6 @@ var saveAs = saveAs
 	FS_proto.onwriteend =
 		null;
 
-	view.addEventListener("unload", process_deletion_queue, false);
-	saveAs.unload = function() {
-		process_deletion_queue();
-		view.removeEventListener("unload", process_deletion_queue, false);
-	};
 	return saveAs;
 }(
 	   typeof self !== "undefined" && self
@@ -232,10 +179,10 @@ var saveAs = saveAs
 // while `this` is nsIContentFrameMessageManager
 // with an attribute `content` that corresponds to the window
 
-if (typeof module !== "undefined" && module !== null) {
-  module.exports = saveAs;
-} else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
-  define([], function() {
+if (typeof module !== "undefined" && module.exports) {
+  module.exports.saveAs = saveAs;
+} else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) {
+  define("FileSaver.js", function() {
     return saveAs;
   });
 }