keycloak-aplcache
Changes
forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java 13(+7 -6)
forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java 5(+3 -2)
services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java 12(+11 -1)
services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java 25(+17 -8)
services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java 2(+1 -1)
services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java 51(+32 -19)
services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java 397(+397 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java 653(+66 -587)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java 519(+519 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java 145(+145 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLFirstBrokerLoginTest.java 40(+40 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java 2(+1 -1)
Details
diff --git a/common/src/main/java/org/keycloak/common/util/ObjectUtil.java b/common/src/main/java/org/keycloak/common/util/ObjectUtil.java
index 1ade852..bec8acf 100644
--- a/common/src/main/java/org/keycloak/common/util/ObjectUtil.java
+++ b/common/src/main/java/org/keycloak/common/util/ObjectUtil.java
@@ -24,4 +24,8 @@ public class ObjectUtil {
return str1.equals(str2);
}
+
+ public static String capitalize(String str) {
+ return str.substring(0, 1).toUpperCase() + str.substring(1);
+ }
}
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
index 549bc84..78d9a4b 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
@@ -818,45 +818,21 @@ $ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificat
</section>
<section>
- <title>Adding Keycloak server in Domain Mode</title>
+ <title>Keycloak server in Domain Mode</title>
<para>
In domain mode, you start the server with the "domain" command instead of the "standalone" command. In this case, the Keycloak subsystem is
defined in domain/configuration/domain.xml instead of standalone/configuration.standalone.xml. Inside domain.xml, you will see more than one
- profile. A Keycloak subsystem can be defined in zero or more of those profiles.
+ profile. The Keycloak subsystem is defined for all initial profiles.
</para>
<para>
- To enable Keycloak for a server profile edit domain/configuration/domain.xml. To the <literal>extensions</literal>
- element add the Keycloak extension:
-<programlisting><![CDATA[
-<extensions>
- ...
- <extension module="org.keycloak.keycloak-subsystem"/>
-</extensions>
-]]></programlisting>
- Then you need to add the server to the required server profiles. By default WildFly starts two servers
- in the main-server-group which uses the full profile. To add Keycloak for this profile add the Keycloak
- subsystem to the <literal>profile</literal> element with <literal>name</literal> full:
-<programlisting><![CDATA[
-<profile name="full">
- ...
- <subsystem xmlns="urn:jboss:domain:keycloak:1.0">
- <auth-server name="main-auth-server">
- <enabled>true</enabled>
- <web-context>auth</web-context>
- </auth-server>
- </subsystem>
-]]></programlisting>
+ THe server is also added to server profiles. By default two servers are started
+ in the main-server-group which uses the full profile.
</para>
<para>
- To configure the server copy <literal>standalone/configuration/keycloak-server.json</literal> to
- <literal>domain/servers/<SERVER NAME>/configuration</literal>. The configuration should be identical
+ You need to make sure <literal>domain/servers/<SERVER NAME>/configuration</literal> is identical
for all servers in a group.
</para>
<para>
- Follow the <link linkend='clustering'>Clustering</link> section of the documentation to configure Keycloak
- for clustering. In domain mode it doesn't make much sense to not configure Keycloak in cluster mode.
- </para>
- <para>
To deploy custom providers and themes you should deploys these as modules and make sure the modules are
available to all servers in the group. See <link linkend='providers'>Providers</link> and
<link linkend='themes'>Themes</link> sections for more information on how to do this.
diff --git a/events/api/src/main/java/org/keycloak/events/Errors.java b/events/api/src/main/java/org/keycloak/events/Errors.java
index 34c5979..b0cbc6a 100755
--- a/events/api/src/main/java/org/keycloak/events/Errors.java
+++ b/events/api/src/main/java/org/keycloak/events/Errors.java
@@ -44,8 +44,7 @@ public interface Errors {
String NOT_ALLOWED = "not_allowed";
- String FEDERATED_IDENTITY_EMAIL_EXISTS = "federated_identity_email_exists";
- String FEDERATED_IDENTITY_USERNAME_EXISTS = "federated_identity_username_exists";
+ String FEDERATED_IDENTITY_EXISTS = "federated_identity_account_exists";
String SSL_REQUIRED = "ssl_required";
String USER_SESSION_NOT_FOUND = "user_session_not_found";
diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java
index 5cffe78..b75728b 100755
--- a/events/api/src/main/java/org/keycloak/events/EventType.java
+++ b/events/api/src/main/java/org/keycloak/events/EventType.java
@@ -48,6 +48,8 @@ public enum EventType {
SEND_VERIFY_EMAIL_ERROR(true),
SEND_RESET_PASSWORD(true),
SEND_RESET_PASSWORD_ERROR(true),
+ SEND_IDENTITY_PROVIDER_LINK(true),
+ SEND_IDENTITY_PROVIDER_LINK_ERROR(true),
RESET_PASSWORD(true),
RESET_PASSWORD_ERROR(true),
@@ -66,8 +68,6 @@ public enum EventType {
IDENTITY_PROVIDER_RESPONSE_ERROR(false),
IDENTITY_PROVIDER_RETRIEVE_TOKEN(false),
IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR(false),
- IDENTITY_PROVIDER_ACCCOUNT_LINKING(false),
- IDENTITY_PROVIDER_ACCCOUNT_LINKING_ERROR(false),
IMPERSONATE(true),
CUSTOM_REQUIRED_ACTION(true),
CUSTOM_REQUIRED_ACTION_ERROR(true),
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl
index 02923d9..f77eb38 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl
@@ -12,8 +12,8 @@
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
- <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="updateProfile">${msg("confirmLinkIdpReviewProfile")}</button>
- <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="linkAccount">${msg("confirmLinkIdpContinue", idpAlias)}</button>
+ <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" id="updateProfile" value="updateProfile">${msg("confirmLinkIdpReviewProfile")}</button>
+ <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" id="linkAccount" value="linkAccount">${msg("confirmLinkIdpContinue", idpAlias)}</button>
</div>
</form>
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl
index ab3e83e..0ba0686 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl
@@ -5,10 +5,10 @@
<#elseif section = "header">
${msg("emailLinkIdpTitle", idpAlias)}
<#elseif section = "form">
- <p class="instruction">
+ <p id="instruction1" class="instruction">
${msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.name)}
</p>
- <p class="instruction">
+ <p id="instruction2" class="instruction">
${msg("emailLinkIdp2")} <a href="${url.firstBrokerLoginUrl}">${msg("doClickHere")}</a> ${msg("emailLinkIdp3")}
</p>
</#if>
diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
index 3a04fbf..25b0166 100755
--- a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
+++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
@@ -21,6 +21,7 @@ import javax.mail.internet.MimeMultipart;
import org.jboss.logging.Logger;
import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.common.util.ObjectUtil;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.email.freemarker.beans.EventBean;
@@ -89,7 +90,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
- String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
+ String realmName = ObjectUtil.capitalize(realm.getName());
attributes.put("realmName", realmName);
send("passwordResetSubject", "password-reset.ftl", attributes);
@@ -102,12 +103,12 @@ public class FreeMarkerEmailProvider implements EmailProvider {
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
- String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
+ String realmName = ObjectUtil.capitalize(realm.getName());
attributes.put("realmName", realmName);
BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
String idpAlias = brokerContext.getIdpConfig().getAlias();
- idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
+ idpAlias = ObjectUtil.capitalize(idpAlias);
attributes.put("identityProviderContext", brokerContext);
attributes.put("identityProviderAlias", idpAlias);
@@ -123,7 +124,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
- String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
+ String realmName = ObjectUtil.capitalize(realm.getName());
attributes.put("realmName", realmName);
send("executeActionsSubject", "executeActions.ftl", attributes);
@@ -137,7 +138,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
- String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
+ String realmName = ObjectUtil.capitalize(realm.getName());
attributes.put("realmName", realmName);
send("emailVerificationSubject", "email-verification.ftl", attributes);
@@ -253,7 +254,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
private String toCamelCase(EventType event){
StringBuilder sb = new StringBuilder("event");
for(String s : event.name().toString().toLowerCase().split("_")){
- sb.append(s.substring(0,1).toUpperCase()).append(s.substring(1));
+ sb.append(ObjectUtil.capitalize(s));
}
return sb.toString();
}
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index ef8cc74..d1b4df9 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -22,6 +22,7 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext;
import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.common.util.ObjectUtil;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
@@ -288,7 +289,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
case LOGIN_IDP_LINK_EMAIL:
BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
String idpAlias = brokerContext.getIdpConfig().getAlias();
- idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
+ idpAlias = ObjectUtil.capitalize(idpAlias);
attributes.put("brokerContext", brokerContext);
attributes.put("idpAlias", idpAlias);
@@ -470,7 +471,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 = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
+ idpAlias = ObjectUtil.capitalize(idpAlias);;
setMessage(MessageType.WARNING, Messages.LINK_IDP, idpAlias);
return createResponse(LoginFormsPages.LOGIN_IDP_LINK_EMAIL);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
index a874f5e..98e960c 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
@@ -25,8 +25,10 @@ public class DefaultAuthenticationFlows {
public static final String CLIENT_AUTHENTICATION_FLOW = "clients";
public static final String FIRST_BROKER_LOGIN_FLOW = "first broker login";
+ public static final String FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW = "Handle Existing Account";
public static final String IDP_REVIEW_PROFILE_CONFIG_ALIAS = "review profile config";
+ public static final String IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS = "create unique user config";
public static void addFlows(RealmModel realm) {
if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm);
@@ -347,7 +349,7 @@ public class DefaultAuthenticationFlows {
AuthenticatorConfigModel createUserIfUniqueConfig = new AuthenticatorConfigModel();
- createUserIfUniqueConfig.setAlias("create unique user config");
+ createUserIfUniqueConfig.setAlias(IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS);
config = new HashMap<>();
config.put("require.password.update.after.registration", "false");
createUserIfUniqueConfig.setConfig(config);
@@ -366,7 +368,7 @@ public class DefaultAuthenticationFlows {
AuthenticationFlowModel linkExistingAccountFlow = new AuthenticationFlowModel();
linkExistingAccountFlow.setTopLevel(false);
linkExistingAccountFlow.setBuiltIn(true);
- linkExistingAccountFlow.setAlias("Handle Existing Account");
+ linkExistingAccountFlow.setAlias(FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW);
linkExistingAccountFlow.setDescription("Handle what to do if there is existing account with same email/username like authenticated identity provider");
linkExistingAccountFlow.setProviderId("basic-flow");
linkExistingAccountFlow = realm.addAuthenticationFlow(linkExistingAccountFlow);
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
index ffb2300..b4ee957 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
@@ -10,11 +10,12 @@ import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo;
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
-import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.messages.Messages;
/**
@@ -78,6 +79,15 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
.setError(Messages.FEDERATED_IDENTITY_EXISTS, duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue())
.createErrorPage();
context.challenge(challengeResponse);
+
+ if (context.getExecution().isRequired()) {
+ context.getEvent()
+ .user(duplication.getExistingUserId())
+ .detail("existing_" + duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue())
+ .removeDetail(Details.AUTH_METHOD)
+ .removeDetail(Details.AUTH_TYPE)
+ .error(Errors.FEDERATED_IDENTITY_EXISTS);
+ }
}
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
index d6bf10f..ae28d3e 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
@@ -14,6 +14,10 @@ import org.keycloak.authentication.authenticators.broker.util.SerializedBrokered
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
@@ -52,6 +56,15 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
String link = UriBuilder.fromUri(context.getActionUrl())
.queryParam(Constants.KEY, clientSession.getNote(Constants.VERIFY_EMAIL_KEY))
.build().toString();
+
+ EventBuilder event = context.getEvent().clone().event(EventType.SEND_IDENTITY_PROVIDER_LINK)
+ .user(existingUser)
+ .detail(Details.USERNAME, existingUser.getUsername())
+ .detail(Details.EMAIL, existingUser.getEmail())
+ .detail(Details.CODE_ID, clientSession.getId())
+ .removeDetail(Details.AUTH_METHOD)
+ .removeDetail(Details.AUTH_TYPE);
+
long expiration = TimeUnit.SECONDS.toMinutes(context.getRealm().getAccessCodeLifespanUserAction());
try {
@@ -60,15 +73,11 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
.setUser(existingUser)
.setAttribute(EmailProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
.sendConfirmIdentityBrokerLink(link, expiration);
-// event.clone().event(EventType.SEND_RESET_PASSWORD)
-// .user(user)
-// .detail(Details.USERNAME, username)
-// .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, context.getClientSession().getId()).success();
+
+ event.success();
} catch (EmailException e) {
-// event.clone().event(EventType.SEND_RESET_PASSWORD)
-// .detail(Details.USERNAME, username)
-// .user(user)
-// .error(Errors.EMAIL_SEND_FAILED);
+ event.error(Errors.EMAIL_SEND_FAILED);
+
logger.error("Failed to send email to confirm identity broker linking", e);
Response challenge = context.form()
.setError(Messages.EMAIL_SENT_ERROR)
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java
index b293d1b..5e732d8 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java
@@ -37,7 +37,7 @@ public class IdpUsernamePasswordForm extends UsernamePasswordForm {
// Restore formData for the case of error
setupForm(context, formData, existingUser);
- return validatePassword(context, formData);
+ return validatePassword(context, existingUser, formData);
}
protected LoginFormsProvider setupForm(AuthenticationFlowContext context, MultivaluedMap<String, String> formData, UserModel existingUser) {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
index 294d220..0506f21 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
@@ -71,12 +71,16 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
context.failureChallenge(AuthenticationFlowError.INVALID_USER, challengeResponse);
return true;
}
+ return false;
+ }
+
+ public boolean enabledUser(AuthenticationFlowContext context, UserModel user) {
if (!user.isEnabled()) {
context.getEvent().user(user);
context.getEvent().error(Errors.USER_DISABLED);
Response challengeResponse = disabledUser(context);
context.failureChallenge(AuthenticationFlowError.USER_DISABLED, challengeResponse);
- return true;
+ return false;
}
if (context.getRealm().isBruteForceProtected()) {
if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) {
@@ -84,13 +88,13 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED);
Response challengeResponse = temporarilyDisabledUser(context);
context.failureChallenge(AuthenticationFlowError.USER_TEMPORARILY_DISABLED, challengeResponse);
- return true;
+ return false;
}
}
- return false;
+ return true;
}
- public boolean validateUser(AuthenticationFlowContext context, MultivaluedMap<String, String> inputData) {
+ public boolean validateUserAndPassword(AuthenticationFlowContext context, MultivaluedMap<String, String> inputData) {
String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME);
if (username == null) {
context.getEvent().error(Errors.USER_NOT_FOUND);
@@ -117,7 +121,18 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
return false;
}
- if (invalidUser(context, user)) return false;
+ if (invalidUser(context, user)){
+ return false;
+ }
+
+ if (!validatePassword(context, user, inputData)){
+ return false;
+ }
+
+ if(!enabledUser(context, user)){
+ return false;
+ }
+
String rememberMe = inputData.getFirst("rememberMe");
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
if (remember) {
@@ -130,29 +145,27 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
return true;
}
- public boolean validatePassword(AuthenticationFlowContext context, MultivaluedMap<String, String> inputData) {
+ public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap<String, String> inputData) {
List<UserCredentialModel> credentials = new LinkedList<>();
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
if (password == null || password.isEmpty()) {
- if (context.getUser() != null) {
- context.getEvent().user(context.getUser());
- }
- context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
- Response challengeResponse = invalidCredentials(context);
- context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
- context.clearUser();
+ invalidPassword(context, user);
return false;
}
credentials.add(UserCredentialModel.password(password));
- boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
+ boolean valid = context.getSession().users().validCredentials(context.getRealm(), user, credentials);
if (!valid) {
- context.getEvent().user(context.getUser());
- context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
- Response challengeResponse = invalidCredentials(context);
- context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
- context.clearUser();
+ invalidPassword(context, user);
return false;
}
return true;
}
+
+ private void invalidPassword(AuthenticationFlowContext context, UserModel user) {
+ context.getEvent().user(user);
+ context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
+ Response challengeResponse = invalidCredentials(context);
+ context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
+ context.clearUser();
+ }
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
index 70d9fd9..7463638 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
@@ -38,7 +38,7 @@ public class UsernamePasswordForm extends AbstractUsernameFormAuthenticator impl
}
protected boolean validateForm(AuthenticationFlowContext context, MultivaluedMap<String, String> formData) {
- return validateUser(context, formData) && validatePassword(context, formData);
+ return validateUserAndPassword(context, formData);
}
@Override
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 71f849b..2bebec3 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -724,7 +724,9 @@ public class AccountService extends AbstractSecuredLocalService {
logger.debugv("Social provider {0} removed successfully from user {1}", providerId, user.getUsername());
event.event(EventType.REMOVE_FEDERATED_IDENTITY).client(auth.getClient()).user(auth.getUser())
- .detail(Details.USERNAME, link.getUserId() + "@" + link.getIdentityProvider())
+ .detail(Details.USERNAME, auth.getUser().getUsername())
+ .detail(Details.IDENTITY_PROVIDER, link.getIdentityProvider())
+ .detail(Details.IDENTITY_PROVIDER_USERNAME, link.getUserName())
.success();
setReferrerOnPage();
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index fda37ef..c8784bd 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -368,6 +368,13 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
context.getUsername(), context.getToken());
session.users().addFederatedIdentity(realmModel, federatedUser, federatedIdentityModel);
+ EventBuilder event = this.event.clone().user(federatedUser)
+ .detail(Details.CODE_ID, clientSession.getId())
+ .detail(Details.USERNAME, federatedUser.getUsername())
+ .detail(Details.IDENTITY_PROVIDER, providerId)
+ .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername())
+ .removeDetail("auth_method");
+
String isRegisteredNewUser = clientSession.getNote(AbstractIdpAuthenticator.BROKER_REGISTERED_NEW_USER);
if (Boolean.parseBoolean(isRegisteredNewUser)) {
@@ -388,15 +395,17 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
federatedUser.setEmailVerified(true);
}
- this.event.clone().user(federatedUser).event(EventType.REGISTER)
- .detail(Details.IDENTITY_PROVIDER, providerId)
- .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername())
- .removeDetail("auth_method")
+ event.event(EventType.REGISTER)
+ .detail(Details.REGISTER_METHOD, "broker")
+ .detail(Details.EMAIL, federatedUser.getEmail())
.success();
} else {
LOGGER.debugf("Linked existing keycloak user '%s' with identity provider '%s' . Identity provider username is '%s' .", federatedUser.getUsername(), providerId, context.getUsername());
+ event.event(EventType.FEDERATED_IDENTITY_LINK)
+ .success();
+
updateFederatedIdentity(context, federatedUser);
}
@@ -453,7 +462,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
private Response performAccountLinking(ClientSessionModel clientSession, BrokeredIdentityContext context, FederatedIdentityModel federatedIdentityModel, UserModel federatedUser) {
- this.event.event(EventType.IDENTITY_PROVIDER_ACCCOUNT_LINKING);
+ this.event.event(EventType.FEDERATED_IDENTITY_LINK);
if (federatedUser != null) {
return redirectToErrorPage(Messages.IDENTITY_PROVIDER_ALREADY_LINKED, context.getIdpConfig().getAlias());
@@ -478,7 +487,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, federatedIdentityModel);
context.getIdp().attachUserSession(clientSession.getUserSession(), clientSession, context);
- this.event.success();
+ this.event.user(authenticatedUser)
+ .detail(Details.USERNAME, authenticatedUser.getUsername())
+ .detail(Details.IDENTITY_PROVIDER, federatedIdentityModel.getIdentityProvider())
+ .detail(Details.IDENTITY_PROVIDER_USERNAME, federatedIdentityModel.getUserName())
+ .success();
return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 8f845c0..7f15d2d 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -509,6 +509,10 @@ public class LoginActionsService {
BrokeredIdentityContext brokerContext = serializedCtx.deserialize(session, clientSession);
AuthenticationFlowModel firstBrokerLoginFlow = realm.getAuthenticationFlowById(brokerContext.getIdpConfig().getFirstBrokerLoginFlowId());
+ event.detail(Details.IDENTITY_PROVIDER, brokerContext.getIdpConfig().getAlias())
+ .detail(Details.IDENTITY_PROVIDER_USERNAME, brokerContext.getUsername());
+
+
AuthenticationProcessor processor = new AuthenticationProcessor() {
@Override
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
new file mode 100644
index 0000000..7d780cd
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
@@ -0,0 +1,397 @@
+package org.keycloak.testsuite.broker;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.mail.internet.MimeMessage;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticator;
+import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory;
+import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory;
+import org.keycloak.authentication.authenticators.broker.IdpReviewProfileAuthenticatorFactory;
+import org.keycloak.common.util.ObjectUtil;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorConfigModel;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.pages.IdpConfirmLinkPage;
+import org.keycloak.testsuite.pages.IdpLinkEmailPage;
+import org.keycloak.testsuite.pages.LoginPasswordResetPage;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.openqa.selenium.By;
+import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.WebElement;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProviderTest {
+
+ protected static final String APP_REALM_ID = "realm-with-broker";
+
+ @WebResource
+ protected LoginUpdateProfileEditUsernameAllowedPage updateProfileWithUsernamePage;
+
+ @WebResource
+ protected IdpConfirmLinkPage idpConfirmLinkPage;
+
+ @WebResource
+ protected IdpLinkEmailPage idpLinkEmailPage;
+
+ @WebResource
+ protected LoginPasswordUpdatePage passwordUpdatePage;
+
+
+
+ /**
+ * Tests that if updateProfile is off and CreateUserIfUnique authenticator mandatory, error page will be shown if user with same email already exists
+ */
+ @Test
+ public void testErrorPageWhenDuplicationNotAllowed_updateProfileOff() {
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.REQUIRED);
+ setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
+ }
+
+ }, APP_REALM_ID);
+
+ loginIDP("pedroigor");
+
+ WebElement element = this.driver.findElement(By.className("instruction"));
+
+ assertNotNull(element);
+
+ assertEquals("User with email psilva@redhat.com already exists. Please login to account management to link the account.", element.getText());
+
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+ }
+
+ }, APP_REALM_ID);
+ }
+
+
+ /**
+ * Tests that if updateProfile is on and CreateUserIfUnique authenticator mandatory, error page will be shown if user with same email already exists
+ */
+ @Test
+ public void testErrorPageWhenDuplicationNotAllowed_updateProfileOn() {
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.REQUIRED);
+ setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_ON);
+ }
+
+ }, APP_REALM_ID);
+
+ loginIDP("test-user");
+
+ this.updateProfileWithUsernamePage.assertCurrent();
+ this.updateProfileWithUsernamePage.update("Test", "User", "test-user@redhat.com", "pedroigor");
+
+ WebElement element = this.driver.findElement(By.className("instruction"));
+
+ assertNotNull(element);
+
+ assertEquals("User with username pedroigor already exists. Please login to account management to link the account.", element.getText());
+
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+ }
+
+ }, APP_REALM_ID);
+ }
+
+
+ /**
+ * Test user registers with IdentityProvider and needs to update password when it's required by IdpCreateUserIfUniqueAuthenticator
+ */
+ @Test
+ public void testRegistrationWithPasswordUpdateRequired() {
+ // Require updatePassword after user registered with broker
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ AuthenticatorConfigModel authenticatorConfig = realmWithBroker.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS);
+ authenticatorConfig.getConfig().put(IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "true");
+ realmWithBroker.updateAuthenticatorConfig(authenticatorConfig);
+
+ setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_MISSING);
+ }
+
+ }, APP_REALM_ID);
+
+ loginIDP("pedroigor");
+ this.updateProfileWithUsernamePage.assertCurrent();
+ this.updateProfileWithUsernamePage.update("Test", "User", "some-user@redhat.com", "some-user");
+
+ // Need to update password now
+ this.passwordUpdatePage.assertCurrent();
+ this.passwordUpdatePage.changePassword("password1", "password1");
+
+
+ // assert authenticated
+ assertFederatedUser("some-user", "some-user@redhat.com", "pedroigor");
+
+
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ AuthenticatorConfigModel authenticatorConfig = realmWithBroker.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS);
+ authenticatorConfig.getConfig().put(IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "false");
+ realmWithBroker.updateAuthenticatorConfig(authenticatorConfig);
+ }
+
+ }, APP_REALM_ID);
+ }
+
+
+ /**
+ * Tests that duplication is detected, the confirmation page is displayed, user clicks on "Review profile" and goes back to updateProfile page and resolves duplication
+ * by create new user
+ */
+ @Test
+ public void testFixDuplicationsByReviewProfile() {
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+ loginIDP("pedroigor");
+
+ // There is user with same email. Update profile to use different email
+ this.idpConfirmLinkPage.assertCurrent();
+ Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+ this.idpConfirmLinkPage.clickReviewProfile();
+
+ this.updateProfileWithUsernamePage.assertCurrent();
+ this.updateProfileWithUsernamePage.update("Test", "User", "testing-user@redhat.com", "pedroigor");
+
+ // There is user with same username. Update profile to use different username
+ this.idpConfirmLinkPage.assertCurrent();
+ Assert.assertEquals("User with username pedroigor already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+ this.idpConfirmLinkPage.clickReviewProfile();
+
+ this.updateProfileWithUsernamePage.assertCurrent();
+ this.updateProfileWithUsernamePage.update("Test", "User", "testing-user@redhat.com", "testing-user");
+
+ assertFederatedUser("testing-user", "testing-user@redhat.com", "pedroigor");
+ }
+
+ /**
+ * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by email
+ */
+ @Test
+ public void testLinkAccountByEmailVerification() throws Exception {
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+ loginIDP("pedroigor");
+
+ this.idpConfirmLinkPage.assertCurrent();
+ Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+ this.idpConfirmLinkPage.clickLinkAccount();
+
+ // Confirm linking account by email
+ this.idpLinkEmailPage.assertCurrent();
+ Assert.assertEquals("An email with instructions to link " + ObjectUtil.capitalize(getProviderId()) + " account pedroigor with your " + APP_REALM_ID + " account has been sent to you.", this.idpLinkEmailPage.getMessage());
+
+ Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+ MimeMessage message = greenMail.getReceivedMessages()[0];
+ String linkFromMail = getVerificationEmailLink(message);
+
+ driver.navigate().to(linkFromMail.trim());
+
+ // authenticated and redirected to app. User is linked with identity provider
+ assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
+ }
+
+
+ /**
+ * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication (confirm password on login screen)
+ */
+ @Test
+ public void testLinkAccountByReauthenticationWithPassword() throws Exception {
+ // Remove smtp config. The reauthentication by username+password screen will be automatically used then
+ final Map<String, String> smtpConfig = new HashMap<>();
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
+ smtpConfig.putAll(realmWithBroker.getSmtpConfig());
+ realmWithBroker.setSmtpConfig(Collections.<String, String>emptyMap());
+ }
+
+ }, APP_REALM_ID);
+
+
+ loginIDP("pedroigor");
+
+
+ this.idpConfirmLinkPage.assertCurrent();
+ Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+ this.idpConfirmLinkPage.clickLinkAccount();
+
+ // Login screen shown. Username is prefilled and disabled. Registration link and social buttons are not shown
+ Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+ Assert.assertEquals("pedroigor", this.loginPage.getUsername());
+ Assert.assertFalse(this.loginPage.isUsernameInputEnabled());
+
+ Assert.assertEquals("Authenticate as pedroigor to link your account with " + getProviderId(), this.loginPage.getSuccessMessage());
+
+ try {
+ this.loginPage.findSocialButton(getProviderId());
+ Assert.fail("Not expected to see social button with " + getProviderId());
+ } catch (NoSuchElementException expected) {
+ }
+
+ try {
+ this.loginPage.clickRegister();
+ Assert.fail("Not expected to see register link");
+ } catch (NoSuchElementException expected) {
+ }
+
+ // Use bad password first
+ this.loginPage.login("password1");
+ Assert.assertEquals("Invalid username or password.", this.loginPage.getError());
+
+ // Use correct password now
+ this.loginPage.login("password");
+
+ // authenticated and redirected to app. User is linked with identity provider
+ assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
+
+
+ // Restore smtp config
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ realmWithBroker.setSmtpConfig(smtpConfig);
+ }
+
+ }, APP_REALM_ID);
+ }
+
+
+ /**
+ * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication (confirm password on login screen)
+ * and additionally he goes through "forget password"
+ */
+ @Test
+ public void testLinkAccountByReauthentication_forgetPassword() throws Exception {
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
+ IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.DISABLED);
+
+ setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
+ }
+
+ }, APP_REALM_ID);
+
+ loginIDP("pedroigor");
+
+ this.idpConfirmLinkPage.assertCurrent();
+ Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+ this.idpConfirmLinkPage.clickLinkAccount();
+
+ // Click "Forget password" on login page. Email sent directly because username is known
+ Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+ this.loginPage.resetPassword();
+
+ Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+ Assert.assertEquals("You should receive an email shortly with further instructions.", this.loginPage.getSuccessMessage());
+
+ // Click on link from email
+ Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+ MimeMessage message = greenMail.getReceivedMessages()[0];
+ String linkFromMail = getVerificationEmailLink(message);
+
+ driver.navigate().to(linkFromMail.trim());
+
+ // Need to update password now
+ this.passwordUpdatePage.assertCurrent();
+ this.passwordUpdatePage.changePassword("password", "password");
+
+ // authenticated and redirected to app. User is linked with identity provider
+ assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
+
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
+ IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+
+ }
+
+ }, APP_REALM_ID);
+ }
+
+
+ protected void assertFederatedUser(String expectedUsername, String expectedEmail, String expectedFederatedUsername) {
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+ UserModel federatedUser = getFederatedUser();
+
+ assertNotNull(federatedUser);
+ assertEquals(expectedUsername, federatedUser.getUsername());
+ assertEquals(expectedEmail, federatedUser.getEmail());
+
+ RealmModel realmWithBroker = getRealm();
+ Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realmWithBroker);
+ assertEquals(1, federatedIdentities.size());
+
+ FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
+
+ assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
+ assertEquals(expectedFederatedUsername, federatedIdentityModel.getUserName());
+ }
+
+
+ protected void setExecutionRequirement(RealmModel realmWithBroker, String flowAlias, String authenticatorProvider, AuthenticationExecutionModel.Requirement requirement) {
+ AuthenticationFlowModel flowModel = realmWithBroker.getFlowByAlias(flowAlias);
+ List<AuthenticationExecutionModel> authExecutions = realmWithBroker.getAuthenticationExecutions(flowModel.getId());
+ for (AuthenticationExecutionModel execution : authExecutions) {
+ if (execution.getAuthenticator().equals(authenticatorProvider)) {
+ execution.setRequirement(requirement);
+ realmWithBroker.updateAuthenticatorExecution(execution);
+ return;
+ }
+ }
+
+ throw new IllegalStateException("Execution not found for flow " + flowAlias + " and authenticator " + authenticatorProvider);
+ }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index 8af43b2..5248c38 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -42,6 +42,7 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.services.Urls;
import org.keycloak.testsuite.MailUtil;
import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionStatus;
import org.keycloak.testsuite.pages.AccountFederatedIdentityPage;
import org.keycloak.testsuite.pages.AccountPasswordPage;
@@ -87,7 +88,7 @@ import static org.junit.Assert.fail;
*/
public abstract class AbstractIdentityProviderTest {
- private static final URI BASE_URI = UriBuilder.fromUri("http://localhost:8081/auth").build();
+ protected static final URI BASE_URI = UriBuilder.fromUri("http://localhost:8081/auth").build();
@ClassRule
public static BrokerKeyCloakRule brokerServerRule = new BrokerKeyCloakRule();
@@ -99,10 +100,10 @@ public abstract class AbstractIdentityProviderTest {
protected WebDriver driver;
@WebResource
- private LoginPage loginPage;
+ protected LoginPage loginPage;
@WebResource
- private LoginUpdateProfilePage updateProfilePage;
+ protected LoginUpdateProfilePage updateProfilePage;
@WebResource
@@ -123,7 +124,7 @@ public abstract class AbstractIdentityProviderTest {
@WebResource
protected AccountFederatedIdentityPage accountFederatedIdentityPage;
- private KeycloakSession session;
+ protected KeycloakSession session;
@Before
public void onBefore() {
@@ -140,87 +141,19 @@ public abstract class AbstractIdentityProviderTest {
brokerServerRule.stopSession(this.session, true);
}
- @Test
- public void testSuccessfulAuthentication() {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
-
- UserModel user = assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com", true);
- Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile"));
- }
-
- @Test
- public void testSuccessfulAuthenticationUpdateProfileOnMissing_nothingMissing() {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING);
-
- assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
- }
-
- @Test
- public void testSuccessfulAuthenticationUpdateProfileOnMissing_missingEmail() {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING);
-
- assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", "new@email.com", true);
- }
-
- @Test
- public void testSuccessfulAuthenticationWithoutUpdateProfile() {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
-
- assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
- }
-
- /**
- * Test that verify email action is performed if email is provided and email trust is not enabled for the provider
- *
- * @throws MessagingException
- * @throws IOException
- */
- @Test
- public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled() throws IOException, MessagingException {
- getRealm().setVerifyEmail(true);
- brokerServerRule.stopSession(this.session, true);
- this.session = brokerServerRule.startSession();
-
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- try {
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
- identityProviderModel.setTrustEmail(false);
-
- UserModel federatedUser = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "test-user@localhost", false);
-
- // email is verified now
- assertFalse(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
-
- } finally {
- getRealm().setVerifyEmail(false);
- }
- }
-
- private UserModel assertSuccessfulAuthenticationWithEmailVerification(IdentityProviderModel identityProviderModel, String username, String expectedEmail,
- boolean isProfileUpdateExpected)
- throws IOException, MessagingException {
+ protected UserModel assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel, String username, String expectedEmail, boolean isProfileUpdateExpected) {
authenticateWithIdentityProvider(identityProviderModel, username, isProfileUpdateExpected);
- // verify email is sent
- Assert.assertTrue(verifyEmailPage.isCurrent());
-
- // read email to take verification link from
- Assert.assertEquals(1, greenMail.getReceivedMessages().length);
- MimeMessage message = greenMail.getReceivedMessages()[0];
- String verificationUrl = getVerificationEmailLink(message);
-
- driver.navigate().to(verificationUrl.trim());
-
// authenticated and redirected to app
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+ assertTrue("Bad current URL " + this.driver.getCurrentUrl() + " and page source: " + this.driver.getPageSource(),
+ this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
UserModel federatedUser = getFederatedUser();
assertNotNull(federatedUser);
+ assertNotNull(federatedUser.getCreatedTimestamp());
+ // test that timestamp is current with 10s tollerance
+ Assert.assertTrue((System.currentTimeMillis() - federatedUser.getCreatedTimestamp()) < 10000);
doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail, isProfileUpdateExpected);
@@ -245,166 +178,7 @@ public abstract class AbstractIdentityProviderTest {
return federatedUser;
}
- /**
- * Test for KEYCLOAK-1053 - verify email action is not performed if email is not provided, login is normal, but action stays in set to be performed later
- */
- @Test
- public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() {
- getRealm().setVerifyEmail(true);
- brokerServerRule.stopSession(this.session, true);
- this.session = brokerServerRule.startSession();
-
- try {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
-
- UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null, false);
-
- assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
-
- } finally {
- getRealm().setVerifyEmail(false);
- }
- }
-
- /**
- * Test for KEYCLOAK-1372 - verify email action is not performed if email is provided but email trust is enabled for the provider
- */
- @Test
- public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled_emailTrustEnabled() {
- getRealm().setVerifyEmail(true);
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
- brokerServerRule.stopSession(this.session, true);
- this.session = brokerServerRule.startSession();
-
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- try {
- identityProviderModel.setTrustEmail(true);
-
- UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
-
- assertFalse(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
-
- } finally {
- identityProviderModel.setTrustEmail(false);
- getRealm().setVerifyEmail(false);
- }
- }
-
- /**
- * Test for KEYCLOAK-1372 - verify email action is performed if email is provided and email trust is enabled for the provider, but email is changed on First login update profile page
- *
- * @throws MessagingException
- * @throws IOException
- */
- @Test
- public void testSuccessfulAuthentication_emailTrustEnabled_emailVerifyEnabled_emailUpdatedOnFirstLogin() throws IOException, MessagingException {
- getRealm().setVerifyEmail(true);
- brokerServerRule.stopSession(this.session, true);
- this.session = brokerServerRule.startSession();
-
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- try {
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
- identityProviderModel.setTrustEmail(true);
-
- UserModel user = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "new@email.com", true);
- Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile"));
- } finally {
- identityProviderModel.setTrustEmail(false);
- getRealm().setVerifyEmail(false);
- }
- }
-
- @Test
- public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() {
-
- getRealm().setRegistrationEmailAsUsername(true);
- brokerServerRule.stopSession(this.session, true);
- this.session = brokerServerRule.startSession();
-
- try {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
-
- authenticateWithIdentityProvider(identityProviderModel, "test-user", false);
-
- // authenticated and redirected to app
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
-
- brokerServerRule.stopSession(session, true);
- session = brokerServerRule.startSession();
-
- // check correct user is created with email as username and bound to correct federated identity
- RealmModel realm = getRealm();
-
- UserModel federatedUser = session.users().getUserByUsername("test-user@localhost", realm);
-
- assertNotNull(federatedUser);
-
- assertEquals("test-user@localhost", federatedUser.getUsername());
-
- doAssertFederatedUser(federatedUser, identityProviderModel, "test-user@localhost", false);
-
- Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
-
- assertEquals(1, federatedIdentities.size());
-
- FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
-
- assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
-
- driver.navigate().to("http://localhost:8081/test-app/logout");
- driver.navigate().to("http://localhost:8081/test-app");
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
- } finally {
- getRealm().setRegistrationEmailAsUsername(false);
- }
- }
-
- @Test
- public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided() {
-
- getRealm().setRegistrationEmailAsUsername(true);
- brokerServerRule.stopSession(this.session, true);
- this.session = brokerServerRule.startSession();
-
- try {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
-
- authenticateWithIdentityProvider(identityProviderModel, "test-user-noemail", false);
-
- brokerServerRule.stopSession(session, true);
- session = brokerServerRule.startSession();
- // check correct user is created with username from provider as email is not available
- RealmModel realm = getRealm();
- UserModel federatedUser = getFederatedUser();
- assertNotNull(federatedUser);
-
- doAssertFederatedUserNoEmail(federatedUser);
-
- Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
-
- assertEquals(1, federatedIdentities.size());
-
- FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
-
- assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
- revokeGrant();
-
- driver.navigate().to("http://localhost:8081/test-app/logout");
- driver.navigate().to("http://localhost:8081/test-app");
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
- } finally {
- getRealm().setRegistrationEmailAsUsername(false);
- }
- }
protected void doAssertFederatedUserNoEmail(UserModel federatedUser) {
assertEquals("kc-oidc-idp.test-user-noemail", federatedUser.getUsername());
@@ -413,316 +187,7 @@ public abstract class AbstractIdentityProviderTest {
assertEquals("User", federatedUser.getLastName());
}
- @Test
- public void testDisabled() {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
- identityProviderModel.setEnabled(false);
-
- this.driver.navigate().to("http://localhost:8081/test-app/");
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
- try {
- this.driver.findElement(By.className(getProviderId()));
- fail("Provider [" + getProviderId() + "] not disabled.");
- } catch (NoSuchElementException nsee) {
-
- }
- }
-
- @Test
- public void testProviderOnLoginPage() {
- // Provider button is available on login page
- this.driver.navigate().to("http://localhost:8081/test-app/");
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
- loginPage.findSocialButton(getProviderId());
- }
-
- // TODO: Reenable and adjust to KEYCLOAK-1750 changed behaviour
- // @Test
- public void testUserAlreadyExistsWhenUpdatingProfile() {
- this.driver.navigate().to("http://localhost:8081/test-app/");
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
- // choose the identity provider
- this.loginPage.clickSocial(getProviderId());
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
-
- // log in to identity provider
- this.loginPage.login("test-user", "password");
-
- doAfterProviderAuthentication();
-
- this.updateProfilePage.assertCurrent();
- this.updateProfilePage.update("Test", "User", "psilva@redhat.com");
-
- WebElement element = this.driver.findElement(By.className("kc-feedback-text"));
-
- assertNotNull(element);
-
- assertEquals("Email already exists.", element.getText());
-
- this.updateProfilePage.assertCurrent();
- this.updateProfilePage.update("Test", "User", "test-user@redhat.com");
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
-
- UserModel federatedUser = getFederatedUser();
-
- assertNotNull(federatedUser);
- }
-
- // TODO: Reenable and adjust to KEYCLOAK-1750 changed behaviour
- // @Test
- public void testUserAlreadyExistsWhenNotUpdatingProfile() {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
-
- this.driver.navigate().to("http://localhost:8081/test-app/");
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
- // choose the identity provider
- this.loginPage.clickSocial(getProviderId());
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
-
- // log in to identity provider
- this.loginPage.login("pedroigor", "password");
-
- doAfterProviderAuthentication();
-
- WebElement element = this.driver.findElement(By.className("kc-feedback-text"));
-
- assertNotNull(element);
-
- assertEquals("User with email already exists. Please login to account management to link the account.", element.getText());
- }
-
- @Test
- public void testAccountManagementLinkIdentity() {
- // Login as pedroigor to account management
- accountFederatedIdentityPage.realm("realm-with-broker");
- accountFederatedIdentityPage.open();
- assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
- loginPage.login("pedroigor", "password");
- assertTrue(accountFederatedIdentityPage.isCurrent());
-
- // Link my "pedroigor" identity with "test-user" from brokered Keycloak
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
- accountFederatedIdentityPage.clickAddProvider(identityProviderModel.getAlias());
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
- this.loginPage.login("test-user", "password");
- doAfterProviderAuthentication();
-
- // Assert identity linked in account management
- assertTrue(accountFederatedIdentityPage.isCurrent());
- assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
-
- // Revoke grant in account mgmt
- revokeGrant();
-
- // Logout from account management
- accountFederatedIdentityPage.logout();
- assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
-
- // Assert I am logged immediately to account management due to previously linked "test-user" identity
- loginPage.clickSocial(identityProviderModel.getAlias());
- doAfterProviderAuthentication();
- assertTrue(accountFederatedIdentityPage.isCurrent());
- assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
-
- // Unlink my "test-user"
- accountFederatedIdentityPage.clickRemoveProvider(identityProviderModel.getAlias());
- assertTrue(driver.getPageSource().contains("id=\"add-" + identityProviderModel.getAlias() + "\""));
-
- // Revoke grant in account mgmt
- revokeGrant();
-
- // Logout from account management
- System.out.println("*** logout from account management");
- accountFederatedIdentityPage.logout();
- assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
- // Try to login. Previous link is not valid anymore, so now it should try to register new user
- this.loginPage.clickSocial(identityProviderModel.getAlias());
- this.loginPage.login("test-user", "password");
- doAfterProviderAuthentication();
- this.updateProfilePage.assertCurrent();
- }
-
- @Test(expected = NoSuchElementException.class)
- public void testIdentityProviderNotAllowed() {
- this.driver.navigate().to("http://localhost:8081/test-app/");
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
- driver.findElement(By.className("model-oidc-idp"));
- }
-
- protected void configureClientRetrieveToken(String clientId) {
- RealmModel realm = getRealm();
- RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
- ClientModel client = realm.getClientByClientId(clientId);
- if (!client.hasScope(readTokenRole)) client.addScopeMapping(readTokenRole);
-
- brokerServerRule.stopSession(session, true);
- session = brokerServerRule.startSession();
-
- }
-
- protected void configureUserRetrieveToken(String username) {
- RealmModel realm = getRealm();
- UserModel user = session.users().getUserByUsername(username, realm);
- RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
- if (user != null && !user.hasRole(readTokenRole)) {
- user.grantRole(readTokenRole);
- }
- brokerServerRule.stopSession(session, true);
- session = brokerServerRule.startSession();
-
- }
-
- protected void unconfigureClientRetrieveToken(String clientId) {
- RealmModel realm = getRealm();
- RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
- ClientModel client = realm.getClientByClientId(clientId);
- if (client.hasScope(readTokenRole)) client.deleteScopeMapping(readTokenRole);
-
- brokerServerRule.stopSession(session, true);
- session = brokerServerRule.startSession();
-
- }
-
- protected void unconfigureUserRetrieveToken(String username) {
- RealmModel realm = getRealm();
- UserModel user = session.users().getUserByUsername(username, realm);
- RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
- if (user != null && user.hasRole(readTokenRole)) {
- user.deleteRoleMapping(readTokenRole);
- }
- brokerServerRule.stopSession(session, true);
- session = brokerServerRule.startSession();
-
- }
-
- @Test
- public void testTokenStorageAndRetrievalByApplication() {
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
- identityProviderModel.setStoreToken(true);
-
- authenticateWithIdentityProvider(identityProviderModel, "test-user", true);
-
- UserModel federatedUser = getFederatedUser();
- RealmModel realm = getRealm();
- Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
-
- assertFalse(federatedIdentities.isEmpty());
- assertEquals(1, federatedIdentities.size());
-
- FederatedIdentityModel identityModel = federatedIdentities.iterator().next();
-
- assertNotNull(identityModel.getToken());
-
- UserSessionStatus userSessionStatus = retrieveSessionStatus();
- String accessToken = userSessionStatus.getAccessTokenString();
- URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName());
- final String authHeader = "Bearer " + accessToken;
- ClientRequestFilter authFilter = new ClientRequestFilter() {
- @Override
- public void filter(ClientRequestContext requestContext) throws IOException {
- requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
- }
- };
- Client client = ClientBuilder.newBuilder().register(authFilter).build();
- WebTarget tokenEndpoint = client.target(tokenEndpointUrl);
- Response response = tokenEndpoint.request().get();
- assertEquals(Status.OK.getStatusCode(), response.getStatus());
- assertNotNull(response.readEntity(String.class));
- revokeGrant();
-
-
- driver.navigate().to("http://localhost:8081/test-app/logout");
- String currentUrl = this.driver.getCurrentUrl();
- System.out.println("after logout currentUrl: " + currentUrl);
- assertTrue(currentUrl.startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
- unconfigureUserRetrieveToken(getProviderId() + ".test-user");
- loginIDP("test-user");
- //authenticateWithIdentityProvider(identityProviderModel, "test-user");
- assertEquals("http://localhost:8081/test-app", driver.getCurrentUrl());
-
- userSessionStatus = retrieveSessionStatus();
- accessToken = userSessionStatus.getAccessTokenString();
- final String authHeader2 = "Bearer " + accessToken;
- ClientRequestFilter authFilter2 = new ClientRequestFilter() {
- @Override
- public void filter(ClientRequestContext requestContext) throws IOException {
- requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader2);
- }
- };
- client = ClientBuilder.newBuilder().register(authFilter2).build();
- tokenEndpoint = client.target(tokenEndpointUrl);
- response = tokenEndpoint.request().get();
-
- assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
-
- revokeGrant();
- driver.navigate().to("http://localhost:8081/test-app/logout");
- driver.navigate().to("http://localhost:8081/test-app");
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
- }
-
- protected abstract void doAssertTokenRetrieval(String pageSource);
-
- private UserModel assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel, String username, String expectedEmail, boolean isProfileUpdateExpected) {
- authenticateWithIdentityProvider(identityProviderModel, username, isProfileUpdateExpected);
-
- // authenticated and redirected to app
- assertTrue("Bad current URL " + this.driver.getCurrentUrl() + " and page source: " + this.driver.getPageSource(),
- this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
-
- UserModel federatedUser = getFederatedUser();
-
- assertNotNull(federatedUser);
- assertNotNull(federatedUser.getCreatedTimestamp());
- // test that timestamp is current with 10s tollerance
- Assert.assertTrue((System.currentTimeMillis() - federatedUser.getCreatedTimestamp()) < 10000);
-
- doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail, isProfileUpdateExpected);
-
- brokerServerRule.stopSession(session, true);
- session = brokerServerRule.startSession();
-
- RealmModel realm = getRealm();
-
- Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
-
- assertEquals(1, federatedIdentities.size());
-
- FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
-
- assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
- assertEquals(federatedUser.getUsername(), federatedIdentityModel.getIdentityProvider() + "." + federatedIdentityModel.getUserName());
-
- driver.navigate().to("http://localhost:8081/test-app/logout");
- driver.navigate().to("http://localhost:8081/test-app");
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
- return federatedUser;
- }
-
- private void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel, String username, boolean isProfileUpdateExpected) {
+ protected void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel, String username, boolean isProfileUpdateExpected) {
loginIDP(username);
@@ -738,7 +203,7 @@ public abstract class AbstractIdentityProviderTest {
}
- private void loginIDP(String username) {
+ protected void loginIDP(String username) {
driver.navigate().to("http://localhost:8081/test-app");
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
@@ -776,6 +241,7 @@ public abstract class AbstractIdentityProviderTest {
protected abstract String getProviderId();
+
protected IdentityProviderModel getIdentityProviderModel() {
IdentityProviderModel identityProviderModel = getRealm().getIdentityProviderByAlias(getProviderId());
@@ -786,10 +252,16 @@ public abstract class AbstractIdentityProviderTest {
return identityProviderModel;
}
- private RealmModel getRealm() {
- return this.session.realms().getRealm("realm-with-broker");
+
+ protected RealmModel getRealm() {
+ return getRealm(this.session);
+ }
+
+ protected RealmModel getRealm(KeycloakSession session) {
+ return session.realms().getRealm("realm-with-broker");
}
+
protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel, String expectedEmail, boolean isProfileUpdateExpected) {
if (isProfileUpdateExpected) {
String userFirstName = "New first";
@@ -805,19 +277,6 @@ public abstract class AbstractIdentityProviderTest {
}
}
- private UserSessionStatus retrieveSessionStatus() {
- UserSessionStatus sessionStatus = null;
-
- try {
- String pageSource = this.driver.getPageSource();
-
- sessionStatus = JsonSerialization.readValue(pageSource.getBytes(), UserSessionStatus.class);
- } catch (IOException ignore) {
- ignore.printStackTrace();
- }
-
- return sessionStatus;
- }
private void removeTestUsers() {
RealmModel realm = getRealm();
@@ -835,40 +294,60 @@ public abstract class AbstractIdentityProviderTest {
}
}
}
-
- private String getVerificationEmailLink(MimeMessage message) throws IOException, MessagingException {
- Multipart multipart = (Multipart) message.getContent();
-
+
+
+ protected void setUpdateProfileFirstLogin(final String updateProfileFirstLogin) {
+ KeycloakModelUtils.runJobInTransaction(this.session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ RealmModel realm = getRealm(session);
+ setUpdateProfileFirstLogin(realm, updateProfileFirstLogin);
+ }
+
+ });
+ }
+
+ protected void setUpdateProfileFirstLogin(RealmModel realm, String updateProfileFirstLogin) {
+ AuthenticatorConfigModel reviewProfileConfig = realm.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS);
+ reviewProfileConfig.getConfig().put(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN, updateProfileFirstLogin);
+ realm.updateAuthenticatorConfig(reviewProfileConfig);
+ }
+
+
+ protected UserSessionStatusServlet.UserSessionStatus retrieveSessionStatus() {
+ UserSessionStatusServlet.UserSessionStatus sessionStatus = null;
+
+ try {
+ String pageSource = this.driver.getPageSource();
+
+ sessionStatus = JsonSerialization.readValue(pageSource.getBytes(), UserSessionStatusServlet.UserSessionStatus.class);
+ } catch (IOException ignore) {
+ ignore.printStackTrace();
+ }
+
+ return sessionStatus;
+ }
+
+ protected String getVerificationEmailLink(MimeMessage message) throws IOException, MessagingException {
+ Multipart multipart = (Multipart) message.getContent();
+
final String textContentType = multipart.getBodyPart(0).getContentType();
-
+
assertEquals("text/plain; charset=UTF-8", textContentType);
-
+
final String textBody = (String) multipart.getBodyPart(0).getContent();
final String textVerificationUrl = MailUtil.getLink(textBody);
-
+
final String htmlContentType = multipart.getBodyPart(1).getContentType();
-
+
assertEquals("text/html; charset=UTF-8", htmlContentType);
-
+
final String htmlBody = (String) multipart.getBodyPart(1).getContent();
final String htmlVerificationUrl = MailUtil.getLink(htmlBody);
-
+
assertEquals(htmlVerificationUrl, textVerificationUrl);
return htmlVerificationUrl;
}
-
- private void setUpdateProfileFirstLogin(final String updateProfileFirstLogin) {
- KeycloakModelUtils.runJobInTransaction(this.session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
-
- @Override
- public void run(KeycloakSession session) {
- RealmModel realm = session.realms().getRealm("realm-with-broker");
- AuthenticatorConfigModel reviewProfileConfig = realm.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS);
- reviewProfileConfig.getConfig().put(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN, updateProfileFirstLogin);
- realm.updateAuthenticatorConfig(reviewProfileConfig);
- }
-
- });
- }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
new file mode 100644
index 0000000..904caf6
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -0,0 +1,519 @@
+package org.keycloak.testsuite.broker;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Set;
+
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.internet.MimeMessage;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.services.Urls;
+import org.keycloak.testsuite.MailUtil;
+import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
+import org.openqa.selenium.By;
+import org.openqa.selenium.NoSuchElementException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author pedroigor
+ */
+public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdentityProviderTest {
+
+ @Test
+ public void testSuccessfulAuthentication() {
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
+
+ UserModel user = assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com", true);
+ Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile"));
+ }
+
+ @Test
+ public void testSuccessfulAuthenticationUpdateProfileOnMissing_nothingMissing() {
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING);
+
+ assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
+ }
+
+ @Test
+ public void testSuccessfulAuthenticationUpdateProfileOnMissing_missingEmail() {
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING);
+
+ assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", "new@email.com", true);
+ }
+
+ @Test
+ public void testSuccessfulAuthenticationWithoutUpdateProfile() {
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+ assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
+ }
+
+ /**
+ * Test that verify email action is performed if email is provided and email trust is not enabled for the provider
+ *
+ * @throws MessagingException
+ * @throws IOException
+ */
+ @Test
+ public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled() throws IOException, MessagingException {
+ getRealm().setVerifyEmail(true);
+ brokerServerRule.stopSession(this.session, true);
+ this.session = brokerServerRule.startSession();
+
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+ try {
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+ identityProviderModel.setTrustEmail(false);
+
+ UserModel federatedUser = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "test-user@localhost", false);
+
+ // email is verified now
+ assertFalse(federatedUser.getRequiredActions().contains(UserModel.RequiredAction.VERIFY_EMAIL.name()));
+
+ } finally {
+ getRealm().setVerifyEmail(false);
+ }
+ }
+
+ private UserModel assertSuccessfulAuthenticationWithEmailVerification(IdentityProviderModel identityProviderModel, String username, String expectedEmail,
+ boolean isProfileUpdateExpected)
+ throws IOException, MessagingException {
+ authenticateWithIdentityProvider(identityProviderModel, username, isProfileUpdateExpected);
+
+ // verify email is sent
+ Assert.assertTrue(verifyEmailPage.isCurrent());
+
+ // read email to take verification link from
+ Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+ MimeMessage message = greenMail.getReceivedMessages()[0];
+ String verificationUrl = getVerificationEmailLink(message);
+
+ driver.navigate().to(verificationUrl.trim());
+
+ // authenticated and redirected to app
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+
+ UserModel federatedUser = getFederatedUser();
+
+ assertNotNull(federatedUser);
+
+ doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail, isProfileUpdateExpected);
+
+ brokerServerRule.stopSession(session, true);
+ session = brokerServerRule.startSession();
+
+ RealmModel realm = getRealm();
+
+ Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
+
+ assertEquals(1, federatedIdentities.size());
+
+ FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
+
+ assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
+ assertEquals(federatedUser.getUsername(), federatedIdentityModel.getIdentityProvider() + "." + federatedIdentityModel.getUserName());
+
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+ driver.navigate().to("http://localhost:8081/test-app");
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+ return federatedUser;
+ }
+
+ /**
+ * Test for KEYCLOAK-1053 - verify email action is not performed if email is not provided, login is normal, but action stays in set to be performed later
+ */
+ @Test
+ public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() {
+ getRealm().setVerifyEmail(true);
+ brokerServerRule.stopSession(this.session, true);
+ this.session = brokerServerRule.startSession();
+
+ try {
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+ UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null, false);
+
+ assertTrue(federatedUser.getRequiredActions().contains(UserModel.RequiredAction.VERIFY_EMAIL.name()));
+
+ } finally {
+ getRealm().setVerifyEmail(false);
+ }
+ }
+
+ /**
+ * Test for KEYCLOAK-1372 - verify email action is not performed if email is provided but email trust is enabled for the provider
+ */
+ @Test
+ public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled_emailTrustEnabled() {
+ getRealm().setVerifyEmail(true);
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+ brokerServerRule.stopSession(this.session, true);
+ this.session = brokerServerRule.startSession();
+
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+ try {
+ identityProviderModel.setTrustEmail(true);
+
+ UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
+
+ assertFalse(federatedUser.getRequiredActions().contains(UserModel.RequiredAction.VERIFY_EMAIL.name()));
+
+ } finally {
+ identityProviderModel.setTrustEmail(false);
+ getRealm().setVerifyEmail(false);
+ }
+ }
+
+ /**
+ * Test for KEYCLOAK-1372 - verify email action is performed if email is provided and email trust is enabled for the provider, but email is changed on First login update profile page
+ *
+ * @throws MessagingException
+ * @throws IOException
+ */
+ @Test
+ public void testSuccessfulAuthentication_emailTrustEnabled_emailVerifyEnabled_emailUpdatedOnFirstLogin() throws IOException, MessagingException {
+ getRealm().setVerifyEmail(true);
+ brokerServerRule.stopSession(this.session, true);
+ this.session = brokerServerRule.startSession();
+
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+ try {
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
+ identityProviderModel.setTrustEmail(true);
+
+ UserModel user = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "new@email.com", true);
+ Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile"));
+ } finally {
+ identityProviderModel.setTrustEmail(false);
+ getRealm().setVerifyEmail(false);
+ }
+ }
+
+ @Test
+ public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() {
+
+ getRealm().setRegistrationEmailAsUsername(true);
+ brokerServerRule.stopSession(this.session, true);
+ this.session = brokerServerRule.startSession();
+
+ try {
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+ authenticateWithIdentityProvider(identityProviderModel, "test-user", false);
+
+ // authenticated and redirected to app
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+
+ brokerServerRule.stopSession(session, true);
+ session = brokerServerRule.startSession();
+
+ // check correct user is created with email as username and bound to correct federated identity
+ RealmModel realm = getRealm();
+
+ UserModel federatedUser = session.users().getUserByUsername("test-user@localhost", realm);
+
+ assertNotNull(federatedUser);
+
+ assertEquals("test-user@localhost", federatedUser.getUsername());
+
+ doAssertFederatedUser(federatedUser, identityProviderModel, "test-user@localhost", false);
+
+ Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
+
+ assertEquals(1, federatedIdentities.size());
+
+ FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
+
+ assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
+
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+ driver.navigate().to("http://localhost:8081/test-app");
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+ } finally {
+ getRealm().setRegistrationEmailAsUsername(false);
+ }
+ }
+
+ @Test
+ public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided() {
+
+ getRealm().setRegistrationEmailAsUsername(true);
+ brokerServerRule.stopSession(this.session, true);
+ this.session = brokerServerRule.startSession();
+
+ try {
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+ authenticateWithIdentityProvider(identityProviderModel, "test-user-noemail", false);
+
+ brokerServerRule.stopSession(session, true);
+ session = brokerServerRule.startSession();
+
+ // check correct user is created with username from provider as email is not available
+ RealmModel realm = getRealm();
+ UserModel federatedUser = getFederatedUser();
+ assertNotNull(federatedUser);
+
+ doAssertFederatedUserNoEmail(federatedUser);
+
+ Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
+
+ assertEquals(1, federatedIdentities.size());
+
+ FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
+
+ assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
+ revokeGrant();
+
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+ driver.navigate().to("http://localhost:8081/test-app");
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+ } finally {
+ getRealm().setRegistrationEmailAsUsername(false);
+ }
+ }
+
+ @Test
+ public void testDisabled() {
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+
+ identityProviderModel.setEnabled(false);
+
+ this.driver.navigate().to("http://localhost:8081/test-app/");
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+ try {
+ this.driver.findElement(By.className(getProviderId()));
+ fail("Provider [" + getProviderId() + "] not disabled.");
+ } catch (NoSuchElementException nsee) {
+
+ }
+ }
+
+ @Test
+ public void testProviderOnLoginPage() {
+ // Provider button is available on login page
+ this.driver.navigate().to("http://localhost:8081/test-app/");
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+ loginPage.findSocialButton(getProviderId());
+ }
+
+ @Test
+ public void testAccountManagementLinkIdentity() {
+ // Login as pedroigor to account management
+ accountFederatedIdentityPage.realm("realm-with-broker");
+ accountFederatedIdentityPage.open();
+ assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+ loginPage.login("pedroigor", "password");
+ assertTrue(accountFederatedIdentityPage.isCurrent());
+
+ // Link my "pedroigor" identity with "test-user" from brokered Keycloak
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
+ accountFederatedIdentityPage.clickAddProvider(identityProviderModel.getAlias());
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+ this.loginPage.login("test-user", "password");
+ doAfterProviderAuthentication();
+
+ // Assert identity linked in account management
+ assertTrue(accountFederatedIdentityPage.isCurrent());
+ assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
+
+ // Revoke grant in account mgmt
+ revokeGrant();
+
+ // Logout from account management
+ accountFederatedIdentityPage.logout();
+ assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+
+ // Assert I am logged immediately to account management due to previously linked "test-user" identity
+ loginPage.clickSocial(identityProviderModel.getAlias());
+ doAfterProviderAuthentication();
+ assertTrue(accountFederatedIdentityPage.isCurrent());
+ assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
+
+ // Unlink my "test-user"
+ accountFederatedIdentityPage.clickRemoveProvider(identityProviderModel.getAlias());
+ assertTrue(driver.getPageSource().contains("id=\"add-" + identityProviderModel.getAlias() + "\""));
+
+ // Revoke grant in account mgmt
+ revokeGrant();
+
+ // Logout from account management
+ System.out.println("*** logout from account management");
+ accountFederatedIdentityPage.logout();
+ assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+ // Try to login. Previous link is not valid anymore, so now it should try to register new user
+ this.loginPage.clickSocial(identityProviderModel.getAlias());
+ this.loginPage.login("test-user", "password");
+ doAfterProviderAuthentication();
+ this.updateProfilePage.assertCurrent();
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testIdentityProviderNotAllowed() {
+ this.driver.navigate().to("http://localhost:8081/test-app/");
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+ driver.findElement(By.className("model-oidc-idp"));
+ }
+
+ protected void configureClientRetrieveToken(String clientId) {
+ RealmModel realm = getRealm();
+ RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
+ ClientModel client = realm.getClientByClientId(clientId);
+ if (!client.hasScope(readTokenRole)) client.addScopeMapping(readTokenRole);
+
+ brokerServerRule.stopSession(session, true);
+ session = brokerServerRule.startSession();
+
+ }
+
+ protected void configureUserRetrieveToken(String username) {
+ RealmModel realm = getRealm();
+ UserModel user = session.users().getUserByUsername(username, realm);
+ RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
+ if (user != null && !user.hasRole(readTokenRole)) {
+ user.grantRole(readTokenRole);
+ }
+ brokerServerRule.stopSession(session, true);
+ session = brokerServerRule.startSession();
+
+ }
+
+ protected void unconfigureClientRetrieveToken(String clientId) {
+ RealmModel realm = getRealm();
+ RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
+ ClientModel client = realm.getClientByClientId(clientId);
+ if (client.hasScope(readTokenRole)) client.deleteScopeMapping(readTokenRole);
+
+ brokerServerRule.stopSession(session, true);
+ session = brokerServerRule.startSession();
+
+ }
+
+ protected void unconfigureUserRetrieveToken(String username) {
+ RealmModel realm = getRealm();
+ UserModel user = session.users().getUserByUsername(username, realm);
+ RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
+ if (user != null && user.hasRole(readTokenRole)) {
+ user.deleteRoleMapping(readTokenRole);
+ }
+ brokerServerRule.stopSession(session, true);
+ session = brokerServerRule.startSession();
+
+ }
+
+ @Test
+ public void testTokenStorageAndRetrievalByApplication() {
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+
+ identityProviderModel.setStoreToken(true);
+
+ authenticateWithIdentityProvider(identityProviderModel, "test-user", true);
+
+ UserModel federatedUser = getFederatedUser();
+ RealmModel realm = getRealm();
+ Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
+
+ assertFalse(federatedIdentities.isEmpty());
+ assertEquals(1, federatedIdentities.size());
+
+ FederatedIdentityModel identityModel = federatedIdentities.iterator().next();
+
+ assertNotNull(identityModel.getToken());
+
+ UserSessionStatusServlet.UserSessionStatus userSessionStatus = retrieveSessionStatus();
+ String accessToken = userSessionStatus.getAccessTokenString();
+ URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName());
+ final String authHeader = "Bearer " + accessToken;
+ ClientRequestFilter authFilter = new ClientRequestFilter() {
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
+ }
+ };
+ Client client = ClientBuilder.newBuilder().register(authFilter).build();
+ WebTarget tokenEndpoint = client.target(tokenEndpointUrl);
+ Response response = tokenEndpoint.request().get();
+ assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+ assertNotNull(response.readEntity(String.class));
+ revokeGrant();
+
+
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+ String currentUrl = this.driver.getCurrentUrl();
+ System.out.println("after logout currentUrl: " + currentUrl);
+ assertTrue(currentUrl.startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+ unconfigureUserRetrieveToken(getProviderId() + ".test-user");
+ loginIDP("test-user");
+ //authenticateWithIdentityProvider(identityProviderModel, "test-user");
+ assertEquals("http://localhost:8081/test-app", driver.getCurrentUrl());
+
+ userSessionStatus = retrieveSessionStatus();
+ accessToken = userSessionStatus.getAccessTokenString();
+ final String authHeader2 = "Bearer " + accessToken;
+ ClientRequestFilter authFilter2 = new ClientRequestFilter() {
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader2);
+ }
+ };
+ client = ClientBuilder.newBuilder().register(authFilter2).build();
+ tokenEndpoint = client.target(tokenEndpointUrl);
+ response = tokenEndpoint.request().get();
+
+ assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
+
+ revokeGrant();
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+ driver.navigate().to("http://localhost:8081/test-app");
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+ }
+
+ protected abstract void doAssertTokenRetrieval(String pageSource);
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java
new file mode 100644
index 0000000..d689363
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java
@@ -0,0 +1,145 @@
+package org.keycloak.testsuite.broker;
+
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.KeycloakServer;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.openqa.selenium.NoSuchElementException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OIDCFirstBrokerLoginTest extends AbstractFirstBrokerLoginTest {
+
+ private static final int PORT = 8082;
+
+ @ClassRule
+ public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() {
+
+ @Override
+ protected void configureServer(KeycloakServer server) {
+ server.getConfig().setPort(PORT);
+ }
+
+ @Override
+ protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+ server.importRealm(getClass().getResourceAsStream("/broker-test/test-broker-realm-with-kc-oidc.json"));
+ server.importRealm(getClass().getResourceAsStream("/broker-test/test-broker-realm-with-saml.json"));
+ }
+
+ @Override
+ protected String[] getTestRealms() {
+ return new String[] { "realm-with-oidc-identity-provider", "realm-with-saml-idp-basic" };
+ }
+ };
+
+ @Override
+ protected String getProviderId() {
+ return "kc-oidc-idp";
+ }
+
+
+ /**
+ * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication
+ * with different broker already linked to his account
+ */
+ @Test
+ public void testLinkAccountByReauthenticationWithDifferentBroker() throws Exception {
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
+ IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.DISABLED);
+
+ setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
+ }
+
+ }, APP_REALM_ID);
+
+ // First link "pedroigor" user with SAML broker and logout
+ driver.navigate().to("http://localhost:8081/test-app");
+ this.loginPage.clickSocial("kc-saml-idp-basic");
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+ Assert.assertEquals("Log in to realm-with-saml-idp-basic", this.driver.getTitle());
+ this.loginPage.login("pedroigor", "password");
+
+ this.idpConfirmLinkPage.assertCurrent();
+ Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+ this.idpConfirmLinkPage.clickLinkAccount();
+
+ this.loginPage.login("password");
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+
+
+ // login through OIDC broker now
+ loginIDP("pedroigor");
+
+ this.idpConfirmLinkPage.assertCurrent();
+ Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+ this.idpConfirmLinkPage.clickLinkAccount();
+
+ // assert reauthentication with login page. On login page is link to kc-saml-idp-basic as user has it linked already
+ Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+ Assert.assertEquals("Authenticate as pedroigor to link your account with " + getProviderId(), this.loginPage.getSuccessMessage());
+
+ try {
+ this.loginPage.findSocialButton(getProviderId());
+ Assert.fail("Not expected to see social button with " + getProviderId());
+ } catch (NoSuchElementException expected) {
+ }
+
+ // reauthenticate with SAML broker
+ this.loginPage.clickSocial("kc-saml-idp-basic");
+ Assert.assertEquals("Log in to realm-with-saml-idp-basic", this.driver.getTitle());
+ this.loginPage.login("pedroigor", "password");
+
+
+ // authenticated and redirected to app. User is linked with identity provider
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+ UserModel federatedUser = getFederatedUser();
+
+ assertNotNull(federatedUser);
+ assertEquals("pedroigor", federatedUser.getUsername());
+ assertEquals("psilva@redhat.com", federatedUser.getEmail());
+
+ RealmModel realmWithBroker = getRealm();
+ Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realmWithBroker);
+ assertEquals(2, federatedIdentities.size());
+
+ for (FederatedIdentityModel link : federatedIdentities) {
+ Assert.assertEquals("pedroigor", link.getUserName());
+ Assert.assertTrue(link.getIdentityProvider().equals(getProviderId()) || link.getIdentityProvider().equals("kc-saml-idp-basic"));
+ }
+
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
+ IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+
+ }
+
+ }, APP_REALM_ID);
+ }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
index 0a7ee16..5bfdc1d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
@@ -26,7 +26,7 @@ import static org.junit.Assert.fail;
/**
* @author pedroigor
*/
-public class OIDCKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderTest {
+public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityProviderTest {
private static final int PORT = 8082;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLFirstBrokerLoginTest.java
new file mode 100644
index 0000000..66692d4
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLFirstBrokerLoginTest.java
@@ -0,0 +1,40 @@
+package org.keycloak.testsuite.broker;
+
+import org.junit.ClassRule;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.KeycloakServer;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SAMLFirstBrokerLoginTest extends AbstractFirstBrokerLoginTest {
+
+ private static final int PORT = 8082;
+
+ @ClassRule
+ public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() {
+
+ @Override
+ protected void configureServer(KeycloakServer server) {
+ server.getConfig().setPort(PORT);
+ }
+
+ @Override
+ protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+ server.importRealm(getClass().getResourceAsStream("/broker-test/test-broker-realm-with-saml.json"));
+ }
+
+ @Override
+ protected String[] getTestRealms() {
+ return new String[] { "realm-with-saml-idp-basic" };
+ }
+ };
+
+ @Override
+ protected String getProviderId() {
+ return "kc-saml-idp-basic";
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
index 7bbaa20..8be9dcc 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
@@ -24,7 +24,7 @@ import static org.junit.Assert.fail;
/**
* @author pedroigor
*/
-public class SAMLKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderTest {
+public class SAMLKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityProviderTest {
@ClassRule
public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
index 249f2e0..197749b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
@@ -24,7 +24,7 @@ import static org.junit.Assert.fail;
/**
* @author pedroigor
*/
-public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractIdentityProviderTest {
+public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractKeycloakIdentityProviderTest {
@ClassRule
public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() {
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 950c182..30d94fa 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -215,9 +215,10 @@ public class LoginTest {
Assert.assertEquals("login-test", loginPage.getUsername());
Assert.assertEquals("", loginPage.getPassword());
- Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError());
+ // KEYCLOAK-2024
+ Assert.assertEquals("Invalid username or password.", loginPage.getError());
- events.expectLogin().user(userId).session((String) null).error("user_disabled")
+ events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials")
.detail(Details.USERNAME, "login-test")
.removeDetail(Details.CONSENT)
.assertEvent();
@@ -250,6 +251,7 @@ public class LoginTest {
Assert.assertEquals("login-test", loginPage.getUsername());
Assert.assertEquals("", loginPage.getPassword());
+ // KEYCLOAK-2024
Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError());
events.expectLogin().user(userId).session((String) null).error("user_disabled")
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpConfirmLinkPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpConfirmLinkPage.java
new file mode 100644
index 0000000..83596a0
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpConfirmLinkPage.java
@@ -0,0 +1,41 @@
+package org.keycloak.testsuite.pages;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpConfirmLinkPage extends AbstractPage {
+
+ @FindBy(id = "updateProfile")
+ private WebElement updateProfileButton;
+
+ @FindBy(id = "linkAccount")
+ private WebElement linkAccountButton;
+
+ @FindBy(className = "instruction")
+ private WebElement message;
+
+ @Override
+ public boolean isCurrent() {
+ return driver.getTitle().equals("Account already exists");
+ }
+
+ public String getMessage() {
+ return message.getText();
+ }
+
+ public void clickReviewProfile() {
+ updateProfileButton.click();
+ }
+
+ public void clickLinkAccount() {
+ linkAccountButton.click();
+ }
+
+ @Override
+ public void open() throws Exception {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpLinkEmailPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpLinkEmailPage.java
new file mode 100644
index 0000000..91387b5
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpLinkEmailPage.java
@@ -0,0 +1,27 @@
+package org.keycloak.testsuite.pages;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpLinkEmailPage extends AbstractPage {
+
+ @FindBy(id = "instruction1")
+ private WebElement message;
+
+ @Override
+ public boolean isCurrent() {
+ return driver.getTitle().startsWith("Link ");
+ }
+
+ @Override
+ public void open() throws Exception {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getMessage() {
+ return message.getText();
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
index f329533..04e5ddd 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
@@ -112,6 +112,10 @@ public class LoginPage extends AbstractPage {
return usernameInput.getAttribute("value");
}
+ public boolean isUsernameInputEnabled() {
+ return usernameInput.isEnabled();
+ }
+
public String getPassword() {
return passwordInput.getAttribute("value");
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
index d67862c..c9038a4 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
@@ -21,6 +21,8 @@
*/
package org.keycloak.testsuite.pages;
+import org.junit.Assert;
+import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;