diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
index 0c93c0f..5d86085 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
@@ -29,8 +29,10 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.models.*;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
+import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient;
@@ -63,9 +65,24 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
user.setEmail("direct-login@localhost");
user.setEnabled(true);
+ session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
+
userId = user.getId();
- session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
+ UserModel user2 = session.users().addUser(appRealm, "direct-login-otp");
+ user2.setEnabled(true);
+
+ UserCredentialModel credentials = new UserCredentialModel();
+ credentials.setType(CredentialRepresentation.TOTP);
+ credentials.setValue("totpSecret");
+ user2.updateCredential(credentials);
+
+ user2.setOtpEnabled(true);
+
+ session.users().updateCredential(appRealm, user2, UserCredentialModel.password("password"));
+
+ userId2 = user2.getId();
+
}
});
@@ -83,6 +100,10 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
private static String userId;
+ private static String userId2;
+
+ private TimeBasedOTP totp = new TimeBasedOTP();
+
@Test
public void grantAccessTokenUsername() throws Exception {
grantAccessToken("direct-login", "resource-owner");
@@ -98,11 +119,57 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
grantAccessToken("direct-login", "resource-owner-public");
}
+ @Test
+ public void grantAccessTokenWithTotp() throws Exception {
+ grantAccessToken(userId2, "direct-login-otp", "resource-owner", totp.generateTOTP("totpSecret"));
+ }
+
+ @Test
+ public void grantAccessTokenMissingTotp() throws Exception {
+ oauth.clientId("resource-owner");
+
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "direct-login-otp", "password");
+
+ assertEquals(401, response.getStatusCode());
+
+ assertEquals("invalid_grant", response.getError());
+
+ events.expectLogin()
+ .client("resource-owner")
+ .session((String) null)
+ .clearDetails()
+ .error(Errors.INVALID_USER_CREDENTIALS)
+ .user(userId2)
+ .assertEvent();
+ }
+
+ @Test
+ public void grantAccessTokenInvalidTotp() throws Exception {
+ oauth.clientId("resource-owner");
+
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "direct-login-otp", "password", totp.generateTOTP("totpSecret2"));
+
+ assertEquals(401, response.getStatusCode());
+
+ assertEquals("invalid_grant", response.getError());
+
+ events.expectLogin()
+ .client("resource-owner")
+ .session((String) null)
+ .clearDetails()
+ .error(Errors.INVALID_USER_CREDENTIALS)
+ .user(userId2)
+ .assertEvent();
+ }
private void grantAccessToken(String login, String clientId) throws Exception {
+ grantAccessToken(userId, login, clientId, null);
+ }
+
+ private void grantAccessToken(String userId, String login, String clientId, String otp) throws Exception {
oauth.clientId(clientId);
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", login, "password");
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", login, "password", otp);
assertEquals(200, response.getStatusCode());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
index 46235ed..e53ebfb 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
@@ -205,6 +205,10 @@ public class OAuthClient {
return doGrantAccessTokenRequest(realm, username, password, null, clientId, clientSecret);
}
+ public AccessTokenResponse doGrantAccessTokenRequest(String clientSecret, String username, String password, String otp) throws Exception {
+ return doGrantAccessTokenRequest(realm, username, password, otp, clientId, clientSecret);
+ }
+
public AccessTokenResponse doGrantAccessTokenRequest(String realm, String username, String password, String totp,
String clientId, String clientSecret) throws Exception {
CloseableHttpClient client = new DefaultHttpClient();