keycloak-uncached

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
index 406ded3..f1ea715 100755
--- a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
@@ -7,6 +7,7 @@ package org.keycloak.representations.idm;
 public class CredentialRepresentation {
     public static final String SECRET = "secret";
     public static final String PASSWORD = "password";
+    public static final String PASSWORD_TOKEN = "password-token";
     public static final String TOTP = "totp";
     public static final String CLIENT_CERT = "cert";
 
diff --git a/core/src/main/java/org/keycloak/representations/PasswordToken.java b/core/src/main/java/org/keycloak/representations/PasswordToken.java
new file mode 100644
index 0000000..8ae9ffc
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/PasswordToken.java
@@ -0,0 +1,47 @@
+package org.keycloak.representations;
+
+import org.keycloak.util.Time;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PasswordToken {
+
+    private String realm;
+    private String user;
+    private int timestamp;
+
+    public PasswordToken() {
+    }
+
+    public PasswordToken(String realm, String user) {
+        this.realm = realm;
+        this.user = user;
+        this.timestamp = Time.currentTime();
+    }
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public void setRealm(String realm) {
+        this.realm = realm;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
+}
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl
index 1127564..613e8f2 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl
@@ -7,7 +7,7 @@
     <#elseif section = "form">
         <form id="kc-totp-login-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
             <input id="username" name="username" value="${login.username!''}" type="hidden" />
-            <input id="password" name="password" value="${login.password!''}" type="hidden" />
+            <input id="password-token" name="password-token" value="${login.passwordToken!''}" type="hidden" />
 
             <div class="${properties.kcFormGroupClass!}">
                 <div class="${properties.kcLabelWrapperClass!}">
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/LoginBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/LoginBean.java
index d54f5b4..22dda67 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/LoginBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/LoginBean.java
@@ -32,10 +32,13 @@ public class LoginBean {
 
     private String password;
 
+    private String passwordToken;
+
     public LoginBean(MultivaluedMap<String, String> formData){
         if (formData != null) {
             username = formData.getFirst("username");
             password = formData.getFirst("password");
+            passwordToken = formData.getFirst("password-token");
         }
     }
 
@@ -47,4 +50,7 @@ public class LoginBean {
         return password;
     }
 
+    public String getPasswordToken() {
+        return passwordToken;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/UserCredentialModel.java b/model/api/src/main/java/org/keycloak/models/UserCredentialModel.java
index 4c5275b..2f1fbf7 100755
--- a/model/api/src/main/java/org/keycloak/models/UserCredentialModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserCredentialModel.java
@@ -8,6 +8,7 @@ import java.util.UUID;
  */
 public class UserCredentialModel {
     public static final String PASSWORD = "password";
+    public static final String PASSWORD_TOKEN = "password-token";
 
     // Secret is same as password but it is not hashed
     public static final String SECRET = "secret";
@@ -27,6 +28,12 @@ public class UserCredentialModel {
         model.setValue(password);
         return model;
     }
+    public static UserCredentialModel passwordToken(String passwordToken) {
+        UserCredentialModel model = new UserCredentialModel();
+        model.setType(PASSWORD_TOKEN);
+        model.setValue(passwordToken);
+        return model;
+    }
 
     public static UserCredentialModel secret(String password) {
         UserCredentialModel model = new UserCredentialModel();
diff --git a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index 901571f..a596c82 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -1,11 +1,16 @@
 package org.keycloak.models.utils;
 
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.representations.PasswordToken;
+import org.keycloak.util.Time;
 
+import java.io.IOException;
 import java.util.List;
 
 /**
@@ -57,6 +62,28 @@ public class CredentialValidation {
 
     }
 
+    public static boolean validPasswordToken(RealmModel realm, UserModel user, String encodedPasswordToken) {
+        JWSInput jws = new JWSInput(encodedPasswordToken);
+        if (!RSAProvider.verify(jws, realm.getPublicKey())) {
+            return false;
+        }
+        try {
+            PasswordToken passwordToken = jws.readJsonContent(PasswordToken.class);
+            if (!passwordToken.getRealm().equals(realm.getName())) {
+                return false;
+            }
+            if (!passwordToken.getUser().equals(user.getId())) {
+                return false;
+            }
+            if (Time.currentTime() - passwordToken.getTimestamp() > realm.getAccessCodeLifespanUserAction()) {
+                return false;
+            }
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
     public static boolean validTOTP(RealmModel realm, UserModel user, String otp) {
         UserCredentialValueModel passwordCred = null;
         for (UserCredentialValueModel cred : user.getCredentialsDirectly()) {
@@ -114,6 +141,10 @@ public class CredentialValidation {
             if (!validPassword(realm, user, credential.getValue())) {
                 return false;
             }
+        } else if (credential.getType().equals(UserCredentialModel.PASSWORD_TOKEN)) {
+            if (!validPasswordToken(realm, user, credential.getValue())) {
+                return false;
+            }
         } else if (credential.getType().equals(UserCredentialModel.TOTP)) {
             if (!validTOTP(realm, user, credential.getValue())) {
                 return false;
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 75e7d8e..3930df1 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -265,29 +265,37 @@ public class AuthenticationManager {
 
         if (types.contains(CredentialRepresentation.PASSWORD)) {
             List<UserCredentialModel> credentials = new LinkedList<UserCredentialModel>();
+
             String password = formData.getFirst(CredentialRepresentation.PASSWORD);
-            if (password == null) {
-                logger.debug("Password not provided");
-                return AuthenticationStatus.MISSING_PASSWORD;
+            if (password != null) {
+                credentials.add(UserCredentialModel.password(password));
             }
-            credentials.add(UserCredentialModel.password(password));
 
-            if (user.isTotp()) {
-                String token = formData.getFirst(CredentialRepresentation.TOTP);
-                if (token == null) {
-                    logger.debug("TOTP token not provided");
-                    return AuthenticationStatus.MISSING_TOTP;
-                }
-                credentials.add(UserCredentialModel.totp(token));
+            String passwordToken = formData.getFirst(CredentialRepresentation.PASSWORD_TOKEN);
+            if (passwordToken != null) {
+                credentials.add(UserCredentialModel.passwordToken(passwordToken));
+            }
+
+            String totp = formData.getFirst(CredentialRepresentation.TOTP);
+            if (totp != null) {
+                credentials.add(UserCredentialModel.totp(totp));
+            }
 
-             }
+            if (password == null && passwordToken == null) {
+                logger.debug("Password not provided");
+                return AuthenticationStatus.MISSING_PASSWORD;
+            }
 
-            logger.debug("validating password for user: " + username);
+            logger.debugv("validating password for user: {0}", username);
 
             if (!session.users().validCredentials(realm, user, credentials)) {
                 return AuthenticationStatus.INVALID_CREDENTIALS;
             }
 
+            if (user.isTotp() && totp == null) {
+                return AuthenticationStatus.MISSING_TOTP;
+            }
+
             if (!user.getRequiredActions().isEmpty()) {
                 return AuthenticationStatus.ACTIONS_REQUIRED;
             } else {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
index acee11d..751f092 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
@@ -66,7 +66,6 @@ public class UserFederationResource {
     @Path("providers")
     @Produces("application/json")
     public List<UserFederationProviderFactoryRepresentation> getProviders() {
-        logger.info("get provider list");
         auth.requireView();
         List<UserFederationProviderFactoryRepresentation> providers = new LinkedList<UserFederationProviderFactoryRepresentation>();
         for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(UserFederationProvider.class)) {
@@ -75,7 +74,6 @@ public class UserFederationResource {
             rep.setOptions(((UserFederationProviderFactory)factory).getConfigurationOptions());
             providers.add(rep);
         }
-        logger.info("provider list.size() " + providers.size());
         return providers;
     }
 
@@ -89,7 +87,6 @@ public class UserFederationResource {
     @Path("providers/{id}")
     @Produces("application/json")
     public UserFederationProviderFactoryRepresentation getProvider(@PathParam("id") String id) {
-        logger.info("get provider list");
         auth.requireView();
         for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(UserFederationProvider.class)) {
             if (!factory.getId().equals(id)) {
@@ -113,7 +110,6 @@ public class UserFederationResource {
     @Path("instances")
     @Consumes("application/json")
     public Response createProviderInstance(UserFederationProviderRepresentation rep) {
-        logger.info("createProvider");
         auth.requireManage();
         String displayName = rep.getDisplayName();
         if (displayName != null && displayName.trim().equals("")) {
@@ -136,7 +132,6 @@ public class UserFederationResource {
     @Path("instances/{id}")
     @Consumes("application/json")
     public void updateProviderInstance(@PathParam("id") String id, UserFederationProviderRepresentation rep) {
-        logger.info("updateProvider");
         auth.requireManage();
         String displayName = rep.getDisplayName();
         if (displayName != null && displayName.trim().equals("")) {
@@ -158,7 +153,6 @@ public class UserFederationResource {
     @Path("instances/{id}")
     @Produces("application/json")
     public UserFederationProviderRepresentation getProviderInstance(@PathParam("id") String id) {
-        logger.info("getProvider");
         auth.requireView();
         for (UserFederationProviderModel model : realm.getUserFederationProviders()) {
             if (model.getId().equals(id)) {
@@ -176,7 +170,6 @@ public class UserFederationResource {
     @DELETE
     @Path("instances/{id}")
     public void deleteProviderInstance(@PathParam("id") String id) {
-        logger.info("deleteProvider");
         auth.requireManage();
         UserFederationProviderModel model = new UserFederationProviderModel(id, null, null, -1, null, -1, -1, 0);
         realm.removeUserFederationProvider(model);
@@ -194,7 +187,6 @@ public class UserFederationResource {
     @Produces("application/json")
     @NoCache
     public List<UserFederationProviderRepresentation> getUserFederationInstances() {
-        logger.info("getUserFederationInstances");
         auth.requireManage();
         List<UserFederationProviderRepresentation> reps = new LinkedList<UserFederationProviderRepresentation>();
         for (UserFederationProviderModel model : realm.getUserFederationProviders()) {
@@ -213,7 +205,7 @@ public class UserFederationResource {
     @Path("sync/{id}")
     @NoCache
     public Response syncUsers(@PathParam("id") String providerId, @QueryParam("action") String action) {
-        logger.info("triggerSync");
+        logger.debug("Syncing users");
         auth.requireManage();
 
         for (UserFederationProviderModel model : realm.getUserFederationProviders()) {
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 61e47b4..a35ebf8 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -17,6 +17,7 @@ import org.keycloak.events.EventBuilder;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventType;
+import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.login.LoginFormsProvider;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.ClientModel;
@@ -38,6 +39,7 @@ import org.keycloak.services.ForbiddenException;
 import org.keycloak.services.managers.AccessCode;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
+import org.keycloak.representations.PasswordToken;
 import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.messages.Messages;
@@ -545,6 +547,11 @@ public class TokenService {
                 event.error(Errors.USER_DISABLED);
                 return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
             case MISSING_TOTP:
+                formData.remove(CredentialRepresentation.PASSWORD);
+
+                String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey());
+                formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken);
+
                 return Flows.forms(this.session, realm, client, uriInfo).setFormData(formData).createLoginTotp();
             case INVALID_USER:
                 event.error(Errors.USER_NOT_FOUND);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
index f53e85a..78b5249 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
@@ -94,6 +94,8 @@ public class LoginTotpTest {
 
     private TimeBasedOTP totp = new TimeBasedOTP();
 
+    private int lifespan;
+
     @Before
     public void before() throws MalformedURLException {
         totp = new TimeBasedOTP();
@@ -133,14 +135,45 @@ public class LoginTotpTest {
         loginPage.open();
         loginPage.login("test-user@localhost", "invalid");
 
-        loginTotpPage.assertCurrent();
-
-        loginTotpPage.login(totp.generate("totpSecret"));
-
         loginPage.assertCurrent();
+
         Assert.assertEquals("Invalid username or password.", loginPage.getError());
 
         events.expectLogin().error("invalid_user_credentials").removeDetail(Details.CODE_ID).session((String) null).assertEvent();
     }
 
+    @Test
+    public void loginWithTotpExpiredPasswordToken() throws Exception {
+        try {
+            keycloakRule.configure(new KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    lifespan = appRealm.getAccessCodeLifespanUserAction();
+                    appRealm.setAccessCodeLifespanUserAction(1);
+                }
+            });
+
+            loginPage.open();
+            loginPage.login("test-user@localhost", "password");
+
+            loginTotpPage.assertCurrent();
+
+            Thread.sleep(2000);
+
+            loginTotpPage.login(totp.generate("totpSecret"));
+
+            loginPage.assertCurrent();
+            Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+            events.expectLogin().error("invalid_user_credentials").removeDetail(Details.CODE_ID).session((String) null).assertEvent();
+        } finally {
+            keycloakRule.configure(new KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setAccessCodeLifespanUserAction(lifespan);
+                }
+            });
+        }
+    }
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationManagerTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationManagerTest.java
index 24a473d..16ff953 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationManagerTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationManagerTest.java
@@ -6,15 +6,19 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.ClientConnection;
+import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.TimeBasedOTP;
+import org.keycloak.representations.PasswordToken;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
 import org.keycloak.services.managers.BruteForceProtector;
+import org.keycloak.services.managers.RealmManager;
 
 import javax.ws.rs.core.MultivaluedMap;
 import java.util.UUID;
@@ -117,7 +121,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
     }
 
     @Test
-    public void authFormWithToltpInvalidPassword() {
+    public void authFormWithTotpInvalidPassword() {
         authFormWithTotp();
 
         formData.remove(CredentialRepresentation.PASSWORD);
@@ -128,6 +132,16 @@ public class AuthenticationManagerTest extends AbstractModelTest {
     }
 
     @Test
+    public void authFormWithTotpMissingPassword() {
+        authFormWithTotp();
+
+        formData.remove(CredentialRepresentation.PASSWORD);
+
+        AuthenticationStatus status = am.authenticateForm(session, dummyConnection, realm, formData);
+        Assert.assertEquals(AuthenticationStatus.MISSING_PASSWORD, status);
+    }
+
+    @Test
     public void authFormWithTotpInvalidTotp() {
         authFormWithTotp();
 
@@ -148,6 +162,92 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         Assert.assertEquals(AuthenticationStatus.MISSING_TOTP, status);
     }
 
+    @Test
+    public void authFormWithTotpPasswordToken() {
+        realm.addRequiredCredential(CredentialRepresentation.TOTP);
+
+        String totpSecret = UUID.randomUUID().toString();
+
+        UserCredentialModel credential = new UserCredentialModel();
+        credential.setType(CredentialRepresentation.TOTP);
+        credential.setValue(totpSecret);
+
+        user.updateCredential(credential);
+
+        user.setTotp(true);
+
+        String token = otp.generate(totpSecret);
+
+        formData.add(CredentialRepresentation.TOTP, token);
+        formData.remove(CredentialRepresentation.PASSWORD);
+
+        String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey());
+        formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken);
+
+        AuthenticationStatus status = am.authenticateForm(session, dummyConnection, realm, formData);
+        Assert.assertEquals(AuthenticationStatus.SUCCESS, status);
+    }
+
+    @Test
+    public void authFormWithTotpPasswordTokenInvalidKey() {
+        authFormWithTotpPasswordToken();
+
+        formData.remove(CredentialRepresentation.PASSWORD_TOKEN);
+        String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey());
+        formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken);
+
+        KeycloakModelUtils.generateRealmKeys(realm);
+
+        AuthenticationStatus status = am.authenticateForm(session, dummyConnection, realm, formData);
+        Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
+    }
+
+    @Test
+    public void authFormWithTotpPasswordTokenInvalidRealm() {
+        authFormWithTotpPasswordToken();
+
+        formData.remove(CredentialRepresentation.PASSWORD_TOKEN);
+        String passwordToken = new JWSBuilder().jsonContent(new PasswordToken("invalid", user.getId())).rsa256(realm.getPrivateKey());
+        formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken);
+
+        AuthenticationStatus status = am.authenticateForm(session, dummyConnection, realm, formData);
+        Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
+    }
+
+    @Test
+    public void authFormWithTotpPasswordTokenInvalidUser() {
+        authFormWithTotpPasswordToken();
+
+        formData.remove(CredentialRepresentation.PASSWORD_TOKEN);
+        String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), "invalid")).rsa256(realm.getPrivateKey());
+        formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken);
+
+        AuthenticationStatus status = am.authenticateForm(session, dummyConnection, realm, formData);
+        Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
+    }
+
+    @Test
+    public void authFormWithTotpPasswordTokenExpired() throws InterruptedException {
+        int lifespan = realm.getAccessCodeLifespanUserAction();
+
+        try {
+            authFormWithTotpPasswordToken();
+
+            realm.setAccessCodeLifespanUserAction(1);
+
+            formData.remove(CredentialRepresentation.PASSWORD_TOKEN);
+            String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), "invalid")).rsa256(realm.getPrivateKey());
+            formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken);
+
+            Thread.sleep(2000);
+
+            AuthenticationStatus status = am.authenticateForm(session, dummyConnection, realm, formData);
+            Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
+        } finally {
+            realm.setAccessCodeLifespanUserAction(lifespan);
+        }
+    }
+
     @Before
     @Override
     public void before() throws Exception {
@@ -157,8 +257,9 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         realm.setAccessCodeLifespan(100);
         realm.setEnabled(true);
         realm.setName("TestAuth");
-        realm.setPrivateKeyPem("0234234");
-        realm.setPublicKeyPem("0234234");
+
+        KeycloakModelUtils.generateRealmKeys(realm);
+
         realm.setAccessTokenLifespan(1000);
         realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
index 2036121..e1a934a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
@@ -33,6 +33,9 @@ public class LoginTotpPage extends AbstractPage {
     @FindBy(id = "totp")
     private WebElement totpInput;
 
+    @FindBy(id = "password-token")
+    private WebElement passwordToken;
+
     @FindBy(css = "input[type=\"submit\"]")
     private WebElement submitButton;