keycloak-aplcache

Merge pull request #232 from stianst/master KEYCLOAK-286

2/21/2014 12:28:39 PM

Details

diff --git a/forms/common-themes/src/main/resources/theme/login/base/login.ftl b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
index 037dc49..4fd6f63 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
@@ -8,7 +8,7 @@
         <form id="kc-form-login" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
             <div class="${properties.kcFormGroupClass!}">
                 <div class="${properties.kcLabelWrapperClass!}">
-                    <label for="username" class="${properties.kcLabelClass!}">${rb.username}</label>
+                    <label for="username" class="${properties.kcLabelClass!}">${rb.usernameOrEmail}</label>
                 </div>
 
                 <div class="${properties.kcInputWrapperClass!}">
@@ -33,7 +33,7 @@
                             <span>${rb.noAccount} <a href="${url.registrationUrl}">${rb.register}</a></span>
                         </#if>
                         <#if realm.resetPasswordAllowed>
-                            <span>${rb.loginForgot} <a href="${url.loginUsernameReminderUrl}">${rb.username}</a> or <a href="${url.loginPasswordResetUrl}">${rb.password}</a>?</span>
+                            <span>${rb.loginForgot} <a href="${url.loginPasswordResetUrl}">${rb.password}</a>?</span>
                         </#if>
                     </div>
                 </div>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl
index a2042c2..aca7d76 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl
@@ -8,10 +8,10 @@
         <form id="kc-reset-password-form" class="${properties.kcFormClass!}" action="${url.loginPasswordResetUrl}" method="post">
             <div class="${properties.kcFormGroupClass!}">
                 <div class="${properties.kcLabelWrapperClass!}">
-                    <label for="email" class="${properties.kcLabelClass!}">${rb.email}</label>
+                    <label for="username" class="${properties.kcLabelClass!}">${rb.usernameOrEmail}</label>
                 </div>
                 <div class="${properties.kcInputWrapperClass!}">
-                    <input type="text" id="email" name="email" class="${properties.kcInputClass!}" />
+                    <input type="text" id="username" name="username" class="${properties.kcInputClass!}" />
                 </div>
             </div>
 
diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
index 6dbef81..79c8e7b 100644
--- a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
+++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
@@ -10,12 +10,13 @@ alreadyHaveAccount=Already have an account?
 poweredByKeycloak=Powered by Keycloak
 
 username=Username
+usernameOrEmail=Username or email
 fullName=Full name
 firstName=First name
 lastName=Last name
 email=Email
 password=Password
-passwordConfirm=Confirmation
+passwordConfirm=Confirm password
 passwordNew=New Password
 passwordNewConfirm=New Password confirmation
 cancel=Cancel
@@ -97,11 +98,7 @@ emailSent=You should receive an email shortly with further instructions.
 emailSendError=Failed to send email, please try again later
 emailError=Invalid email.
 emailErrorInfo=Please, fill in the fields again.
-emailInstruction=Enter your email address and we will send you instructions on how to create a new password.
-
-emailUsernameForgotHeader=Forgot Your Username?
-emailUsernameInstruction=Enter your email address and we will send you an email with your username.
-emailUsernameSent=You should receive an email shortly with your username.
+emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password.
 
 accountUpdated=Your account has been updated
 accountPasswordUpdated=Your password has been updated
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/login/patternfly/resources/css/login.css b/forms/common-themes/src/main/resources/theme/login/patternfly/resources/css/login.css
index e7ece07..5de3439 100644
--- a/forms/common-themes/src/main/resources/theme/login/patternfly/resources/css/login.css
+++ b/forms/common-themes/src/main/resources/theme/login/patternfly/resources/css/login.css
@@ -24,6 +24,7 @@
 #kc-login {
     float: right;
     margin-left: 10px;
+    margin-bottom: 10px;
 }
 
 
diff --git a/forms/common-themes/src/main/resources/theme/login/patternfly/theme.properties b/forms/common-themes/src/main/resources/theme/login/patternfly/theme.properties
index 64be559..4b46ae1 100644
--- a/forms/common-themes/src/main/resources/theme/login/patternfly/theme.properties
+++ b/forms/common-themes/src/main/resources/theme/login/patternfly/theme.properties
@@ -15,10 +15,10 @@ kcFormAreaClass=col-sm-7 col-md-6 col-lg-5 login
 kcFormClass=form-horizontal
 kcFormGroupClass=form-group
 kcLabelClass=control-label
-kcLabelWrapperClass=col-sm-2 col-md-2
+kcLabelWrapperClass=col-sm-4 col-md-4 col-lg-3
 kcInputClass=form-control
-kcInputWrapperClass=col-sm-10 col-md-10
-kcFormOptionsClass=col-xs-8 col-sm-offset-2 col-sm-5 col-md-offset-2 col-md-5
-kcFormButtonsClass=col-xs-4 col-sm-5 col-md-5 submit
+kcInputWrapperClass=col-sm-8 col-md-8 col-lg-9
+kcFormOptionsClass=col-sm-offset-4 col-sm-4 col-md-offset-4 col-md-4 col-lg-offset-3 col-lg-5
+kcFormButtonsClass=col-sm-4 col-md-4 col-lg-4 submit
 
 kcInfoAreaClass=col-sm-5 col-md-6 col-lg-7 details
\ No newline at end of file
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java b/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java
index c693be2..3cce80c 100644
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginForms.java
@@ -18,8 +18,6 @@ public interface LoginForms {
 
     public Response createPasswordReset();
 
-    public Response createUsernameReminder();
-
     public Response createLoginTotp();
 
     public Response createRegistration();
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
index ad96e38..a57e8a3 100644
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
@@ -23,8 +23,6 @@ public class Templates {
                 return "login-reset-password.ftl";
             case LOGIN_UPDATE_PASSWORD:
                 return "login-update-password.ftl";
-            case LOGIN_USERNAME_REMINDER:
-                return "login-username-reminder.ftl";
             case REGISTER:
                 return "register.ftl";
             case ERROR:
diff --git a/services/src/main/java/org/keycloak/services/email/EmailSender.java b/services/src/main/java/org/keycloak/services/email/EmailSender.java
index 1226590..aec3d57 100755
--- a/services/src/main/java/org/keycloak/services/email/EmailSender.java
+++ b/services/src/main/java/org/keycloak/services/email/EmailSender.java
@@ -145,16 +145,6 @@ public class EmailSender {
         send(user.getEmail(), "Reset password link", sb.toString());
     }
 
-    public void sendUsernameReminder(UserModel user) throws EmailException {
-        StringBuilder sb = getHeader(user);
-
-        sb.append("The username for your Keycloak account is ").append(user.getLoginName()).append(".\n");
-
-        addFooter(sb);
-
-        send(user.getEmail(), "Username reminder", sb.toString());
-    }
-
     private StringBuilder getHeader(UserModel user) {
         StringBuilder sb = new StringBuilder();
 
diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
index b38df73..2d6268c 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -237,7 +237,7 @@ public class RequiredActionsService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response sendPasswordReset(final MultivaluedMap<String, String> formData) {
-        String email = formData.getFirst("email");
+        String username = formData.getFirst("username");
 
         String scopeParam = uriInfo.getQueryParameters().getFirst("scope");
         String state = uriInfo.getQueryParameters().getFirst("state");
@@ -254,65 +254,30 @@ public class RequiredActionsService {
                     "Login requester not enabled.");
         }
 
-        UserModel user = realm.getUserByEmail(email);
-        if (user == null) {
-            return Flows.forms(realm, request, uriInfo).setError("emailError").createPasswordReset();
+        UserModel user = realm.getUser(username);
+        if (user == null && username.contains("@")) {
+            user = realm.getUserByEmail(username);
         }
 
-        Set<RequiredAction> requiredActions = new HashSet<RequiredAction>(user.getRequiredActions());
-        requiredActions.add(RequiredAction.UPDATE_PASSWORD);
-
-        AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
-        accessCode.setRequiredActions(requiredActions);
-        accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
-
-        try {
-            new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo);
-        } catch (EmailException e) {
-            logger.error("Failed to send password reset email", e);
-            return Flows.forms(realm, request, uriInfo).setError("emailSendError").createErrorPage();
-        }
-
-        return Flows.forms(realm, request, uriInfo).setSuccess("emailSent").createPasswordReset();
-    }
-
-
-    @Path("username-reminder")
-    @GET
-    public Response usernameReminder() {
-        return Flows.forms(realm, request, uriInfo).createUsernameReminder();
-    }
-
-    @Path("username-reminder")
-    @POST
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response sendUsernameReminder(final MultivaluedMap<String, String> formData) {
-        String email = formData.getFirst("email");
-        String clientId = uriInfo.getQueryParameters().getFirst("client_id");
-
-        UserModel client = realm.getUser(clientId);
-        if (client == null) {
-            return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).forwardToSecurityFailure(
-                    "Unknown login requester.");
-        }
-        if (!client.isEnabled()) {
-            return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).forwardToSecurityFailure(
-                    "Login requester not enabled.");
-        }
-
-        UserModel user = realm.getUserByEmail(email);
         if (user == null) {
-            return Flows.forms(realm, request, uriInfo).setError("emailError").createUsernameReminder();
-        }
-
-        try {
-            new EmailSender(realm.getSmtpConfig()).sendUsernameReminder(user);
-        } catch (EmailException e) {
-            logger.error("Failed to send username reminder email", e);
-            return Flows.forms(realm, request, uriInfo).setError("emailSendError").createErrorPage();
+            logger.warn("Failed to send password reset email: user not found");
+        } else {
+            Set<RequiredAction> requiredActions = new HashSet<RequiredAction>(user.getRequiredActions());
+            requiredActions.add(RequiredAction.UPDATE_PASSWORD);
+
+            AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
+            accessCode.setRequiredActions(requiredActions);
+            accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
+
+            try {
+                new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo);
+            } catch (EmailException e) {
+                logger.error("Failed to send password reset email", e);
+                return Flows.forms(realm, request, uriInfo).setError("emailSendError").createErrorPage();
+            }
         }
 
-        return Flows.forms(realm, request, uriInfo).setSuccess("emailUsernameSent").createLogin();
+        return Flows.forms(realm, request, uriInfo).setSuccess("emailSent").createPasswordReset();
     }
 
     private AccessCodeEntry getAccessCodeEntry(RequiredAction requiredAction) {
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 9c82014..cf86534 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -226,6 +226,9 @@ public class TokenService {
 
         String username = formData.getFirst("username");
         UserModel user = realm.getUser(username);
+        if (user == null && username.contains("@")) {
+            user = realm.getUserByEmail(username);
+        }
 
         if (user == null){
             return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index 23750c1..61a312e 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -25,6 +25,11 @@ import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
@@ -40,7 +45,20 @@ import org.openqa.selenium.WebDriver;
 public class LoginTest {
 
     @ClassRule
-    public static KeycloakRule keycloakRule = new KeycloakRule();
+    public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            UserModel user = appRealm.addUser("login-test");
+            user.setEmail("login@test.com");
+            user.setEnabled(true);
+
+            UserCredentialModel creds = new UserCredentialModel();
+            creds.setType(CredentialRepresentation.PASSWORD);
+            creds.setValue("password");
+
+            appRealm.updateCredential(user, creds);
+        }
+    });
 
     @Rule
     public WebRule webRule = new WebRule(this);
@@ -48,7 +66,6 @@ public class LoginTest {
     @WebResource
     protected OAuthClient oauth;
 
-
     @WebResource
     protected WebDriver driver;
 
@@ -61,7 +78,7 @@ public class LoginTest {
     @Test
     public void loginInvalidPassword() {
         loginPage.open();
-        loginPage.login("test-user@localhost", "invalid");
+        loginPage.login("login-test", "invalid");
 
         loginPage.assertCurrent();
 
@@ -81,13 +98,22 @@ public class LoginTest {
     @Test
     public void loginSuccess() {
         loginPage.open();
-        loginPage.login("test-user@localhost", "password");
+        loginPage.login("login-test", "password");
         
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
         Assert.assertNotNull(oauth.getCurrentQuery().get("code"));
     }
 
     @Test
+    public void loginWithEmailSuccess() {
+        loginPage.open();
+        loginPage.login("login@test.com", "password");
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get("code"));
+    }
+
+    @Test
     public void loginCancel() {
         loginPage.open();
         loginPage.cancel();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 2febe44..96f5428 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -27,6 +27,9 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.AppPage;
@@ -50,7 +53,20 @@ import java.io.IOException;
 public class ResetPasswordTest {
 
     @ClassRule
-    public static KeycloakRule keycloakRule = new KeycloakRule();
+    public static KeycloakRule keycloakRule = new KeycloakRule((new KeycloakRule.KeycloakSetup() {
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            UserModel user = appRealm.addUser("login-test");
+            user.setEmail("login@test.com");
+            user.setEnabled(true);
+
+            UserCredentialModel creds = new UserCredentialModel();
+            creds.setType(CredentialRepresentation.PASSWORD);
+            creds.setValue("password");
+
+            appRealm.updateCredential(user, creds);
+        }
+    }));
 
     @Rule
     public WebRule webRule = new WebRule(this);
@@ -83,7 +99,44 @@ public class ResetPasswordTest {
 
         resetPasswordPage.assertCurrent();
 
-        resetPasswordPage.changePassword("test-user@localhost");
+        resetPasswordPage.changePassword("login-test");
+
+        resetPasswordPage.assertCurrent();
+
+        Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
+
+        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+
+        MimeMessage message = greenMail.getReceivedMessages()[0];
+
+        String body = (String) message.getContent();
+        String changePasswordUrl = body.split("\n")[3];
+
+        driver.navigate().to(changePasswordUrl.trim());
+
+        updatePasswordPage.assertCurrent();
+
+        updatePasswordPage.changePassword("resetPassword", "resetPassword");
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        oauth.openLogout();
+
+        loginPage.open();
+
+        loginPage.login("login-test", "resetPassword");
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+    }
+
+    @Test
+    public void resetPasswordByEmail() throws IOException, MessagingException {
+        loginPage.open();
+        loginPage.resetPassword();
+
+        resetPasswordPage.assertCurrent();
+
+        resetPasswordPage.changePassword("login@test.com");
 
         resetPasswordPage.assertCurrent();
 
@@ -100,7 +153,7 @@ public class ResetPasswordTest {
 
         updatePasswordPage.assertCurrent();
 
-        updatePasswordPage.changePassword("new-password", "new-password");
+        updatePasswordPage.changePassword("resetPassword", "resetPassword");
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
@@ -108,13 +161,13 @@ public class ResetPasswordTest {
 
         loginPage.open();
 
-        loginPage.login("test-user@localhost", "new-password");
+        loginPage.login("login@test.com", "resetPassword");
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
     }
 
     @Test
-    public void resetPasswordWrongEmail() throws IOException, MessagingException {
+    public void resetPasswordWrongEmail() throws IOException, MessagingException, InterruptedException {
         loginPage.open();
         loginPage.resetPassword();
 
@@ -124,7 +177,11 @@ public class ResetPasswordTest {
 
         resetPasswordPage.assertCurrent();
 
-        Assert.assertEquals("Invalid email.", resetPasswordPage.getErrorMessage());
+        Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
+
+        Thread.sleep(1000);
+
+        Assert.assertEquals(0, greenMail.getReceivedMessages().length);
     }
 
     @Test
@@ -141,7 +198,7 @@ public class ResetPasswordTest {
 
         resetPasswordPage.assertCurrent();
 
-        resetPasswordPage.changePassword("test-user@localhost");
+        resetPasswordPage.changePassword("login-test");
 
         resetPasswordPage.assertCurrent();
 
@@ -162,7 +219,7 @@ public class ResetPasswordTest {
 
         Assert.assertEquals("Invalid password: minimum length 8", resetPasswordPage.getErrorMessage());
 
-        updatePasswordPage.changePassword("new-password", "new-password");
+        updatePasswordPage.changePassword("resetPasswordWithPasswordPolicy", "resetPasswordWithPasswordPolicy");
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
@@ -170,7 +227,7 @@ public class ResetPasswordTest {
 
         loginPage.open();
 
-        loginPage.login("test-user@localhost", "new-password");
+        loginPage.login("login-test", "resetPasswordWithPasswordPolicy");
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java
index 758bd9f..8817c9c 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java
@@ -29,8 +29,8 @@ import org.openqa.selenium.support.FindBy;
  */
 public class LoginPasswordResetPage extends AbstractPage {
 
-    @FindBy(id = "email")
-    private WebElement emailInput;
+    @FindBy(id = "username")
+    private WebElement usernameInput;
 
     @FindBy(css = "input[type=\"submit\"]")
     private WebElement submitButton;
@@ -41,8 +41,8 @@ public class LoginPasswordResetPage extends AbstractPage {
     @FindBy(className = "feedback-error")
     private WebElement emailErrorMessage;
 
-    public void changePassword(String email) {
-        emailInput.sendKeys(email);
+    public void changePassword(String username) {
+        usernameInput.sendKeys(username);
 
         submitButton.click();
     }