Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions forms/src/main/java/org/keycloak/forms/ErrorBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,41 @@
*/
package org.keycloak.forms;

import org.keycloak.services.resources.flows.FormFlows;

/**
* @author <a href="mailto:[email protected]">Stian Thorgersen</a>
*/
public class ErrorBean {

private String summary;

private FormFlows.ErrorType type;

// Message is considered ERROR by default
public ErrorBean(String summary) {
this(summary, FormFlows.ErrorType.ERROR);
}

public ErrorBean(String summary, FormFlows.ErrorType type) {
this.summary = summary;
this.type = type;
}

public String getSummary() {
return summary;
}

public boolean isSuccess(){
return FormFlows.ErrorType.SUCCESS.equals(this.type);
}

public boolean isWarning(){
return FormFlows.ErrorType.WARNING.equals(this.type);
}

public boolean isError(){
return FormFlows.ErrorType.ERROR.equals(this.type);
}

}
5 changes: 5 additions & 0 deletions forms/src/main/java/org/keycloak/service/FormServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.keycloak.forms.UrlBean;
import org.keycloak.forms.UserBean;
import org.keycloak.services.FormService;
import org.keycloak.services.resources.flows.FormFlows;
import org.keycloak.services.resources.flows.Pages;

/**
Expand Down Expand Up @@ -149,6 +150,10 @@ public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,19 +250,19 @@ button.primary:enabled:active {
background-position: 1.27272727272727em 1.63636363636364em;
}
.feedback.error {
background-image: url(img/feedback-error-arrow-down.svg);
background-image: url(img/feedback-error-arrow-down.png);
}
.feedback.error p {
border-color: #b91415;
background-image: url(img/feedback-error-sign.svg);
background-image: url(img/feedback-error-sign.png);
background-color: #f8e7e7;
}
.feedback.success {
background-image: url(img/feedback-success-arrow-down.svg);
background-image: url(img/feedback-success-arrow-down.png);
}
.feedback.success p {
border-color: #4b9e39;
background-image: url(img/feedback-success-sign.svg);
background-image: url(img/feedback-success-sign.png);
background-color: #e4f1e1;
}
.feedback.warning p {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,12 @@ a.zocial:before {
line-height: 1.3em;
}

.rcue-login-register.reset p.subtitle {
margin-bottom: 10px;
position: inherit;
text-align: right;
}

.rcue-login-register .background-area p.instruction.instruction.second {
color: #999999;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
<#import "template-login-action.ftl" as layout>
<@layout.registrationLayout bodyClass=""; section>
<@layout.registrationLayout bodyClass="reset"; section>
<#if section = "title">

Reset password
${rb.getString('emailForgotHeader')}

<#elseif section = "header">

Reset password
${rb.getString('emailForgotHeader')}

<#elseif section = "form">

<div id="form">
<#if message?has_content>
<#if message.success>
<div class="feedback success bottom-left show"><p><strong>${rb.getString('successHeader')}</strong> ${rb.getString(message.summary)}</p></div>
</#if>
<#if message.error>
<div class="feedback error bottom-left show"><p><strong>${rb.getString('errorHeader')}</strong><br/>${rb.getString(message.summary)}</p></div>
</#if>
</#if>

<p class="instruction">${rb.getString('emailInstruction')}</p>
<form action="${url.passwordResetUrl}" method="post">
<div>
<label for="username">${rb.getString('username')}</label>
<input id="username" name="username" type="text" />
<label for="username">${rb.getString('username')}</label><input id="username" name="username" type="text" />
</div>
<div>
<label for="email">${rb.getString('email')}</label>
<input type="text" id="email" name="email" />
<label for="email">${rb.getString('email')}</label><input type="text" id="email" name="email" />
</div>

<input class="btn-primary" type="submit" value="Submit" />
</form>
</div>

<#elseif section = "info" >

<div id="info">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
<#import "template-login-action.ftl" as layout>
<@layout.registrationLayout bodyClass=""; section>
<@layout.registrationLayout bodyClass="reset"; section>
<#if section = "title">

Update password
${rb.getString('emailUpdateHeader')}

<#elseif section = "header">

Update password
${rb.getString('emailUpdateHeader')}

<#elseif section = "form">

<div id="form">
<form action="${url.passwordUrl}" method="post">
<div>
<label for="password-new">${rb.getString('passwordNew')}</label>
<input type="password" id="password-new" name="password-new" />
<label for="password-new">${rb.getString('passwordNew')}</label><input type="password" id="password-new" name="password-new" />
</div>
<div>
<label for="password-confirm">${rb.getString('passwordConfirm')}</label>
<input type="password" id="password-confirm" name="password-confirm" />
<label for="password-confirm">${rb.getString('passwordConfirm')}</label><input type="password" id="password-confirm" name="password-confirm" />
</div>

<input class="btn-primary" type="submit" value="Submit" />
Expand Down
12 changes: 12 additions & 0 deletions forms/src/main/resources/org/keycloak/forms/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ email=Email
password=Password
passwordConfirm=Confirm Password
passwordNew=New Password
passwordNewConfirm=New Password confirmation

authenticatorCode=One-time-password
clientCertificate=Client Certificate
Expand All @@ -39,3 +40,14 @@ invalidTotp=Invalid authenticator code
usernameExists=Username already exists

error=A system error has occured, contact admin

successHeader=Success!
errorHeader=Error!

# Forgot password part

emailForgotHeader=Forgot Your Password?
emailUpdateHeader=Update password
emailSent=You should receive an email shortly with further instructions.
emailError=Invalid username or email.
emailInstruction=Enter your username and email address and we will send you instructions on how to create a new password.
12 changes: 12 additions & 0 deletions services/src/main/java/org/keycloak/services/FormService.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.resources.flows.FormFlows;

/**
* @author <a href="mailto:[email protected]">Viliam Rockai</a>
Expand All @@ -42,6 +43,9 @@ public static class FormServiceDataBean {
private RealmModel realm;
private UserModel userModel;
private String error;

private FormFlows.ErrorType errorType;

private MultivaluedMap<String, String> formData;
private URI baseURI;

Expand Down Expand Up @@ -121,5 +125,13 @@ public UserModel getUserModel() {
public void setUserModel(UserModel userModel) {
this.userModel = userModel;
}

public FormFlows.ErrorType getErrorType() {
return errorType;
}

public void setErrorType(FormFlows.ErrorType errorType) {
this.errorType = errorType;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,16 @@ public void sendPasswordReset(UserModel user, RealmModel realm, AccessCodeEntry
URI uri = builder.build(realm.getId());

StringBuilder sb = new StringBuilder();

sb.append("Hi ").append(user.getFirstName()).append(",\n\n");
sb.append("Someone just requested to change your Keycloak account's password. ");
sb.append("If this was you, click on the link below to set a new password:\n");
sb.append(uri.toString());
sb.append("\n");
sb.append("Expires in " + TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction()));
sb.append("\n\nThis link will expire within ").append(TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction()));
sb.append(" minutes.\n\n");
sb.append("If you don't want to reset your password, just ignore this message and nothing will be changed.\n\n");
sb.append("Thanks,\n");
sb.append("The Keycloak Team");

try {
send(user.getEmail(), "Reset password link", sb.toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,7 @@ public Response sendPasswordReset(final MultivaluedMap<String, String> formData)

UserModel user = realm.getUser(username);
if (user == null || !email.equals(user.getEmail())) {
return Flows.forms(realm, request, uriInfo).setError("Invalid username or email")
.forwardToAction(RequiredAction.UPDATE_PASSWORD);
return Flows.forms(realm, request, uriInfo).setError("emailError").forwardToPasswordReset();
}

Set<RequiredAction> requiredActions = new HashSet<RequiredAction>(user.getRequiredActions());
Expand All @@ -253,7 +252,8 @@ public Response sendPasswordReset(final MultivaluedMap<String, String> formData)

new EmailSender().sendPasswordReset(user, realm, accessCode, uriInfo);

return Flows.forms(realm, request, uriInfo).forwardToPasswordReset();
return Flows.forms(realm, request, uriInfo).setError("emailSent").setErrorType(FormFlows.ErrorType.SUCCESS)
.forwardToPasswordReset();
}

@Path("email-verification")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ public class FormFlows {
public static final String SOCIAL_REGISTRATION = "socialRegistration";
public static final String CODE = "code";

// TODO refactor/rename "error" to "message" everywhere where it makes sense
private String error;

public static enum ErrorType {SUCCESS, WARNING, ERROR};
private ErrorType errorType;

private MultivaluedMap<String, String> formData;

private RealmModel realm;
Expand Down Expand Up @@ -98,6 +103,7 @@ public Response forwardToAccount() {
private Response forwardToForm(String template) {

FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, error);
formDataBean.setErrorType(errorType == null ? ErrorType.ERROR : errorType);

// Getting URI needed by form processing service
ResteasyUriInfo uriInfo = request.getUri();
Expand Down Expand Up @@ -172,6 +178,11 @@ public FormFlows setError(String error) {
return this;
}

public FormFlows setErrorType(ErrorType errorType) {
this.errorType = errorType;
return this;
}

public FormFlows setUser(UserModel userModel) {
this.userModel = userModel;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,14 @@ public void resetPassword() throws IOException, MessagingException {

resetPasswordPage.assertCurrent();

Assert.assertEquals("Success!", resetPasswordPage.getMessage());

Assert.assertEquals(1, greenMail.getReceivedMessages().length);

MimeMessage message = greenMail.getReceivedMessages()[0];

String body = (String) message.getContent();
String changePasswordUrl = body.split("\n")[0];
String changePasswordUrl = body.split("\n")[3];

driver.navigate().to(changePasswordUrl.trim());

Expand All @@ -109,4 +111,34 @@ public void resetPassword() throws IOException, MessagingException {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
}

@Test
public void resetPasswordWrongUsername() throws IOException, MessagingException {
loginPage.open();
loginPage.resetPassword();

resetPasswordPage.assertCurrent();

resetPasswordPage.changePassword("invalid", "test-user@localhost");

resetPasswordPage.assertCurrent();

Assert.assertNotEquals("Success!", resetPasswordPage.getMessage());
Assert.assertEquals("Error!", resetPasswordPage.getMessage());
}

@Test
public void resetPasswordWrongEmail() throws IOException, MessagingException {
loginPage.open();
loginPage.resetPassword();

resetPasswordPage.assertCurrent();

resetPasswordPage.changePassword("test-user@localhost", "invalid");

resetPasswordPage.assertCurrent();

Assert.assertNotEquals("Success!", resetPasswordPage.getMessage());
Assert.assertEquals("Error!", resetPasswordPage.getMessage());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ public class LoginPasswordResetPage extends Page {
@FindBy(css = "input[type=\"submit\"]")
private WebElement submitButton;

@FindBy(css = ".feedback > p > strong")
private WebElement emailErrorMessage;

public void changePassword(String username, String email) {
usernameInput.sendKeys(username);
emailInput.sendKeys(email);
Expand All @@ -46,11 +49,15 @@ public void changePassword(String username, String email) {
}

public boolean isCurrent() {
return driver.getTitle().equals("Reset password");
return driver.getTitle().equals("Forgot Your Password?");
}

public void open() {
throw new UnsupportedOperationException();
}

public String getMessage() {
return emailErrorMessage != null ? emailErrorMessage.getText() : null;
}

}