commit
d3e3990d77
8 changed files with 113 additions and 13 deletions
|
@ -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")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -486,6 +486,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
|
||||
@Path("revoke-grant")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response processRevokeGrant(final MultivaluedMap<String, String> formData) {
|
||||
if (auth == null) {
|
||||
return login("applications");
|
||||
|
|
|
@ -986,6 +986,7 @@ public class AuthenticationManagementResource {
|
|||
@Path("config")
|
||||
@POST
|
||||
@NoCache
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response createAuthenticatorConfig(AuthenticatorConfigRepresentation rep) {
|
||||
auth.requireManage();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue