Merge pull request #4366 from hmlnarik/KEYCLOAK-4694-null

KEYCLOAK-4694
This commit is contained in:
Bill Burke 2017-08-02 19:47:34 -04:00 committed by GitHub
commit 3b5ca2bac0
21 changed files with 297 additions and 22 deletions

View file

@ -57,6 +57,8 @@ public interface Constants {
String KEY = "key";
String SKIP_LINK = "skipLink";
String TEMPLATE_ATTR_ACTION_URI = "actionUri";
String TEMPLATE_ATTR_REQUIRED_ACTIONS = "requiredActions";
// Prefix for user attributes used in various "context"data maps
String USER_ATTRIBUTES_PREFIX = "user.attributes.";

View file

@ -22,14 +22,20 @@ import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.authentication.actiontoken.*;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.*;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionModel;
import java.util.Objects;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
/**
*
@ -64,6 +70,21 @@ public class ExecuteActionsActionTokenHandler extends AbstractActionTokenHander<
@Override
public Response handleToken(ExecuteActionsActionToken token, ActionTokenContext<ExecuteActionsActionToken> tokenContext) {
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
final UriInfo uriInfo = tokenContext.getUriInfo();
final RealmModel realm = tokenContext.getRealm();
final KeycloakSession session = tokenContext.getSession();
if (tokenContext.isAuthenticationSessionFresh()) {
// Update the authentication session in the token
token.setAuthenticationSessionId(authSession.getId());
UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo));
String confirmUri = builder.build(realm.getName()).toString();
return session.getProvider(LoginFormsProvider.class)
.setSuccess(Messages.CONFIRM_EXECUTION_OF_ACTIONS)
.setAttribute(Constants.TEMPLATE_ATTR_ACTION_URI, confirmUri)
.setAttribute(Constants.TEMPLATE_ATTR_REQUIRED_ACTIONS, token.getRequiredActions())
.createInfoPage();
}
String redirectUri = RedirectUtils.verifyRedirectUri(tokenContext.getUriInfo(), token.getRedirectUri(),
tokenContext.getRealm(), authSession.getClient());

View file

@ -30,6 +30,7 @@ public class IdpVerifyAccountLinkActionToken extends DefaultActionToken {
private static final String JSON_FIELD_IDENTITY_PROVIDER_USERNAME = "idpu";
private static final String JSON_FIELD_IDENTITY_PROVIDER_ALIAS = "idpa";
private static final String JSON_FIELD_ORIGINAL_AUTHENTICATION_SESSION_ID = "oasid";
@JsonProperty(value = JSON_FIELD_IDENTITY_PROVIDER_USERNAME)
private String identityProviderUsername;
@ -37,6 +38,9 @@ public class IdpVerifyAccountLinkActionToken extends DefaultActionToken {
@JsonProperty(value = JSON_FIELD_IDENTITY_PROVIDER_ALIAS)
private String identityProviderAlias;
@JsonProperty(value = JSON_FIELD_ORIGINAL_AUTHENTICATION_SESSION_ID)
private String originalAuthenticationSessionId;
public IdpVerifyAccountLinkActionToken(String userId, int absoluteExpirationInSecs, String authenticationSessionId,
String identityProviderUsername, String identityProviderAlias) {
super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, authenticationSessionId);
@ -62,4 +66,12 @@ public class IdpVerifyAccountLinkActionToken extends DefaultActionToken {
public void setIdentityProviderAlias(String identityProviderAlias) {
this.identityProviderAlias = identityProviderAlias;
}
public String getOriginalAuthenticationSessionId() {
return originalAuthenticationSessionId;
}
public void setOriginalAuthenticationSessionId(String originalAuthenticationSessionId) {
this.originalAuthenticationSessionId = originalAuthenticationSessionId;
}
}

View file

@ -24,13 +24,18 @@ import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAut
import org.keycloak.events.*;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.AuthenticationSessionProvider;
import java.util.Collections;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
/**
* Action token handler for verification of e-mail address.
@ -58,6 +63,9 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
public Response handleToken(IdpVerifyAccountLinkActionToken token, ActionTokenContext<IdpVerifyAccountLinkActionToken> tokenContext) {
UserModel user = tokenContext.getAuthenticationSession().getAuthenticatedUser();
EventBuilder event = tokenContext.getEvent();
final UriInfo uriInfo = tokenContext.getUriInfo();
final RealmModel realm = tokenContext.getRealm();
final KeycloakSession session = tokenContext.getSession();
event.event(EventType.IDENTITY_PROVIDER_LINK_ACCOUNT)
.detail(Details.EMAIL, user.getEmail())
@ -65,16 +73,28 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
.detail(Details.IDENTITY_PROVIDER_USERNAME, token.getIdentityProviderUsername())
.success();
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
if (tokenContext.isAuthenticationSessionFresh()) {
token.setOriginalAuthenticationSessionId(token.getAuthenticationSessionId());
token.setAuthenticationSessionId(authSession.getId());
UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo));
String confirmUri = builder.build(realm.getName()).toString();
return session.getProvider(LoginFormsProvider.class)
.setSuccess(Messages.CONFIRM_ACCOUNT_LINKING, token.getIdentityProviderUsername(), token.getIdentityProviderAlias())
.setAttribute(Constants.TEMPLATE_ATTR_ACTION_URI, confirmUri)
.createInfoPage();
}
// verify user email as we know it is valid as this entry point would never have gotten here.
user.setEmailVerified(true);
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
if (tokenContext.isAuthenticationSessionFresh()) {
AuthenticationSessionManager asm = new AuthenticationSessionManager(tokenContext.getSession());
asm.removeAuthenticationSession(tokenContext.getRealm(), authSession, true);
if (token.getOriginalAuthenticationSessionId() != null) {
AuthenticationSessionManager asm = new AuthenticationSessionManager(session);
asm.removeAuthenticationSession(realm, authSession, true);
AuthenticationSessionProvider authSessProvider = tokenContext.getSession().authenticationSessions();
authSession = authSessProvider.getAuthenticationSession(tokenContext.getRealm(), token.getAuthenticationSessionId());
AuthenticationSessionProvider authSessProvider = session.authenticationSessions();
authSession = authSessProvider.getAuthenticationSession(realm, token.getOriginalAuthenticationSessionId());
if (authSession != null) {
authSession.setAuthNote(IdpEmailVerificationAuthenticator.VERIFY_ACCOUNT_IDP_USERNAME, token.getIdentityProviderUsername());
@ -85,7 +105,7 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
);
}
return tokenContext.getSession().getProvider(LoginFormsProvider.class)
return session.getProvider(LoginFormsProvider.class)
.setSuccess(Messages.IDENTITY_PROVIDER_LINK_SUCCESS, token.getIdentityProviderAlias(), token.getIdentityProviderUsername())
.setAttribute(Constants.SKIP_LINK, true)
.createInfoPage();

View file

@ -29,10 +29,14 @@ public class VerifyEmailActionToken extends DefaultActionToken {
public static final String TOKEN_TYPE = "verify-email";
private static final String JSON_FIELD_EMAIL = "eml";
private static final String JSON_FIELD_ORIGINAL_AUTHENTICATION_SESSION_ID = "oasid";
@JsonProperty(value = JSON_FIELD_EMAIL)
private String email;
@JsonProperty(value = JSON_FIELD_ORIGINAL_AUTHENTICATION_SESSION_ID)
private String originalAuthenticationSessionId;
public VerifyEmailActionToken(String userId, int absoluteExpirationInSecs, String authenticationSessionId, String email) {
super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, authenticationSessionId);
this.email = email;
@ -48,4 +52,12 @@ public class VerifyEmailActionToken extends DefaultActionToken {
public void setEmail(String email) {
this.email = email;
}
public String getOriginalAuthenticationSessionId() {
return originalAuthenticationSessionId;
}
public void setOriginalAuthenticationSessionId(String originalAuthenticationSessionId) {
this.originalAuthenticationSessionId = originalAuthenticationSessionId;
}
}

View file

@ -21,14 +21,20 @@ import org.keycloak.TokenVerifier.Predicate;
import org.keycloak.authentication.actiontoken.*;
import org.keycloak.events.*;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionModel;
import java.util.Objects;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
/**
* Action token handler for verification of e-mail address.
@ -57,13 +63,29 @@ public class VerifyEmailActionTokenHandler extends AbstractActionTokenHander<Ver
}
@Override
public Response handleToken(VerifyEmailActionToken token, ActionTokenContext<VerifyEmailActionToken> tokenContext) {
public Response handleToken(VerifyEmailActionToken token, ActionTokenContext<VerifyEmailActionToken> tokenContext) {
UserModel user = tokenContext.getAuthenticationSession().getAuthenticatedUser();
EventBuilder event = tokenContext.getEvent();
event.event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail());
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
final UriInfo uriInfo = tokenContext.getUriInfo();
final RealmModel realm = tokenContext.getRealm();
final KeycloakSession session = tokenContext.getSession();
if (tokenContext.isAuthenticationSessionFresh()) {
// Update the authentication session in the token
token.setOriginalAuthenticationSessionId(token.getAuthenticationSessionId());
token.setAuthenticationSessionId(authSession.getId());
UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo));
String confirmUri = builder.build(realm.getName()).toString();
return session.getProvider(LoginFormsProvider.class)
.setSuccess(Messages.CONFIRM_EMAIL_ADDRESS_VERIFICATION, user.getEmail())
.setAttribute(Constants.TEMPLATE_ATTR_ACTION_URI, confirmUri)
.createInfoPage();
}
// verify user email as we know it is valid as this entry point would never have gotten here.
user.setEmailVerified(true);
@ -72,9 +94,10 @@ public class VerifyEmailActionTokenHandler extends AbstractActionTokenHander<Ver
event.success();
if (tokenContext.isAuthenticationSessionFresh()) {
if (token.getOriginalAuthenticationSessionId() != null) {
AuthenticationSessionManager asm = new AuthenticationSessionManager(tokenContext.getSession());
asm.removeAuthenticationSession(tokenContext.getRealm(), authSession, true);
return tokenContext.getSession().getProvider(LoginFormsProvider.class)
.setSuccess(Messages.EMAIL_VERIFIED)
.createInfoPage();
@ -82,8 +105,8 @@ public class VerifyEmailActionTokenHandler extends AbstractActionTokenHander<Ver
tokenContext.setEvent(event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN));
String nextAction = AuthenticationManager.nextRequiredAction(tokenContext.getSession(), authSession, tokenContext.getClientConnection(), tokenContext.getRequest(), tokenContext.getUriInfo(), event);
return AuthenticationManager.redirectToRequiredActions(tokenContext.getSession(), tokenContext.getRealm(), authSession, tokenContext.getUriInfo(), nextAction);
String nextAction = AuthenticationManager.nextRequiredAction(session, authSession, tokenContext.getClientConnection(), tokenContext.getRequest(), uriInfo, event);
return AuthenticationManager.redirectToRequiredActions(session, realm, authSession, uriInfo, nextAction);
}
}

View file

@ -97,7 +97,7 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
@Override
public void sendPasswordReset(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
Map<String, Object> attributes = new HashMap<String, Object>(this.attributes);
attributes.put("user", new ProfileBean(user));
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
@ -112,7 +112,7 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
setRealm(session.getContext().getRealm());
setUser(user);
Map<String, Object> attributes = new HashMap<String, Object>();
Map<String, Object> attributes = new HashMap<String, Object>(this.attributes);
attributes.put("user", new ProfileBean(user));
attributes.put("realmName", realm.getName());
@ -122,7 +122,7 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
@Override
public void sendConfirmIdentityBrokerLink(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
Map<String, Object> attributes = new HashMap<String, Object>(this.attributes);
attributes.put("user", new ProfileBean(user));
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
@ -142,7 +142,7 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
@Override
public void sendExecuteActions(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
Map<String, Object> attributes = new HashMap<String, Object>(this.attributes);
attributes.put("user", new ProfileBean(user));
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
@ -155,7 +155,7 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
@Override
public void sendVerifyEmail(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
Map<String, Object> attributes = new HashMap<String, Object>(this.attributes);
attributes.put("user", new ProfileBean(user));
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);

View file

@ -449,7 +449,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
public Response createIdpLinkEmailPage() {
BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
String idpAlias = brokerContext.getIdpConfig().getAlias();
idpAlias = ObjectUtil.capitalize(idpAlias);;
idpAlias = ObjectUtil.capitalize(idpAlias);
setMessage(MessageType.WARNING, Messages.LINK_IDP, idpAlias);
return createResponse(LoginFormsPages.LOGIN_IDP_LINK_EMAIL);

View file

@ -160,6 +160,12 @@ public class Messages {
public static final String IDENTITY_PROVIDER_LINK_SUCCESS = "identityProviderLinkSuccess";
public static final String CONFIRM_ACCOUNT_LINKING = "confirmAccountLinking";
public static final String CONFIRM_EMAIL_ADDRESS_VERIFICATION = "confirmEmailAddressVerification";
public static final String CONFIRM_EXECUTION_OF_ACTIONS = "confirmExecutionOfActions";
public static final String STALE_CODE = "staleCodeMessage";
public static final String STALE_CODE_ACCOUNT = "staleCodeAccountMessage";

View file

@ -704,6 +704,7 @@ public class UserResource {
String link = builder.build(realm.getName()).toString();
this.session.getProvider(EmailTemplateProvider.class)
.setAttribute(Constants.TEMPLATE_ATTR_REQUIRED_ACTIONS, token.getRequiredActions())
.setRealm(realm)
.setUser(user)
.sendExecuteActions(link, TimeUnit.SECONDS.toMinutes(lifespan));

View file

@ -0,0 +1,51 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.pages;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/**
*
* @author hmlnarik
*/
public class ProceedPage extends AbstractPage {
@FindBy(className = "instruction")
private WebElement infoMessage;
@FindBy(linkText = "» Click here to proceed")
private WebElement proceedLink;
public String getInfo() {
return infoMessage.getText();
}
public boolean isCurrent() {
return driver.getPageSource().contains("kc-info-message") && proceedLink.isDisplayed();
}
@Override
public void open() {
throw new UnsupportedOperationException();
}
public void clickProceedLink() {
proceedLink.click();
}
}

View file

@ -37,6 +37,7 @@ import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.auth.page.AuthRealm;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.ProceedPage;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.InfoPage;
import org.keycloak.testsuite.pages.LoginPage;
@ -81,6 +82,9 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
@Page
protected InfoPage infoPage;
@Page
protected ProceedPage proceedPage;
@Page
protected ErrorPage errorPage;
@ -330,6 +334,8 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
driver.navigate().to(verificationUrl2.trim());
proceedPage.assertCurrent();
proceedPage.clickProceedLink();
infoPage.assertCurrent();
assertEquals("Your email address has been verified.", infoPage.getInfo());
}
@ -355,6 +361,9 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
driver.manage().deleteAllCookies();
driver.navigate().to(verificationUrl.trim());
proceedPage.assertCurrent();
proceedPage.clickProceedLink();
infoPage.assertCurrent();
events.expectRequiredAction(EventType.VERIFY_EMAIL)
.user(testUserId)

View file

@ -55,6 +55,7 @@ import org.keycloak.testsuite.page.LoginPasswordUpdatePage;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.InfoPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.ProceedPage;
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.ClientBuilder;
@ -107,6 +108,9 @@ public class UserTest extends AbstractAdminTest {
@Page
protected InfoPage infoPage;
@Page
protected ProceedPage proceedPage;
@Page
protected ErrorPage errorPage;
@ -628,6 +632,9 @@ public class UserTest extends AbstractAdminTest {
driver.navigate().to(link);
proceedPage.assertCurrent();
Assert.assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password"));
proceedPage.clickProceedLink();
passwordUpdatePage.assertCurrent();
passwordUpdatePage.changePassword("new-pass", "new-pass");
@ -664,6 +671,9 @@ public class UserTest extends AbstractAdminTest {
driver.navigate().to(link);
proceedPage.assertCurrent();
Assert.assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password"));
proceedPage.clickProceedLink();
passwordUpdatePage.assertCurrent();
passwordUpdatePage.changePassword("new-pass" + i, "new-pass" + i);
@ -706,6 +716,9 @@ public class UserTest extends AbstractAdminTest {
driver.navigate().to(link);
proceedPage.assertCurrent();
Assert.assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password"));
proceedPage.clickProceedLink();
passwordUpdatePage.assertCurrent();
passwordUpdatePage.changePassword("new-pass" + i, "new-pass" + i);
@ -744,6 +757,9 @@ public class UserTest extends AbstractAdminTest {
driver.navigate().to(link);
proceedPage.assertCurrent();
Assert.assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password"));
proceedPage.clickProceedLink();
passwordUpdatePage.assertCurrent();
driver.manage().deleteAllCookies();
@ -751,6 +767,9 @@ public class UserTest extends AbstractAdminTest {
driver.navigate().to(link);
proceedPage.assertCurrent();
Assert.assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password"));
proceedPage.clickProceedLink();
passwordUpdatePage.assertCurrent();
passwordUpdatePage.changePassword("new-pass", "new-pass");
@ -850,6 +869,9 @@ public class UserTest extends AbstractAdminTest {
driver.navigate().to(link);
proceedPage.assertCurrent();
Assert.assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password"));
proceedPage.clickProceedLink();
passwordUpdatePage.assertCurrent();
passwordUpdatePage.changePassword("new-pass", "new-pass");
@ -910,6 +932,9 @@ public class UserTest extends AbstractAdminTest {
driver.navigate().to(link);
proceedPage.assertCurrent();
Assert.assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password"));
proceedPage.clickProceedLink();
passwordUpdatePage.assertCurrent();
passwordUpdatePage.changePassword("new-pass", "new-pass");
@ -981,11 +1006,17 @@ public class UserTest extends AbstractAdminTest {
driver.navigate().to(link);
proceedPage.assertCurrent();
Assert.assertThat(proceedPage.getInfo(), Matchers.containsString("Verify Email"));
proceedPage.clickProceedLink();
Assert.assertEquals("Your account has been updated.", infoPage.getInfo());
driver.navigate().to("about:blank");
driver.navigate().to(link); // It should be possible to use the same action token multiple times
proceedPage.assertCurrent();
Assert.assertThat(proceedPage.getInfo(), Matchers.containsString("Verify Email"));
proceedPage.clickProceedLink();
Assert.assertEquals("Your account has been updated.", infoPage.getInfo());
}

View file

@ -37,6 +37,7 @@ import org.keycloak.testsuite.pages.InfoPage;
import org.keycloak.testsuite.pages.LoginExpiredPage;
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
import org.keycloak.testsuite.pages.ProceedPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
@ -52,6 +53,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hamcrest.Matchers;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
@ -345,6 +347,9 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
// Go to the same link again
driver.navigate().to(linkFromMail.trim());
proceedPage.assertCurrent();
Assert.assertThat(proceedPage.getInfo(), Matchers.containsString("Confirm linking the account"));
proceedPage.clickProceedLink();
infoPage.assertCurrent();
Assert.assertThat(infoPage.getInfo(), startsWith("You successfully verified your email. Please go back to your original browser and continue there with the login."));
}
@ -379,10 +384,14 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
WebDriver driver2 = webRule2.getDriver();
InfoPage infoPage2 = webRule2.getPage(InfoPage.class);
ProceedPage proceedPage2 = webRule2.getPage(ProceedPage.class);
driver2.navigate().to(linkFromMail.trim());
// authenticated, but not redirected to app. Just seeing info page.
proceedPage2.assertCurrent();
Assert.assertThat(proceedPage2.getInfo(), Matchers.containsString("Confirm linking the account"));
proceedPage2.clickProceedLink();
infoPage2.assertCurrent();
Assert.assertThat(infoPage2.getInfo(), startsWith("You successfully verified your email. Please go back to your original browser and continue there with the login."));
} finally {

View file

@ -110,6 +110,9 @@ public abstract class AbstractIdentityProviderTest {
@WebResource
protected InfoPage infoPage;
@WebResource
protected ProceedPage proceedPage;
protected KeycloakSession session;
protected int logoutTimeOffset = 0;

View file

@ -0,0 +1,51 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.pages;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/**
*
* @author hmlnarik
*/
public class ProceedPage extends AbstractPage {
@FindBy(className = "instruction")
private WebElement infoMessage;
@FindBy(linkText = "» Click here to proceed")
private WebElement proceedLink;
public String getInfo() {
return infoMessage.getText();
}
public boolean isCurrent() {
return driver.getPageSource().contains("kc-info-message") && proceedLink.isDisplayed();
}
@Override
public void open() {
throw new UnsupportedOperationException();
}
public void clickProceedLink() {
proceedLink.click();
}
}

View file

@ -1,5 +1,8 @@
<#assign requiredActionsText>
<#if requiredActions??><#list requiredActions><b><#items as reqActionItem>${msg("requiredAction.${reqActionItem}")}<#sep>, </#items></b></#list><#else></#if>
</#assign>
<html>
<body>
${msg("executeActionsBodyHtml",link, linkExpiration, realmName)}
${msg("executeActionsBodyHtml",link, linkExpiration, realmName, requiredActionsText)}
</body>
</html>

View file

@ -11,8 +11,8 @@ passwordResetSubject=Reset password
passwordResetBody=Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your credentials, just ignore this message and nothing will be changed.
passwordResetBodyHtml=<p>Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.</p><p><a href="{0}">Link to reset credentials</a></p><p>This link will expire within {1} minutes.</p><p>If you don''t want to reset your credentials, just ignore this message and nothing will be changed.</p>
executeActionsSubject=Update Your Account
executeActionsBody=Your administrator has just requested that you update your {2} account. Click on the link below to start this process.\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you are unaware that your admin has requested this, just ignore this message and nothing will be changed.
executeActionsBodyHtml=<p>Your administrator has just requested that you update your {2} account. Click on the link below to start this process.</p><p><a href="{0}">Link to account update</a></p><p>This link will expire within {1} minutes.</p><p>If you are unaware that your admin has requested this, just ignore this message and nothing will be changed.</p>
executeActionsBody=Your administrator has just requested that you update your {2} account by performing the following action(s): {3}. Click on the link below to start this process.\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you are unaware that your admin has requested this, just ignore this message and nothing will be changed.
executeActionsBodyHtml=<p>Your administrator has just requested that you update your {2} account by performing the following action(s): {3}. Click on the link below to start this process.</p><p><a href="{0}">Link to account update</a></p><p>This link will expire within {1} minutes.</p><p>If you are unaware that your admin has requested this, just ignore this message and nothing will be changed.</p>
eventLoginErrorSubject=Login error
eventLoginErrorBody=A failed login attempt was detected to your account on {0} from {1}. If this was not you, please contact an admin.
eventLoginErrorBodyHtml=<p>A failed login attempt was detected to your account on {0} from {1}. If this was not you, please contact an admin.</p>
@ -25,3 +25,9 @@ eventUpdatePasswordBodyHtml=<p>Your password was changed on {0} from {1}. If thi
eventUpdateTotpSubject=Update TOTP
eventUpdateTotpBody=TOTP was updated for your account on {0} from {1}. If this was not you, please contact an admin.
eventUpdateTotpBodyHtml=<p>TOTP was updated for your account on {0} from {1}. If this was not you, please contact an admin.</p>
requiredAction.CONFIGURE_TOTP=Configure OTP
requiredAction.terms_and_conditions=Terms and Conditions
requiredAction.UPDATE_PASSWORD=Update Password
requiredAction.UPDATE_PROFILE=Update Profile
requiredAction.VERIFY_EMAIL=Verify Email

View file

@ -1 +1,4 @@
${msg("executeActionsBody",link, linkExpiration, realmName)}
<#assign requiredActionsText>
<#if requiredActions??><#list requiredActions><#items as reqActionItem>${msg("requiredAction.${reqActionItem}")}<#sep>, </#items></#list><#else></#if>
</#assign>
${msg("executeActionsBody",link, linkExpiration, realmName, requiredActionsText)}

View file

@ -6,11 +6,13 @@
${message.summary}
<#elseif section = "form">
<div id="kc-info-message">
<p class="instruction">${message.summary}</p>
<p class="instruction">${message.summary}<#if requiredActions??><#list requiredActions>: <b><#items as reqActionItem>${msg("requiredAction.${reqActionItem}")}<#sep>, </#items></b></#list><#else></#if></p>
<#if skipLink??>
<#else>
<#if pageRedirectUri??>
<p><a href="${pageRedirectUri}">${msg("backToApplication")}</a></p>
<#elseif actionUri??>
<p><a href="${actionUri}">${msg("proceedWithAction")}</a></p>
<#elseif client.baseUrl??>
<p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p>
</#if>

View file

@ -219,6 +219,9 @@ identityProviderNotUniqueMessage=Realm supports multiple identity providers. Cou
emailVerifiedMessage=Your email address has been verified.
staleEmailVerificationLink=The link you clicked is a old stale link and is no longer valid. Maybe you have already verified your email?
identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
confirmAccountLinking=Confirm linking the account {0} of identity provider {1} with your account.
confirmEmailAddressVerification=Confirm validity of e-mail address {0}.
confirmExecutionOfActions=Perform the following action(s)
locale_ca=Catal\u00E0
locale_de=Deutsch
@ -242,5 +245,12 @@ invalidParameterMessage=Invalid parameter\: {0}
alreadyLoggedIn=You are already logged in.
differentUserAuthenticated=You are already authenticated as different user ''{0}'' in this session. Please logout first.
brokerLinkingSessionExpired=Requested broker account linking, but current session is no longer valid.
proceedWithAction=&raquo; Click here to proceed
requiredAction.CONFIGURE_TOTP=Configure OTP
requiredAction.terms_and_conditions=Terms and Conditions
requiredAction.UPDATE_PASSWORD=Update Password
requiredAction.UPDATE_PROFILE=Update Profile
requiredAction.VERIFY_EMAIL=Verify Email
p3pPolicy=CP="This is not a P3P policy!"