KEYCLOAK-84 added totp test and better form feedback
This commit is contained in:
parent
a0808ad224
commit
23f2fb9125
10 changed files with 122 additions and 19 deletions
|
@ -81,6 +81,10 @@ public class FormServiceImpl implements FormService {
|
|||
|
||||
Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
|
||||
if (dataBean.getError() != null){
|
||||
attributes.put("message", new ErrorBean(dataBean.getError(), dataBean.getErrorType()));
|
||||
}
|
||||
|
||||
RealmBean realm = new RealmBean(dataBean.getRealm());
|
||||
attributes.put("template", new TemplateBean(realm, dataBean.getContextPath()));
|
||||
|
||||
|
@ -145,10 +149,6 @@ public class FormServiceImpl implements FormService {
|
|||
|
||||
private class CommandPassword implements Command {
|
||||
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
|
||||
if (dataBean.getError() != null){
|
||||
attributes.put("message", new ErrorBean(dataBean.getError(), dataBean.getErrorType()));
|
||||
}
|
||||
|
||||
RealmBean realm = new RealmBean(dataBean.getRealm());
|
||||
|
||||
attributes.put("realm", realm);
|
||||
|
|
|
@ -60,6 +60,7 @@ body {
|
|||
}
|
||||
.header.rcue .navbar {
|
||||
margin-bottom: 0;
|
||||
z-index:auto;
|
||||
}
|
||||
.header.rcue .navbar.primary {
|
||||
font-size: 13px;
|
||||
|
|
|
@ -35,7 +35,8 @@
|
|||
</div>
|
||||
|
||||
<#if error?has_content>
|
||||
<div class="feedback error bottom-left show">
|
||||
<div class="
|
||||
error bottom-left show">
|
||||
<p>
|
||||
<strong id="loginError">${rb.getString(error.summary)}</strong>
|
||||
</p>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Edit Account</title>
|
||||
<title>Edit Account - <#nested "title"></title>
|
||||
<link rel="icon" href="img/favicon.ico">
|
||||
|
||||
<!-- Frameworks -->
|
||||
|
@ -34,10 +34,14 @@
|
|||
</head>
|
||||
<body class="admin-console user ${bodyClass}">
|
||||
|
||||
<#if error?has_content>
|
||||
<!--div class="feedback success show"><p><strong>Success!</strong> Your changes have been saved.</p></div-->
|
||||
<#if message?has_content>
|
||||
<div class="feedback-aligner">
|
||||
<div class="alert alert-danger">${rb.getString(error.summary)}</div>
|
||||
<#if message.success>
|
||||
<div class="feedback success show"><p><strong>${rb.getString('successHeader')}</strong> ${rb.getString(message.summary)}</p></div>
|
||||
</#if>
|
||||
<#if message.error>
|
||||
<div class="feedback error show"><p><strong>${rb.getString('errorHeader')}</strong> ${rb.getString(message.summary)}</p></div>
|
||||
</#if>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<#import "template-main.ftl" as layout>
|
||||
<@layout.mainLayout active='totp' bodyClass='totp'; section>
|
||||
|
||||
<#if section = "header">
|
||||
<#if section = "title">
|
||||
Google Authenticator
|
||||
<#elseif section = "header">
|
||||
|
||||
<#if totp.enabled>
|
||||
<h2>Authenticators</h2>
|
||||
|
@ -12,7 +14,6 @@
|
|||
<#elseif section = "content">
|
||||
|
||||
<#if totp.enabled>
|
||||
<#-- TODO this is only mock page -->
|
||||
<form>
|
||||
<fieldset>
|
||||
<p class="info">You have the following authenticators set up:</p>
|
||||
|
@ -21,13 +22,16 @@
|
|||
<tbody>
|
||||
<tr>
|
||||
<td class="provider"><span class="social googleplus">Google</span></td>
|
||||
<td class="soft">Connected as john@google.com</td>
|
||||
<td class="action">
|
||||
<a href="${url.totpRemoveUrl}" class="button">Remove Google</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="info">
|
||||
If the totp authentication is required by the realm and you remove your configured authenticator,
|
||||
you will have to reconfigure it immediately or on the next login.
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -37,6 +37,9 @@ invalidPasswordExisting=Invalid existing password
|
|||
invalidPasswordConfirm=Password confirmation doesn't match
|
||||
invalidTotp=Invalid authenticator code
|
||||
|
||||
successTotp=Google authenticator configured.
|
||||
successTotpRemoved=Google authenticator removed.
|
||||
|
||||
usernameExists=Username already exists
|
||||
|
||||
error=A system error has occured, contact admin
|
||||
|
|
|
@ -169,7 +169,8 @@ public class AccountService {
|
|||
public Response processTotpRemove() {
|
||||
UserModel user = getUserFromAuthManager();
|
||||
user.setTotp(false);
|
||||
return Flows.forms(realm, request, uriInfo).setUser(user).forwardToTotp();
|
||||
return Flows.forms(realm, request, uriInfo).setError("successTotpRemoved").setErrorType(FormFlows.ErrorType.SUCCESS)
|
||||
.setUser(user).forwardToTotp();
|
||||
}
|
||||
|
||||
@Path("totp")
|
||||
|
@ -214,7 +215,8 @@ public class AccountService {
|
|||
if (accessCodeEntry != null) {
|
||||
return redirectOauth(user, accessCodeEntry);
|
||||
} else {
|
||||
return Flows.forms(realm, request, uriInfo).setUser(user).forwardToTotp();
|
||||
return Flows.forms(realm, request, uriInfo).setError("successTotp").setErrorType(FormFlows.ErrorType.SUCCESS)
|
||||
.setUser(user).forwardToTotp();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ import org.keycloak.services.managers.RealmManager;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AccountTotpPage;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||
import org.keycloak.testsuite.pages.LoginConfigTotpPage;
|
||||
|
@ -54,9 +56,6 @@ public class RequiredActionTotpSetupTest {
|
|||
public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
|
||||
appRealm.addRequiredCredential(CredentialRepresentation.TOTP);
|
||||
appRealm.setResetPasswordAllowed(true);
|
||||
|
||||
UserModel user = appRealm.getUser("test-user@localhost");
|
||||
user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -76,6 +75,12 @@ public class RequiredActionTotpSetupTest {
|
|||
@WebResource
|
||||
protected LoginConfigTotpPage totpPage;
|
||||
|
||||
@WebResource
|
||||
protected AccountTotpPage accountTotpPage;
|
||||
|
||||
@WebResource
|
||||
protected OAuthClient oauth;
|
||||
|
||||
@WebResource
|
||||
protected RegisterPage registerPage;
|
||||
|
||||
|
@ -101,9 +106,69 @@ public class RequiredActionTotpSetupTest {
|
|||
|
||||
totpPage.assertCurrent();
|
||||
|
||||
totpPage.configure(totp.generate(totpPage.getTotpSecret()));
|
||||
String totpSecret = totpPage.getTotpSecret();
|
||||
|
||||
totpPage.configure(totp.generate(totpSecret));
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
oauth.openLogout();
|
||||
|
||||
loginPage.open();
|
||||
loginPage.loginTotp("test-user@localhost", "password", totp.generate(totpSecret));
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setupTotpRegisteredAfterTotpRemoval() {
|
||||
// Register new user
|
||||
loginPage.open();
|
||||
loginPage.clickRegister();
|
||||
registerPage.register("firstName2", "lastName2", "email2", "setupTotp2", "password2", "password2");
|
||||
|
||||
// Configure totp
|
||||
totpPage.assertCurrent();
|
||||
|
||||
String totpCode = totpPage.getTotpSecret();
|
||||
totpPage.configure(totp.generate(totpCode));
|
||||
|
||||
// After totp config, user should be on the app page
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
// Logout
|
||||
oauth.openLogout();
|
||||
|
||||
// Try to login after logout
|
||||
loginPage.open();
|
||||
loginPage.login("setupTotp2", "password2");
|
||||
|
||||
// Totp is already configured, thus one-time password is needed, login page should be loaded
|
||||
Assert.assertTrue(loginPage.isCurrent());
|
||||
Assert.assertFalse(totpPage.isCurrent());
|
||||
|
||||
// Login with one-time password
|
||||
loginPage.loginTotp("setupTotp2", "password2", totp.generate(totpCode));
|
||||
|
||||
// Open account page
|
||||
accountTotpPage.open();
|
||||
accountTotpPage.assertCurrent();
|
||||
|
||||
// Remove google authentificator
|
||||
accountTotpPage.removeTotp();
|
||||
|
||||
// Logout
|
||||
oauth.openLogout();
|
||||
|
||||
// Try to login
|
||||
loginPage.open();
|
||||
loginPage.login("setupTotp2", "password2");
|
||||
|
||||
// Since the authentificator was removed, it has to be set up again
|
||||
totpPage.assertCurrent();
|
||||
totpPage.configure(totp.generate(totpPage.getTotpSecret()));
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,9 @@ public class AccountTotpPage extends Page {
|
|||
@FindBy(css = "button[type=\"submit\"]")
|
||||
private WebElement submitButton;
|
||||
|
||||
@FindBy(linkText = "Remove Google")
|
||||
private WebElement removeLink;
|
||||
|
||||
public void configure(String totp) {
|
||||
totpInput.sendKeys(totp);
|
||||
submitButton.click();
|
||||
|
@ -51,11 +54,15 @@ public class AccountTotpPage extends Page {
|
|||
}
|
||||
|
||||
public boolean isCurrent() {
|
||||
return driver.getPageSource().contains("Google Authenticator Setup");
|
||||
return driver.getTitle().contains("Edit Account - Google Authenticator");
|
||||
}
|
||||
|
||||
public void open() {
|
||||
driver.navigate().to(PATH);
|
||||
}
|
||||
|
||||
public void removeTotp() {
|
||||
removeLink.click();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,6 +41,9 @@ public class LoginPage extends Page {
|
|||
@FindBy(id = "password")
|
||||
private WebElement passwordInput;
|
||||
|
||||
@FindBy(id = "totp")
|
||||
private WebElement totp;
|
||||
|
||||
@FindBy(css = "input[type=\"submit\"]")
|
||||
private WebElement submitButton;
|
||||
|
||||
|
@ -63,6 +66,19 @@ public class LoginPage extends Page {
|
|||
submitButton.click();
|
||||
}
|
||||
|
||||
public void loginTotp(String username, String password, String code) {
|
||||
usernameInput.clear();
|
||||
usernameInput.sendKeys(username);
|
||||
|
||||
passwordInput.clear();
|
||||
passwordInput.sendKeys(password);
|
||||
|
||||
totp.clear();
|
||||
totp.sendKeys(code);
|
||||
|
||||
submitButton.click();
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return loginErrorMessage != null ? loginErrorMessage.getText() : null;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue