keycloak-uncached

Details

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 6be4b2c..e0069b0 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
@@ -1855,7 +1855,7 @@ public class RepresentationToModel {
     public static AuthenticatorConfigModel toModel(AuthenticatorConfigRepresentation rep) {
         AuthenticatorConfigModel model = new AuthenticatorConfigModel();
         model.setAlias(rep.getAlias());
-        model.setConfig(rep.getConfig());
+        model.setConfig(removeEmptyString(rep.getConfig()));
         return model;
     }
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java
index d7caba4..323ce21 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java
@@ -26,6 +26,8 @@ import org.keycloak.events.admin.ResourceType;
 import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
 import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
 import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
+import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.util.AdminEventPaths;
 import org.keycloak.testsuite.util.AssertAdminEvents;
 
@@ -35,12 +37,65 @@ import javax.ws.rs.core.Response;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import static org.hamcrest.Matchers.hasItems;
 
 /**
  * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
  */
 public class ExecutionTest extends AbstractAuthenticationTest {
 
+    // KEYCLOAK-7975
+    @Test
+    public void testUpdateAuthenticatorConfig() {
+        // copy built-in flow so we get a new editable flow
+        HashMap<String, String> params = new HashMap<>();
+        params.put("newName", "new-browser-flow");
+        Response response = authMgmtResource.copy("browser", params);
+        assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW);
+        try {
+            Assert.assertEquals("Copy flow", 201, response.getStatus());
+        } finally {
+            response.close();
+        }
+
+        // create Conditional OTP Form execution
+        params.put("provider", "auth-conditional-otp-form");
+        authMgmtResource.addExecution("new-browser-flow", params);
+        assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("new-browser-flow"), params, ResourceType.AUTH_EXECUTION);
+
+        List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions("new-browser-flow");
+        AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider("auth-conditional-otp-form", executionReps);
+
+        // create authenticator config for the execution
+        Map<String, String> config = new HashMap<>();
+        config.put("defaultOtpOutcome", "skip");
+        config.put("otpControlAttribute", "test");
+        config.put("forceOtpForHeaderPattern", "");
+        config.put("forceOtpRole", "");
+        config.put("noOtpRequiredForHeaderPattern", "");
+        config.put("skipOtpRole", "");
+
+        AuthenticatorConfigRepresentation authConfigRep = new AuthenticatorConfigRepresentation();
+        authConfigRep.setAlias("conditional-otp-form-config-alias");
+        authConfigRep.setConfig(config);
+        response = authMgmtResource.newExecutionConfig(exec.getId(), authConfigRep);
+
+        try {
+            authConfigRep.setId(ApiUtil.getCreatedId(response));
+        } finally {
+            response.close();
+        }
+
+        // try to update the config adn check
+        config.put("otpControlAttribute", "test-updated");
+        authConfigRep.setConfig(config);
+        authMgmtResource.updateAuthenticatorConfig(authConfigRep.getId(), authConfigRep);
+
+        AuthenticatorConfigRepresentation updated = authMgmtResource.getAuthenticatorConfig(authConfigRep.getId());
+
+        Assert.assertThat(updated.getConfig().values(), hasItems("test-updated", "skip"));
+    }
+
     @Test
     public void testAddRemoveExecution() {