Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2015-11-13 09:30:37 -05:00
commit fa7a7d35a9
30 changed files with 1372 additions and 682 deletions

View file

@ -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);
}
}

View file

@ -818,44 +818,20 @@ $ 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/&lt;SERVER NAME&gt;/configuration</literal>. The configuration should be identical
You need to make sure <literal>domain/servers/&lt;SERVER NAME&gt;/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

View file

@ -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";

View file

@ -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),

View file

@ -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>

View file

@ -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>

View file

@ -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();
}

View file

@ -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);

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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)

View file

@ -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) {

View file

@ -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();
}
}

View file

@ -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

View file

@ -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();

View file

@ -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();
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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,552 +141,7 @@ 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 {
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(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());
assertEquals(null, federatedUser.getEmail());
assertEquals("Test", federatedUser.getFirstName());
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) {
protected UserModel assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel, String username, String expectedEmail, boolean isProfileUpdateExpected) {
authenticateWithIdentityProvider(identityProviderModel, username, isProfileUpdateExpected);
// authenticated and redirected to app
@ -722,7 +178,16 @@ public abstract class AbstractIdentityProviderTest {
return federatedUser;
}
private void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel, String username, boolean isProfileUpdateExpected) {
protected void doAssertFederatedUserNoEmail(UserModel federatedUser) {
assertEquals("kc-oidc-idp.test-user-noemail", federatedUser.getUsername());
assertEquals(null, federatedUser.getEmail());
assertEquals("Test", federatedUser.getFirstName());
assertEquals("User", federatedUser.getLastName());
}
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();
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) {
protected 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);
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;
}
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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";
}
}

View file

@ -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() {

View file

@ -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() {

View file

@ -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")

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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");
}

View file

@ -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;