keycloak-uncached

KEYCLOAK-2806

12/8/2016 7:28:22 PM

Details

diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
index e871313..9cb090b 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
@@ -98,13 +98,32 @@ public interface UserResource {
     @Deprecated
     public void resetPasswordEmail(@QueryParam("client_id") String clientId);
 
+    /**
+     * Sends an email to the user with a link within it.  If they click on the link they will be asked to perform some actions
+     * i.e. reset password, update profile, etc.
+     *
+     *
+     * @param actions
+     */
     @PUT
     @Path("execute-actions-email")
     public void executeActionsEmail(List<String> actions);
 
+    /**
+     * Sends an email to the user with a link within it.  If they click on the link they will be asked to perform some actions
+     * i.e. reset password, update profile, etc.
+     *
+     * If redirectUri is not null, then you must specify a client id.  This will set the URI you want the flow to link
+     * to after the email link is clicked and actions completed.  If both parameters are null, then no page is linked to
+     * at the end of the flow.
+     *
+     * @param clientId
+     * @param redirectUri
+     * @param actions
+     */
     @PUT
     @Path("execute-actions-email")
-    public void executeActionsEmail(@QueryParam("client_id") String clientId, List<String> actions);
+    public void executeActionsEmail(@QueryParam("client_id") String clientId, @QueryParam("redirect_uri") String redirectUri, List<String> actions);
 
     @PUT
     @Path("send-verify-email")
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 b3f2638..04dfada 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -77,6 +77,7 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 public class AuthenticationManager {
+    public static final String SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS= "SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS";
     public static final String END_AFTER_REQUIRED_ACTIONS = "END_AFTER_REQUIRED_ACTIONS";
 
     // userSession note with authTime (time when authentication flow including requiredActions was finished)
@@ -469,9 +470,17 @@ public class AuthenticationManager {
 
     public static Response finishedRequiredActions(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession, ClientConnection clientConnection, HttpRequest request, UriInfo uriInfo, EventBuilder event) {
         if (clientSession.getNote(END_AFTER_REQUIRED_ACTIONS) != null) {
-            Response response = session.getProvider(LoginFormsProvider.class)
-                    .setAttribute("skipLink", true)
-                    .setSuccess(Messages.ACCOUNT_UPDATED)
+            LoginFormsProvider infoPage = session.getProvider(LoginFormsProvider.class)
+                    .setSuccess(Messages.ACCOUNT_UPDATED);
+            if (clientSession.getNote(SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS) != null) {
+                if (clientSession.getRedirectUri() != null) {
+                    infoPage.setAttribute("pageRedirectUri", clientSession.getRedirectUri());
+                }
+
+            } else {
+                infoPage.setAttribute("skipLink", true);
+            }
+            Response response = infoPage
                     .createInfoPage();
             session.sessions().removeUserSession(session.getContext().getRealm(), userSession);
             return response;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 7a8964f..c866ac9 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -836,8 +836,9 @@ public class UsersResource {
      * Send a update account email to the user
      *
      * An email contains a link the user can click to perform a set of required actions.
-     * The redirectUri and clientId parameters are optional. The default for the
-     * redirect is the account client.
+     * The redirectUri and clientId parameters are optional. If no redirect is given, then there will
+     * be no link back to click after actions have completed.  Redirect uri must be a valid uri for the
+     * particular clientId.
      *
      * @param id User is
      * @param redirectUri Redirect uri
@@ -867,6 +868,10 @@ public class UsersResource {
         for (String action : actions) {
             clientSession.addRequiredAction(action);
         }
+        if (redirectUri != null) {
+            clientSession.setNote(AuthenticationManager.SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS, "true");
+
+        }
         ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
         accessCode.setAction(ClientSessionModel.Action.EXECUTE_ACTIONS.name());
 
@@ -933,15 +938,13 @@ public class UsersResource {
                 ErrorResponse.error(clientId + " not enabled", Response.Status.BAD_REQUEST));
         }
 
-        String redirect;
+        String redirect = null;
         if (redirectUri != null) {
             redirect = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client);
             if (redirect == null) {
                 throw new WebApplicationException(
                     ErrorResponse.error("Invalid redirect uri.", Response.Status.BAD_REQUEST));
             }
-        } else {
-            redirect = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString();
         }
 
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AbstractAdminTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AbstractAdminTest.java
index b09209d..bf98b98 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AbstractAdminTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AbstractAdminTest.java
@@ -83,7 +83,6 @@ public abstract class AbstractAdminTest extends TestRealmKeycloakTest  {
     }
 
     // old testsuite expects this realm to be removed at the end of the test
-    // not sure if it really matters
     @After
     public void after() {
         for (RealmRepresentation r : adminClient.realms().findAll()) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java
index 5b4777b..e443191 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java
@@ -33,6 +33,7 @@ import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.Constants;
 import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.ErrorRepresentation;
 import org.keycloak.representations.idm.FederatedIdentityRepresentation;
@@ -501,7 +502,7 @@ public class UserTest extends AbstractAdminTest {
             userRep.setEnabled(true);
             updateUser(user, userRep);
 
-            user.executeActionsEmail("invalidClientId", actions);
+            user.executeActionsEmail("invalidClientId", "invalidUri", actions);
             fail("Expected failure");
         } catch (ClientErrorException e) {
             assertEquals(400, e.getResponse().getStatus());
@@ -523,7 +524,7 @@ public class UserTest extends AbstractAdminTest {
         UserResource user = realm.users().get(id);
         List<String> actions = new LinkedList<>();
         actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
-        user.executeActionsEmail("account", actions);
+        user.executeActionsEmail(actions);
         assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
 
         Assert.assertEquals(1, greenMail.getReceivedMessages().length);
@@ -545,6 +546,71 @@ public class UserTest extends AbstractAdminTest {
         assertEquals("We're sorry...", driver.getTitle());
     }
 
+    @Test
+    public void sendResetPasswordEmailWithRedirect() throws IOException, MessagingException {
+
+        UserRepresentation userRep = new UserRepresentation();
+        userRep.setEnabled(true);
+        userRep.setUsername("user1");
+        userRep.setEmail("user1@test.com");
+
+        String id = createUser(userRep);
+
+        UserResource user = realm.users().get(id);
+
+        ClientRepresentation client = new ClientRepresentation();
+        client.setClientId("myclient");
+        client.setRedirectUris(new LinkedList<>());
+        client.getRedirectUris().add("http://myclient.com/*");
+        client.setName("myclient");
+        client.setEnabled(true);
+        Response response = realm.clients().create(client);
+        String createdId = ApiUtil.getCreatedId(response);
+        assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(createdId), client, ResourceType.CLIENT);
+
+
+        List<String> actions = new LinkedList<>();
+        actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
+
+        try {
+            // test that an invalid redirect uri is rejected.
+            user.executeActionsEmail("myclient", "http://unregistered-uri.com/", actions);
+            fail("Expected failure");
+        } catch (ClientErrorException e) {
+            assertEquals(400, e.getResponse().getStatus());
+
+            ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class);
+            Assert.assertEquals("Invalid redirect uri.", error.getErrorMessage());
+        }
+
+
+        user.executeActionsEmail("myclient", "http://myclient.com/home.html", actions);
+        assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
+
+        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+
+        MimeMessage message = greenMail.getReceivedMessages()[0];
+
+        String link = MailUtils.getPasswordResetEmailLink(message);
+
+        driver.navigate().to(link);
+
+        assertTrue(passwordUpdatePage.isCurrent());
+
+        passwordUpdatePage.changePassword("new-pass", "new-pass");
+
+        assertEquals("Your account has been updated.", driver.getTitle());
+
+        String pageSource = driver.getPageSource();
+
+        // check to make sure the back link is set.
+        Assert.assertTrue(pageSource.contains("http://myclient.com/home.html"));
+
+        driver.navigate().to(link);
+
+        assertEquals("We're sorry...", driver.getTitle());
+    }
+
 
     @Test
     public void sendVerifyEmail() throws IOException, MessagingException {
diff --git a/themes/src/main/resources/theme/base/login/info.ftl b/themes/src/main/resources/theme/base/login/info.ftl
index ca50401..cb228d2 100755
--- a/themes/src/main/resources/theme/base/login/info.ftl
+++ b/themes/src/main/resources/theme/base/login/info.ftl
@@ -9,7 +9,9 @@
         <p class="instruction">${message.summary}</p>
         <#if skipLink??>
         <#else>
-            <#if client.baseUrl??>
+            <#if pageRedirectUri??>
+                <p><a href="${pageRedirectUri}">${msg("backToApplication")}</a></p>
+            <#elseif client.baseUrl??>
                 <p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p>
             </#if>
         </#if>