keycloak-uncached
Changes
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java 21(+20 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AbstractAdminTest.java 1(+0 -1)
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>