Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
fa7a7d35a9
30 changed files with 1372 additions and 682 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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/<SERVER NAME>/configuration</literal>. The configuration should be identical
|
||||
You need to make sure <literal>domain/servers/<SERVER NAME>/configuration</literal> is identical
|
||||
for all servers in a group.
|
||||
</para>
|
||||
<para>
|
||||
Follow the <link linkend='clustering'>Clustering</link> section of the documentation to configure Keycloak
|
||||
for clustering. In domain mode it doesn't make much sense to not configure Keycloak in cluster mode.
|
||||
</para>
|
||||
<para>
|
||||
To deploy custom providers and themes you should deploys these as modules and make sure the modules are
|
||||
available to all servers in the group. See <link linkend='providers'>Providers</link> and
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue