diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js index ab6f78826a..8c5e03dcbf 100755 --- a/adapters/oidc/js/src/main/resources/keycloak.js +++ b/adapters/oidc/js/src/main/resources/keycloak.js @@ -50,7 +50,7 @@ } else if (initOptions && initOptions.adapter === 'default') { adapter = loadAdapter(); } else { - if (window.Cordova) { + if (window.Cordova || window.cordova) { adapter = loadAdapter('cordova'); } else { adapter = loadAdapter(); @@ -948,7 +948,14 @@ if (type == 'cordova') { loginIframe.enable = false; - + var cordovaOpenWindowWrapper = function(loginUrl, target, options) { + if (window.cordova && window.cordova.InAppBrowser) { + // Use inappbrowser for IOS and Android if available + return window.cordova.InAppBrowser.open(loginUrl, target, options); + } else { + return window.open(loginUrl, target, options); + } + }; return { login: function(options) { var promise = createPromise(); @@ -959,8 +966,7 @@ } var loginUrl = kc.createLoginUrl(options); - var ref = window.open(loginUrl, '_blank', o); - + var ref = cordovaOpenWindowWrapper(loginUrl, '_blank', o); var completed = false; ref.addEventListener('loadstart', function(event) { @@ -993,7 +999,7 @@ var promise = createPromise(); var logoutUrl = kc.createLogoutUrl(options); - var ref = window.open(logoutUrl, '_blank', 'location=no,hidden=yes'); + var ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes'); var error; @@ -1026,7 +1032,7 @@ register : function() { var registerUrl = kc.createRegisterUrl(); - var ref = window.open(registerUrl, '_blank', 'location=no'); + var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', 'location=no'); ref.addEventListener('loadstart', function(event) { if (event.url.indexOf('http://localhost') == 0) { ref.close(); @@ -1036,7 +1042,7 @@ accountManagement : function() { var accountUrl = kc.createAccountUrl(); - var ref = window.open(accountUrl, '_blank', 'location=no'); + var ref = cordovaOpenWindowWrapper(accountUrl, '_blank', 'location=no'); ref.addEventListener('loadstart', function(event) { if (event.url.indexOf('http://localhost') == 0) { ref.close(); diff --git a/core/src/test/java/org/keycloak/RSAVerifierTest.java b/core/src/test/java/org/keycloak/RSAVerifierTest.java index e606e35a94..8418f14572 100755 --- a/core/src/test/java/org/keycloak/RSAVerifierTest.java +++ b/core/src/test/java/org/keycloak/RSAVerifierTest.java @@ -28,6 +28,7 @@ import org.keycloak.common.VerificationException; import org.keycloak.common.util.Time; import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.representations.AccessToken; +import org.keycloak.util.JsonSerialization; import org.keycloak.util.TokenUtil; import javax.security.auth.x500.X500Principal; @@ -126,28 +127,28 @@ public class RSAVerifierTest { return RSATokenVerifier.verifyToken(encoded, idpPair.getPublic(), "http://localhost:8080/auth/realm"); } - /* - @Test + + // @Test public void testSpeed() throws Exception { - - byte[] tokenBytes = JsonSerialization.toByteArray(token, false); - - String encoded = new JWSBuilder() - .content(tokenBytes) - .rsa256(idpPair.getPrivate()); + // Took 44 seconds with 50000 iterations + byte[] tokenBytes = JsonSerialization.writeValueAsBytes(token); long start = System.currentTimeMillis(); - int count = 10000; + int count = 50000; for (int i = 0; i < count; i++) { - SkeletonKeyTokenVerification v = RSATokenVerifier.verify(null, encoded, metadata); + String encoded = new JWSBuilder() + .content(tokenBytes) + .rsa256(idpPair.getPrivate()); + + verifySkeletonKeyToken(encoded); } long end = System.currentTimeMillis() - start; - System.out.println("rate: " + ((double)end/(double)count)); + System.out.println("took: " + end); } - */ + @Test diff --git a/core/src/test/java/org/keycloak/jose/JWETest.java b/core/src/test/java/org/keycloak/jose/JWETest.java index f97f3c5b9b..31d8a8ac13 100644 --- a/core/src/test/java/org/keycloak/jose/JWETest.java +++ b/core/src/test/java/org/keycloak/jose/JWETest.java @@ -94,7 +94,7 @@ public class JWETest { } - // @Test + //@Test public void testPerfDirect() throws Exception { int iterations = 50000; diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java index 192c9647c7..2a31453a94 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java @@ -16,6 +16,7 @@ */ package org.keycloak.models.sessions.infinispan; +import org.jboss.logging.Logger; import org.keycloak.cluster.ClusterProvider; import org.keycloak.common.util.Time; import org.keycloak.models.*; @@ -33,6 +34,8 @@ import org.infinispan.Cache; */ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvider { + private static final Logger LOG = Logger.getLogger(InfinispanActionTokenStoreProvider.class); + private final Cache actionKeyCache; private final InfinispanKeycloakTransaction tx; private final KeycloakSession session; @@ -58,6 +61,8 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi ActionTokenReducedKey tokenKey = new ActionTokenReducedKey(key.getUserId(), key.getActionId(), key.getActionVerificationNonce()); ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes); + LOG.debugf("Adding used action token to actionTokens cache: %s", tokenKey.toString()); + this.tx.put(actionKeyCache, tokenKey, tokenValue, key.getExpiration() - Time.currentTime(), TimeUnit.SECONDS); } @@ -68,7 +73,15 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi } ActionTokenReducedKey key = new ActionTokenReducedKey(actionTokenKey.getUserId(), actionTokenKey.getActionId(), actionTokenKey.getActionVerificationNonce()); - return this.actionKeyCache.getAdvancedCache().get(key); + + ActionTokenValueModel value = this.actionKeyCache.getAdvancedCache().get(key); + if (value == null) { + LOG.debugf("Not found any value in actionTokens cache for key: %s", key.toString()); + } else { + LOG.debugf("Found value in actionTokens cache for key: %s", key.toString()); + } + + return value; } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java index 7e3c76ed5e..647a9df584 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java @@ -45,6 +45,11 @@ public class ActionTokenValueEntity implements ActionTokenValueModel { return notes.get(name); } + @Override + public String toString() { + return String.format("ActionTokenValueEntity [ notes=%s ]", notes.toString()); + } + public static class ExternalizerImpl implements Externalizer { private static final int VERSION_1 = 1; diff --git a/pom.xml b/pom.xml index 43d614564b..87b288b674 100755 --- a/pom.xml +++ b/pom.xml @@ -1433,6 +1433,7 @@ once ${surefire.memory.settings} + -Djava.awt.headless=true diff --git a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java index cb203c17ce..3f44b1e8fe 100755 --- a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java @@ -22,6 +22,7 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.FormMessage; import org.keycloak.provider.Provider; +import org.keycloak.sessions.AuthenticationSessionModel; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -74,6 +75,8 @@ public interface LoginFormsProvider extends Provider { public Response createOAuthGrant(); public Response createCode(); + + public LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authenticationSession); public LoginFormsProvider setClientSessionCode(String accessCode); diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java index e212c924fe..c7a8edee44 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -469,6 +469,7 @@ public class AuthenticationProcessor { String accessCode = generateAccessCode(); URI action = getActionUrl(accessCode); LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class) + .setAuthenticationSession(getAuthenticationSession()) .setUser(getUser()) .setActionUri(action) .setExecution(getExecution().getId()) @@ -609,25 +610,25 @@ public class AuthenticationProcessor { if (e.getError() == AuthenticationFlowError.INVALID_USER) { ServicesLogger.LOGGER.failedAuthentication(e); event.error(Errors.USER_NOT_FOUND); - return ErrorPage.error(session, Messages.INVALID_USER); + return ErrorPage.error(session, authenticationSession, Messages.INVALID_USER); } else if (e.getError() == AuthenticationFlowError.USER_DISABLED) { ServicesLogger.LOGGER.failedAuthentication(e); event.error(Errors.USER_DISABLED); - return ErrorPage.error(session, Messages.ACCOUNT_DISABLED); + return ErrorPage.error(session,authenticationSession, Messages.ACCOUNT_DISABLED); } else if (e.getError() == AuthenticationFlowError.USER_TEMPORARILY_DISABLED) { ServicesLogger.LOGGER.failedAuthentication(e); event.error(Errors.USER_TEMPORARILY_DISABLED); - return ErrorPage.error(session, Messages.INVALID_USER); + return ErrorPage.error(session,authenticationSession, Messages.INVALID_USER); } else if (e.getError() == AuthenticationFlowError.INVALID_CLIENT_SESSION) { ServicesLogger.LOGGER.failedAuthentication(e); event.error(Errors.INVALID_CODE); - return ErrorPage.error(session, Messages.INVALID_CODE); + return ErrorPage.error(session, authenticationSession, Messages.INVALID_CODE); } else if (e.getError() == AuthenticationFlowError.EXPIRED_CODE) { ServicesLogger.LOGGER.failedAuthentication(e); event.error(Errors.EXPIRED_CODE); - return ErrorPage.error(session, Messages.EXPIRED_CODE); + return ErrorPage.error(session, authenticationSession, Messages.EXPIRED_CODE); } else if (e.getError() == AuthenticationFlowError.FORK_FLOW) { ForkFlowException reset = (ForkFlowException)e; @@ -654,13 +655,13 @@ public class AuthenticationProcessor { } else { ServicesLogger.LOGGER.failedAuthentication(e); event.error(Errors.INVALID_USER_CREDENTIALS); - return ErrorPage.error(session, Messages.INVALID_USER); + return ErrorPage.error(session, authenticationSession, Messages.INVALID_USER); } } else { ServicesLogger.LOGGER.failedAuthentication(failure); event.error(Errors.INVALID_USER_CREDENTIALS); - return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST); + return ErrorPage.error(session, authenticationSession, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST); } } @@ -885,7 +886,7 @@ public class AuthenticationProcessor { if (!authSession.getAuthenticatedUser().equals(userSession.getUser())) { event.detail(Details.EXISTING_USER, userSession.getUser().getId()); event.error(Errors.DIFFERENT_USER_AUTHENTICATED); - throw new ErrorPageException(session, Messages.DIFFERENT_USER_AUTHENTICATED, userSession.getUser().getUsername()); + throw new ErrorPageException(session, authSession, Messages.DIFFERENT_USER_AUTHENTICATED, userSession.getUser().getUsername()); } } userSession.setState(UserSessionModel.State.LOGGED_IN); @@ -921,7 +922,8 @@ public class AuthenticationProcessor { if (!authenticatedUser.isEnabled()) throw new AuthenticationFlowException(AuthenticationFlowError.USER_DISABLED); if (realm.isBruteForceProtected() && !realm.isPermanentLockout()) { if (getBruteForceProtector().isTemporarilyDisabled(session, realm, authenticatedUser)) { - throw new AuthenticationFlowException(AuthenticationFlowError.USER_TEMPORARILY_DISABLED); + getEvent().error(Errors.RESET_CREDENTIAL_DISABLED); + ServicesLogger.LOGGER.passwordResetFailed(new AuthenticationFlowException(AuthenticationFlowError.USER_TEMPORARILY_DISABLED)); } } } diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java index 575677d0b3..0a9fc07b66 100755 --- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java +++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java @@ -269,6 +269,7 @@ public class FormAuthenticationFlow implements AuthenticationFlow { String code = processor.generateCode(); URI actionUrl = getActionUrl(executionId, code); LoginFormsProvider form = processor.getSession().getProvider(LoginFormsProvider.class) + .setAuthenticationSession(processor.getAuthenticationSession()) .setActionUri(actionUrl) .setExecution(executionId) .setClientSessionCode(code) diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java index 3ce9768859..e87652a513 100755 --- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java +++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java @@ -166,6 +166,7 @@ public class RequiredActionContextResult implements RequiredActionContext { String accessCode = generateCode(); URI action = getActionUrl(accessCode); LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class) + .setAuthenticationSession(getAuthenticationSession()) .setUser(getUser()) .setActionUri(action) .setExecution(getExecution()) diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java index a1c857f0f1..43e4f902b6 100644 --- a/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java +++ b/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java @@ -80,6 +80,7 @@ public class ExecuteActionsActionTokenHandler extends AbstractActionTokenHander< String confirmUri = builder.build(realm.getName()).toString(); return session.getProvider(LoginFormsProvider.class) + .setAuthenticationSession(authSession) .setSuccess(Messages.CONFIRM_EXECUTION_OF_ACTIONS) .setAttribute(Constants.TEMPLATE_ATTR_ACTION_URI, confirmUri) .setAttribute(Constants.TEMPLATE_ATTR_REQUIRED_ACTIONS, token.getRequiredActions()) diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java index c5dc897a18..7f9d58c3bc 100644 --- a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java +++ b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java @@ -81,6 +81,7 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH String confirmUri = builder.build(realm.getName()).toString(); return session.getProvider(LoginFormsProvider.class) + .setAuthenticationSession(authSession) .setSuccess(Messages.CONFIRM_ACCOUNT_LINKING, token.getIdentityProviderUsername(), token.getIdentityProviderAlias()) .setAttribute(Constants.TEMPLATE_ATTR_ACTION_URI, confirmUri) .createInfoPage(); @@ -106,6 +107,7 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH } return session.getProvider(LoginFormsProvider.class) + .setAuthenticationSession(authSession) .setSuccess(Messages.IDENTITY_PROVIDER_LINK_SUCCESS, token.getIdentityProviderAlias(), token.getIdentityProviderUsername()) .setAttribute(Constants.SKIP_LINK, true) .createInfoPage(); diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java index 0f08bd39a4..a17efefa46 100644 --- a/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java +++ b/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java @@ -85,7 +85,7 @@ public class ResetCredentialsActionTokenHandler extends AbstractActionTokenHande UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realm, authenticationSession); if (!linkingUser.getId().equals(authenticationSession.getAuthenticatedUser().getId())) { - return ErrorPage.error(session, + return ErrorPage.error(session, authenticationSession, Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, authenticationSession.getAuthenticatedUser().getUsername(), linkingUser.getUsername() diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java index b5d046e971..98ad8b4b98 100644 --- a/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java +++ b/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java @@ -82,6 +82,7 @@ public class VerifyEmailActionTokenHandler extends AbstractActionTokenHander subjectAttributes, String template, Map attributes) throws EmailException { try { - ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); - Theme theme = themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL); + Theme theme = getTheme(); Locale locale = session.getContext().resolveLocale(user); attributes.put("locale", locale); Properties rb = theme.getMessages(locale); @@ -198,10 +197,18 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider { throw new EmailException("Failed to template email", e); } } + + protected Theme getTheme() throws IOException { + ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); + return themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL); + } + protected void send(String subjectKey, List subjectAttributes, String template, Map attributes) throws EmailException { try { EmailTemplate email = processTemplate(subjectKey, subjectAttributes, template, attributes); send(email.getSubject(), email.getTextBody(), email.getHtmlBody()); + } catch (EmailException e){ + throw e; } catch (Exception e) { throw new EmailException("Failed to template email", e); } diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java b/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java index 6241c855eb..855b388415 100755 --- a/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java +++ b/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java @@ -71,26 +71,26 @@ public class FreeMarkerAccountProvider implements AccountProvider { private static final Logger logger = Logger.getLogger(FreeMarkerAccountProvider.class); - private UserModel user; - private MultivaluedMap profileFormData; - private Response.Status status = Response.Status.OK; - private RealmModel realm; - private String[] referrer; - private List events; - private String stateChecker; - private List sessions; - private boolean identityProviderEnabled; - private boolean eventsEnabled; - private boolean passwordUpdateSupported; - private boolean passwordSet; - private KeycloakSession session; - private FreeMarkerUtil freeMarker; - private HttpHeaders headers; + protected UserModel user; + protected MultivaluedMap profileFormData; + protected Response.Status status = Response.Status.OK; + protected RealmModel realm; + protected String[] referrer; + protected List events; + protected String stateChecker; + protected List sessions; + protected boolean identityProviderEnabled; + protected boolean eventsEnabled; + protected boolean passwordUpdateSupported; + protected boolean passwordSet; + protected KeycloakSession session; + protected FreeMarkerUtil freeMarker; + protected HttpHeaders headers; - private UriInfo uriInfo; + protected UriInfo uriInfo; - private List messages = null; - private MessageType messageType = MessageType.ERROR; + protected List messages = null; + protected MessageType messageType = MessageType.ERROR; public FreeMarkerAccountProvider(KeycloakSession session, FreeMarkerUtil freeMarker) { this.session = session; @@ -112,30 +112,16 @@ public class FreeMarkerAccountProvider implements AccountProvider { public Response createResponse(AccountPages page) { Map attributes = new HashMap(); - ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); Theme theme; try { - theme = themeProvider.getTheme(realm.getAccountTheme(), Theme.Type.ACCOUNT); + theme = getTheme(); } catch (IOException e) { logger.error("Failed to create theme", e); return Response.serverError().build(); } - try { - attributes.put("properties", theme.getProperties()); - } catch (IOException e) { - logger.warn("Failed to load properties", e); - } - Locale locale = session.getContext().resolveLocale(user); - Properties messagesBundle; - try { - messagesBundle = theme.getMessages(locale); - attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle)); - } catch (IOException e) { - logger.warn("Failed to load messages", e); - messagesBundle = new Properties(); - } + Properties messagesBundle = handleThemeResources(theme, locale, attributes); URI baseUri = uriInfo.getBaseUri(); UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); @@ -148,19 +134,7 @@ public class FreeMarkerAccountProvider implements AccountProvider { attributes.put("stateChecker", stateChecker); } - MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean(); - if (messages != null) { - MessageBean wholeMessage = new MessageBean(null, messageType); - for (FormMessage message : this.messages) { - String formattedMessageText = formatMessage(message, messagesBundle, locale); - if (formattedMessageText != null) { - wholeMessage.appendSummaryLine(formattedMessageText); - messagesPerField.addMessage(message.getField(), formattedMessageText, messageType); - } - } - attributes.put("message", wholeMessage); - } - attributes.put("messagesPerField", messagesPerField); + handleMessages(locale, messagesBundle, attributes); if (referrer != null) { attributes.put("referrer", new ReferrerBean(referrer)); @@ -173,12 +147,7 @@ public class FreeMarkerAccountProvider implements AccountProvider { attributes.put("url", new UrlBean(realm, theme, baseUri, baseQueryUri, uriInfo.getRequestUri(), stateChecker)); if (realm.isInternationalizationEnabled()) { - UriBuilder b; - switch (page) { - default: - b = UriBuilder.fromUri(baseQueryUri).path(uriInfo.getPath()); - break; - } + UriBuilder b = UriBuilder.fromUri(baseQueryUri).path(uriInfo.getPath()); attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle)); } @@ -204,8 +173,84 @@ public class FreeMarkerAccountProvider implements AccountProvider { break; case PASSWORD: attributes.put("password", new PasswordBean(passwordSet)); + break; + default: } + return processTemplate(theme, page, attributes, locale); + } + + /** + * Get Theme used for page rendering. + * + * @return theme for page rendering, never null + * @throws IOException in case of Theme loading problem + */ + protected Theme getTheme() throws IOException { + ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); + return themeProvider.getTheme(realm.getAccountTheme(), Theme.Type.ACCOUNT); + } + + /** + * Load message bundle and place it into msg template attribute. Also load Theme properties and place them into properties template attribute. + * + * @param theme actual Theme to load bundle from + * @param locale to load bundle for + * @param attributes template attributes to add resources to + * @return message bundle for other use + */ + protected Properties handleThemeResources(Theme theme, Locale locale, Map attributes) { + Properties messagesBundle; + try { + messagesBundle = theme.getMessages(locale); + attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle)); + } catch (IOException e) { + logger.warn("Failed to load messages", e); + messagesBundle = new Properties(); + } + try { + attributes.put("properties", theme.getProperties()); + } catch (IOException e) { + logger.warn("Failed to load properties", e); + } + return messagesBundle; + } + + /** + * Handle messages to be shown on the page - set them to template attributes + * + * @param locale to be used for message text loading + * @param messagesBundle to be used for message text loading + * @param attributes template attributes to messages related info to + * @see #messageType + * @see #messages + */ + protected void handleMessages(Locale locale, Properties messagesBundle, Map attributes) { + MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean(); + if (messages != null) { + MessageBean wholeMessage = new MessageBean(null, messageType); + for (FormMessage message : this.messages) { + String formattedMessageText = formatMessage(message, messagesBundle, locale); + if (formattedMessageText != null) { + wholeMessage.appendSummaryLine(formattedMessageText); + messagesPerField.addMessage(message.getField(), formattedMessageText, messageType); + } + } + attributes.put("message", wholeMessage); + } + attributes.put("messagesPerField", messagesPerField); + } + + /** + * Process FreeMarker template and prepare Response. Some fields are used for rendering also. + * + * @param theme to be used (provided by getTheme()) + * @param page to be rendered + * @param attributes pushed to the template + * @param locale to be used + * @return Response object to be returned to the browser, never null + */ + protected Response processTemplate(Theme theme, AccountPages page, Map attributes, Locale locale) { try { String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme); Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8_TYPE).language(locale).entity(result); diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java index 8ec6a5b299..e4cb41d253 100755 --- a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java +++ b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java @@ -39,6 +39,7 @@ import org.keycloak.models.*; import org.keycloak.models.utils.FormMessage; import org.keycloak.services.Urls; import org.keycloak.services.messages.Messages; +import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.theme.BrowserSecurityHeaderSetup; import org.keycloak.theme.FreeMarkerException; import org.keycloak.theme.FreeMarkerUtil; @@ -68,42 +69,52 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class); - private String accessCode; - private Response.Status status; - private List realmRolesRequested; - private MultivaluedMap resourceRolesRequested; - private List protocolMappersRequested; - private Map httpResponseHeaders = new HashMap(); - private String accessRequestMessage; - private URI actionUri; - private String execution; + protected String accessCode; + protected Response.Status status; + protected List realmRolesRequested; + protected MultivaluedMap resourceRolesRequested; + protected List protocolMappersRequested; + protected Map httpResponseHeaders = new HashMap(); + protected String accessRequestMessage; + protected URI actionUri; + protected String execution; - private List messages = null; - private MessageType messageType = MessageType.ERROR; + protected List messages = null; + protected MessageType messageType = MessageType.ERROR; - private MultivaluedMap formData; + protected MultivaluedMap formData; - private KeycloakSession session; - private FreeMarkerUtil freeMarker; + protected KeycloakSession session; + /** authenticationSession can be null for some renderings, mainly error pages */ + protected AuthenticationSessionModel authenticationSession; + protected RealmModel realm; + protected ClientModel client; + protected UriInfo uriInfo; - private UserModel user; + protected FreeMarkerUtil freeMarker; - private final Map attributes = new HashMap(); + protected UserModel user; + + protected final Map attributes = new HashMap(); public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) { this.session = session; this.freeMarker = freeMarker; this.attributes.put("scripts", new LinkedList()); + this.realm = session.getContext().getRealm(); + this.client = session.getContext().getClient(); + this.uriInfo = session.getContext().getUri(); } + @SuppressWarnings("unchecked") @Override public void addScript(String scriptUrl) { - List scripts = (List)this.attributes.get("scripts"); + List scripts = (List) this.attributes.get("scripts"); scripts.add(scriptUrl); } + @Override public Response createResponse(UserModel.RequiredAction action) { - RealmModel realm = session.getContext().getRealm(); String actionMessage; LoginFormsPages page; @@ -139,110 +150,29 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { return createResponse(page); } - private Response createResponse(LoginFormsPages page) { - RealmModel realm = session.getContext().getRealm(); - ClientModel client = session.getContext().getClient(); - UriInfo uriInfo = session.getContext().getUri(); + @SuppressWarnings("incomplete-switch") + protected Response createResponse(LoginFormsPages page) { - String requestURI = uriInfo.getBaseUri().getPath(); - UriBuilder uriBuilder = UriBuilder.fromUri(requestURI); - if (page == LoginFormsPages.OAUTH_GRANT) { - // for some reason Resteasy 2.3.7 doesn't like query params and form params with the same name and will null out the code form param - uriBuilder.replaceQuery(null); + if (status == null) { + status = Response.Status.OK; } - if (client != null) { - uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId()); - } - - URI baseUri = uriBuilder.build(); - - if (accessCode != null) { - uriBuilder.queryParam(OAuth2Constants.CODE, accessCode); - } - - URI baseUriWithCodeAndClientId = uriBuilder.build(); - - ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); Theme theme; try { - theme = themeProvider.getTheme(realm.getLoginTheme(), Theme.Type.LOGIN); + theme = getTheme(); } catch (IOException e) { logger.error("Failed to create theme", e); return Response.serverError().build(); } - try { - attributes.put("properties", theme.getProperties()); - } catch (IOException e) { - logger.warn("Failed to load properties", e); - } - - Properties messagesBundle; Locale locale = session.getContext().resolveLocale(user); - try { - messagesBundle = theme.getMessages(locale); - attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle)); - } catch (IOException e) { - logger.warn("Failed to load messages", e); - messagesBundle = new Properties(); - } + Properties messagesBundle = handleThemeResources(theme, locale); - MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean(); - if (messages != null) { - MessageBean wholeMessage = new MessageBean(null, messageType); - for (FormMessage message : this.messages) { - String formattedMessageText = formatMessage(message, messagesBundle, locale); - if (formattedMessageText != null) { - wholeMessage.appendSummaryLine(formattedMessageText); - messagesPerField.addMessage(message.getField(), formattedMessageText, messageType); - } - } - attributes.put("message", wholeMessage); - } else { - attributes.put("message", null); - } - attributes.put("messagesPerField", messagesPerField); + handleMessages(locale, messagesBundle); - attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri)); - if (realm != null && user != null && session != null) { - attributes.put("authenticatorConfigured", new AuthenticatorConfiguredMethod(realm, user, session)); - } - - if (realm != null) { - attributes.put("realm", new RealmBean(realm)); - - List identityProviders = realm.getIdentityProviders(); - identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData); - attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCodeAndClientId)); - - attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri)); - - if (realm.isInternationalizationEnabled()) { - UriBuilder b; - switch (page) { - case LOGIN: - b = UriBuilder.fromUri(Urls.realmLoginPage(baseUri, realm.getName())); - break; - case REGISTER: - b = UriBuilder.fromUri(Urls.realmRegisterPage(baseUri, realm.getName())); - break; - default: - b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath()); - break; - } - - if (execution != null) { - b.queryParam(Constants.EXECUTION, execution); - } - - attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle)); - } - } - - if (client != null) { - attributes.put("client", new ClientBean(client, baseUri)); - } + // for some reason Resteasy 2.3.7 doesn't like query params and form params with the same name and will null out the code form param + UriBuilder uriBuilder = prepareBaseUriBuilder(page == LoginFormsPages.OAUTH_GRANT); + createCommonAttributes(theme, locale, messagesBundle, uriBuilder, page); attributes.put("login", new LoginBean(formData)); @@ -267,7 +197,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { attributes.put("register", new RegisterBean(formData)); break; case OAUTH_GRANT: - attributes.put("oauth", new OAuthGrantBean(accessCode, client, realmRolesRequested, resourceRolesRequested, protocolMappersRequested, this.accessRequestMessage)); + attributes.put("oauth", + new OAuthGrantBean(accessCode, client, realmRolesRequested, resourceRolesRequested, protocolMappersRequested, this.accessRequestMessage)); attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle)); break; case CODE: @@ -279,62 +210,74 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { status = Response.Status.OK; } - try { - String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme); - Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8).entity(result); - BrowserSecurityHeaderSetup.headers(builder, realm); - for (Map.Entry entry : httpResponseHeaders.entrySet()) { - builder.header(entry.getKey(), entry.getValue()); - } - return builder.build(); - } catch (FreeMarkerException e) { - logger.error("Failed to process template", e); - return Response.serverError().build(); - } + return processTemplate(theme, Templates.getTemplate(page), locale); } @Override public Response createForm(String form) { - RealmModel realm = session.getContext().getRealm(); - ClientModel client = session.getContext().getClient(); - UriInfo uriInfo = session.getContext().getUri(); - - String requestURI = uriInfo.getBaseUri().getPath(); - UriBuilder uriBuilder = UriBuilder.fromUri(requestURI); - - if (client != null) { - uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId()); + if (status == null) { + status = Response.Status.OK; } - URI baseUri = uriBuilder.build(); - - if (accessCode != null) { - uriBuilder.queryParam(OAuth2Constants.CODE, accessCode); - } - - URI baseUriWithCode = uriBuilder.build(); - - ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); Theme theme; try { - theme = themeProvider.getTheme(realm.getLoginTheme(), Theme.Type.LOGIN); + theme = getTheme(); } catch (IOException e) { logger.error("Failed to create theme", e); return Response.serverError().build(); } - try { - attributes.put("properties", theme.getProperties()); - } catch (IOException e) { - logger.warn("Failed to load properties", e); - } - if (client != null) { - attributes.put("client", new ClientBean(client, baseUri)); + Locale locale = session.getContext().resolveLocale(user); + Properties messagesBundle = handleThemeResources(theme, locale); + + handleMessages(locale, messagesBundle); + + UriBuilder uriBuilder = prepareBaseUriBuilder(false); + createCommonAttributes(theme, locale, messagesBundle, uriBuilder, null); + + return processTemplate(theme, form, locale); + } + + /** + * Prepare base uri builder for later use + * + * @param resetRequestUriParams - for some reason Resteasy 2.3.7 doesn't like query params and form params with the same name and will null out the code form param, so we have to reset them for some pages + * @return base uri builder + */ + protected UriBuilder prepareBaseUriBuilder(boolean resetRequestUriParams) { + String requestURI = uriInfo.getBaseUri().getPath(); + UriBuilder uriBuilder = UriBuilder.fromUri(requestURI); + if (resetRequestUriParams) { + uriBuilder.replaceQuery(null); } + if (client != null) { + uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId()); + } + return uriBuilder; + } + + /** + * Get Theme used for page rendering. + * + * @return theme for page rendering, never null + * @throws IOException in case of Theme loading problem + */ + protected Theme getTheme() throws IOException { + ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); + return themeProvider.getTheme(realm.getLoginTheme(), Theme.Type.LOGIN); + } + + /** + * Load message bundle and place it into msg template attribute. Also load Theme properties and place them into properties template attribute. + * + * @param theme actual Theme to load bundle from + * @param locale to load bundle for + * @return message bundle for other use + */ + protected Properties handleThemeResources(Theme theme, Locale locale) { Properties messagesBundle; - Locale locale = session.getContext().resolveLocale(user); try { messagesBundle = theme.getMessages(locale); attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle)); @@ -343,6 +286,24 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { messagesBundle = new Properties(); } + try { + attributes.put("properties", theme.getProperties()); + } catch (IOException e) { + logger.warn("Failed to load properties", e); + } + + return messagesBundle; + } + + /** + * Handle messages to be shown on the page - set them to template attributes + * + * @param locale to be used for message text loading + * @param messagesBundle to be used for message text loading + * @see #messageType + * @see #messages + */ + protected void handleMessages(Locale locale, Properties messagesBundle) { MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean(); if (messages != null) { MessageBean wholeMessage = new MessageBean(null, messageType); @@ -354,11 +315,31 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { } } attributes.put("message", wholeMessage); + } else { + attributes.put("message", null); } attributes.put("messagesPerField", messagesPerField); + } + + /** + * Create common attributes used in all templates. + * + * @param theme actual Theme used (provided by getTheme()) + * @param locale actual locale + * @param messagesBundle actual message bundle (provided by handleThemeResources()) + * @param baseUriBuilder actual base uri builder (provided by prepareBaseUriBuilder()) + * @param page in case if common page is rendered, is null if called from createForm() + * + */ + protected void createCommonAttributes(Theme theme, Locale locale, Properties messagesBundle, UriBuilder baseUriBuilder, LoginFormsPages page) { + URI baseUri = baseUriBuilder.build(); + if (accessCode != null) { + baseUriBuilder.queryParam(OAuth2Constants.CODE, accessCode); + } + URI baseUriWithCodeAndClientId = baseUriBuilder.build(); - if (status == null) { - status = Response.Status.OK; + if (client != null) { + attributes.put("client", new ClientBean(client, baseUri)); } if (realm != null) { @@ -366,14 +347,29 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { List identityProviders = realm.getIdentityProviders(); identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData); - attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCode)); + attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCodeAndClientId)); attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri)); attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri)); if (realm.isInternationalizationEnabled()) { - UriBuilder b = UriBuilder.fromUri(baseUri) - .path(uriInfo.getPath()); + UriBuilder b; + if (page != null) { + switch (page) { + case LOGIN: + b = UriBuilder.fromUri(Urls.realmLoginPage(baseUri, realm.getName())); + break; + case REGISTER: + b = UriBuilder.fromUri(Urls.realmRegisterPage(baseUri, realm.getName())); + break; + default: + b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath()); + break; + } + } else { + b = UriBuilder.fromUri(baseUri) + .path(uriInfo.getPath()); + } if (execution != null) { b.queryParam(Constants.EXECUTION, execution); @@ -385,8 +381,19 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { if (realm != null && user != null && session != null) { attributes.put("authenticatorConfigured", new AuthenticatorConfiguredMethod(realm, user, session)); } + } + + /** + * Process FreeMarker template and prepare Response. Some fields are used for rendering also. + * + * @param theme to be used (provided by getTheme()) + * @param templateName name of the template to be rendered + * @param locale to be used + * @return Response object to be returned to the browser, never null + */ + protected Response processTemplate(Theme theme, String templateName, Locale locale) { try { - String result = freeMarker.processTemplate(attributes, form, theme); + String result = freeMarker.processTemplate(attributes, templateName, theme); Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8_TYPE).language(locale).entity(result); BrowserSecurityHeaderSetup.headers(builder, realm); for (Map.Entry entry : httpResponseHeaders.entrySet()) { @@ -434,7 +441,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { return createResponse(LoginFormsPages.LOGIN_UPDATE_PROFILE); } - @Override public Response createIdpLinkConfirmLinkPage() { return createResponse(LoginFormsPages.LOGIN_IDP_LINK_CONFIRM); @@ -504,7 +510,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { @Override public LoginFormsProvider setErrors(List messages) { - if (messages == null) return this; + if (messages == null) + return this; this.messageType = MessageType.ERROR; this.messages = new ArrayList<>(messages); return this; @@ -552,6 +559,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { return this; } + @Override + public LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authenticationSession) { + this.authenticationSession = authenticationSession; + return this; + } + @Override public FreeMarkerLoginFormsProvider setUser(UserModel user) { this.user = user; @@ -571,7 +584,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { } @Override - public LoginFormsProvider setAccessRequest(List realmRolesRequested, MultivaluedMap resourceRolesRequested, List protocolMappersRequested) { + public LoginFormsProvider setAccessRequest(List realmRolesRequested, MultivaluedMap resourceRolesRequested, + List protocolMappersRequested) { this.realmRolesRequested = realmRolesRequested; this.resourceRolesRequested = resourceRolesRequested; this.protocolMappersRequested = protocolMappersRequested; diff --git a/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java index e259738de6..b4e037b337 100644 --- a/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java +++ b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java @@ -64,6 +64,10 @@ public class RestartLoginCookie { @JsonProperty("notes") protected Map notes = new HashMap<>(); + @Deprecated // Backwards compatibility + @JsonProperty("cs") + protected String cs; + public Map getNotes() { return notes; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index f00c89237d..5fa46a0da1 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -153,7 +153,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { action = Action.REGISTER; if (!realm.isRegistrationAllowed()) { - throw new ErrorPageException(session, Messages.REGISTRATION_NOT_ALLOWED); + throw new ErrorPageException(session, authenticationSession, Messages.REGISTRATION_NOT_ALLOWED); } return this; @@ -164,7 +164,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { action = Action.FORGOT_CREDENTIALS; if (!realm.isResetPasswordAllowed()) { - throw new ErrorPageException(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED); + throw new ErrorPageException(session, authenticationSession, Messages.RESET_CREDENTIAL_NOT_ALLOWED); } return this; @@ -173,7 +173,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { private void checkClient(String clientId) { if (clientId == null) { event.error(Errors.INVALID_REQUEST); - throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM); + throw new ErrorPageException(session, authenticationSession, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM); } event.client(clientId); @@ -181,17 +181,17 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { client = realm.getClientByClientId(clientId); if (client == null) { event.error(Errors.CLIENT_NOT_FOUND); - throw new ErrorPageException(session, Messages.CLIENT_NOT_FOUND); + throw new ErrorPageException(session, authenticationSession, Messages.CLIENT_NOT_FOUND); } if (!client.isEnabled()) { event.error(Errors.CLIENT_DISABLED); - throw new ErrorPageException(session, Messages.CLIENT_DISABLED); + throw new ErrorPageException(session, authenticationSession, Messages.CLIENT_DISABLED); } if (client.isBearerOnly()) { event.error(Errors.NOT_ALLOWED); - throw new ErrorPageException(session, Messages.BEARER_ONLY); + throw new ErrorPageException(session, authenticationSession, Messages.BEARER_ONLY); } session.getContext().setClient(client); @@ -354,7 +354,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUriParam, realm, client, isOIDCRequest); if (redirectUri == null) { event.error(Errors.INVALID_REDIRECT_URI); - throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM); + throw new ErrorPageException(session, authenticationSession, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM); } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java index fd500250ce..4c58f3aace 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java @@ -107,7 +107,7 @@ public class LogoutEndpoint { event.event(EventType.LOGOUT); event.detail(Details.REDIRECT_URI, redirect); event.error(Errors.INVALID_REDIRECT_URI); - return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI); + return ErrorPage.error(session, null, Messages.INVALID_REDIRECT_URI); } redirect = validatedUri; } @@ -120,7 +120,7 @@ public class LogoutEndpoint { } catch (OAuthErrorException e) { event.event(EventType.LOGOUT); event.error(Errors.INVALID_TOKEN); - return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE); + return ErrorPage.error(session, null, Messages.SESSION_NOT_ACTIVE); } } diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java index 66a7609568..8d42b3ecce 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java @@ -173,7 +173,7 @@ public class SamlProtocol implements LoginProtocol { URI redirect = builder.buildFromMap(params); return Response.status(302).location(redirect).build(); } else { - return ErrorPage.error(session, translateErrorToIdpInitiatedErrorMessage(error)); + return ErrorPage.error(session, authSession, translateErrorToIdpInitiatedErrorMessage(error)); } } else { SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(authSession.getRedirectUri()).issuer(getResponseIssuer(realm)).status(translateErrorToSAMLStatus(error).get()); @@ -196,7 +196,7 @@ public class SamlProtocol implements LoginProtocol { Document document = builder.buildDocument(); return buildErrorResponse(authSession, binding, document); } catch (Exception e) { - return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE); + return ErrorPage.error(session, authSession, Messages.FAILED_TO_PROCESS_RESPONSE); } } } finally { @@ -427,7 +427,7 @@ public class SamlProtocol implements LoginProtocol { samlDocument = builder.buildDocument(samlModel); } catch (Exception e) { logger.error("failed", e); - return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE); + return ErrorPage.error(session, null, Messages.FAILED_TO_PROCESS_RESPONSE); } JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder(); @@ -453,7 +453,7 @@ public class SamlProtocol implements LoginProtocol { publicKey = SamlProtocolUtils.getEncryptionKey(client); } catch (Exception e) { logger.error("failed", e); - return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE); + return ErrorPage.error(session, null, Messages.FAILED_TO_PROCESS_RESPONSE); } bindingBuilder.encrypt(publicKey); } @@ -461,7 +461,7 @@ public class SamlProtocol implements LoginProtocol { return buildAuthenticatedResponse(clientSession, redirectUri, samlDocument, bindingBuilder); } catch (Exception e) { logger.error("failed", e); - return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE); + return ErrorPage.error(session, null, Messages.FAILED_TO_PROCESS_RESPONSE); } } @@ -568,7 +568,7 @@ public class SamlProtocol implements LoginProtocol { String logoutBindingUri = userSession.getNote(SAML_LOGOUT_BINDING_URI); if (logoutBindingUri == null) { logger.error("Can't finish SAML logout as there is no logout binding set. Please configure the logout service url in the admin console for your client applications."); - return ErrorPage.error(session, Messages.FAILED_LOGOUT); + return ErrorPage.error(session, null, Messages.FAILED_LOGOUT); } String logoutRelayState = userSession.getNote(SAML_LOGOUT_RELAY_STATE); diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java index 8cbde8ed79..afea781ded 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -118,18 +118,18 @@ public class SamlService extends AuthorizationEndpointBase { if (!checkSsl()) { event.event(EventType.LOGIN); event.error(Errors.SSL_REQUIRED); - return ErrorPage.error(session, Messages.HTTPS_REQUIRED); + return ErrorPage.error(session, null, Messages.HTTPS_REQUIRED); } if (!realm.isEnabled()) { event.event(EventType.LOGIN_ERROR); event.error(Errors.REALM_DISABLED); - return ErrorPage.error(session, Messages.REALM_NOT_ENABLED); + return ErrorPage.error(session, null, Messages.REALM_NOT_ENABLED); } if (samlRequest == null && samlResponse == null) { event.event(EventType.LOGIN); event.error(Errors.INVALID_TOKEN); - return ErrorPage.error(session, Messages.INVALID_REQUEST); + return ErrorPage.error(session, null, Messages.INVALID_REQUEST); } return null; @@ -142,7 +142,7 @@ public class SamlService extends AuthorizationEndpointBase { if (! (holder.getSamlObject() instanceof StatusResponseType)) { event.detail(Details.REASON, "invalid_saml_response"); event.error(Errors.INVALID_SAML_RESPONSE); - return ErrorPage.error(session, Messages.INVALID_REQUEST); + return ErrorPage.error(session, null, Messages.INVALID_REQUEST); } StatusResponseType statusResponse = (StatusResponseType) holder.getSamlObject(); @@ -150,7 +150,7 @@ public class SamlService extends AuthorizationEndpointBase { if (statusResponse.getDestination() != null && !uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) { event.detail(Details.REASON, "invalid_destination"); event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE); - return ErrorPage.error(session, Messages.INVALID_REQUEST); + return ErrorPage.error(session, null, Messages.INVALID_REQUEST); } AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, false); @@ -158,7 +158,7 @@ public class SamlService extends AuthorizationEndpointBase { logger.warn("Unknown saml response."); event.event(EventType.LOGOUT); event.error(Errors.INVALID_TOKEN); - return ErrorPage.error(session, Messages.INVALID_REQUEST); + return ErrorPage.error(session, null, Messages.INVALID_REQUEST); } // assume this is a logout response UserSessionModel userSession = authResult.getSession(); @@ -167,7 +167,7 @@ public class SamlService extends AuthorizationEndpointBase { logger.warn("UserSession is not tagged as logging out."); event.event(EventType.LOGOUT); event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE); - return ErrorPage.error(session, Messages.INVALID_REQUEST); + return ErrorPage.error(session, null, Messages.INVALID_REQUEST); } logger.debug("logout response"); Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers); @@ -180,7 +180,7 @@ public class SamlService extends AuthorizationEndpointBase { if (documentHolder == null) { event.event(EventType.LOGIN); event.error(Errors.INVALID_TOKEN); - return ErrorPage.error(session, Messages.INVALID_REQUEST); + return ErrorPage.error(session, null, Messages.INVALID_REQUEST); } SAML2Object samlObject = documentHolder.getSamlObject(); @@ -188,7 +188,7 @@ public class SamlService extends AuthorizationEndpointBase { if (! (samlObject instanceof RequestAbstractType)) { event.event(EventType.LOGIN); event.error(Errors.INVALID_SAML_AUTHN_REQUEST); - return ErrorPage.error(session, Messages.INVALID_REQUEST); + return ErrorPage.error(session, null, Messages.INVALID_REQUEST); } RequestAbstractType requestAbstractType = (RequestAbstractType) samlObject; @@ -199,23 +199,23 @@ public class SamlService extends AuthorizationEndpointBase { event.event(EventType.LOGIN); event.client(issuer); event.error(Errors.CLIENT_NOT_FOUND); - return ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER); + return ErrorPage.error(session, null, Messages.UNKNOWN_LOGIN_REQUESTER); } if (!client.isEnabled()) { event.event(EventType.LOGIN); event.error(Errors.CLIENT_DISABLED); - return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED); + return ErrorPage.error(session, null, Messages.LOGIN_REQUESTER_NOT_ENABLED); } if (client.isBearerOnly()) { event.event(EventType.LOGIN); event.error(Errors.NOT_ALLOWED); - return ErrorPage.error(session, Messages.BEARER_ONLY); + return ErrorPage.error(session, null, Messages.BEARER_ONLY); } if (!client.isStandardFlowEnabled()) { event.event(EventType.LOGIN); event.error(Errors.NOT_ALLOWED); - return ErrorPage.error(session, Messages.STANDARD_FLOW_DISABLED); + return ErrorPage.error(session, null, Messages.STANDARD_FLOW_DISABLED); } session.getContext().setClient(client); @@ -226,7 +226,7 @@ public class SamlService extends AuthorizationEndpointBase { SamlService.logger.error("request validation failed", e); event.event(EventType.LOGIN); event.error(Errors.INVALID_SIGNATURE); - return ErrorPage.error(session, Messages.INVALID_REQUESTER); + return ErrorPage.error(session, null, Messages.INVALID_REQUESTER); } logger.debug("verified request"); if (samlObject instanceof AuthnRequestType) { @@ -244,7 +244,7 @@ public class SamlService extends AuthorizationEndpointBase { } else { event.event(EventType.LOGIN); event.error(Errors.INVALID_TOKEN); - return ErrorPage.error(session, Messages.INVALID_REQUEST); + return ErrorPage.error(session, null, Messages.INVALID_REQUEST); } } @@ -260,12 +260,12 @@ public class SamlService extends AuthorizationEndpointBase { if (requestAbstractType.getDestination() == null && samlClient.requiresClientSignature()) { event.detail(Details.REASON, "invalid_destination"); event.error(Errors.INVALID_SAML_AUTHN_REQUEST); - return ErrorPage.error(session, Messages.INVALID_REQUEST); + return ErrorPage.error(session, null, Messages.INVALID_REQUEST); } if (! isValidDestination(requestAbstractType.getDestination())) { event.detail(Details.REASON, "invalid_destination"); event.error(Errors.INVALID_SAML_AUTHN_REQUEST); - return ErrorPage.error(session, Messages.INVALID_REQUEST); + return ErrorPage.error(session, null, Messages.INVALID_REQUEST); } String bindingType = getBindingType(requestAbstractType); if (samlClient.forcePostBinding()) @@ -288,7 +288,7 @@ public class SamlService extends AuthorizationEndpointBase { if (redirect == null) { event.error(Errors.INVALID_REDIRECT_URI); - return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI); + return ErrorPage.error(session, null, Messages.INVALID_REDIRECT_URI); } AuthorizationEndpointChecks checks = getOrCreateAuthenticationSession(client, relayState); @@ -316,7 +316,7 @@ public class SamlService extends AuthorizationEndpointBase { } else { event.detail(Details.REASON, "unsupported_nameid_format"); event.error(Errors.INVALID_SAML_AUTHN_REQUEST); - return ErrorPage.error(session, Messages.UNSUPPORTED_NAME_ID_FORMAT); + return ErrorPage.error(session, null, Messages.UNSUPPORTED_NAME_ID_FORMAT); } } @@ -367,12 +367,12 @@ public class SamlService extends AuthorizationEndpointBase { if (logoutRequest.getDestination() == null && samlClient.requiresClientSignature()) { event.detail(Details.REASON, "invalid_destination"); event.error(Errors.INVALID_SAML_LOGOUT_REQUEST); - return ErrorPage.error(session, Messages.INVALID_REQUEST); + return ErrorPage.error(session, null, Messages.INVALID_REQUEST); } if (! isValidDestination(logoutRequest.getDestination())) { event.detail(Details.REASON, "invalid_destination"); event.error(Errors.INVALID_SAML_LOGOUT_REQUEST); - return ErrorPage.error(session, Messages.INVALID_REQUEST); + return ErrorPage.error(session, null, Messages.INVALID_REQUEST); } // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways. @@ -620,16 +620,16 @@ public class SamlService extends AuthorizationEndpointBase { } if (client == null) { event.error(Errors.CLIENT_NOT_FOUND); - return ErrorPage.error(session, Messages.CLIENT_NOT_FOUND); + return ErrorPage.error(session, null, Messages.CLIENT_NOT_FOUND); } if (!client.isEnabled()) { event.error(Errors.CLIENT_DISABLED); - return ErrorPage.error(session, Messages.CLIENT_DISABLED); + return ErrorPage.error(session, null, Messages.CLIENT_DISABLED); } if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) == null) { logger.error("SAML assertion consumer url not set up"); event.error(Errors.INVALID_REDIRECT_URI); - return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI); + return ErrorPage.error(session, null, Messages.INVALID_REDIRECT_URI); } AuthenticationSessionModel authSession = getOrCreateLoginSessionForIdpInitiatedSso(this.session, this.realm, client, relayState); diff --git a/services/src/main/java/org/keycloak/services/ErrorPage.java b/services/src/main/java/org/keycloak/services/ErrorPage.java index fb52ea4cca..291caaa14c 100755 --- a/services/src/main/java/org/keycloak/services/ErrorPage.java +++ b/services/src/main/java/org/keycloak/services/ErrorPage.java @@ -18,6 +18,7 @@ package org.keycloak.services; import org.keycloak.forms.login.LoginFormsProvider; import org.keycloak.models.KeycloakSession; +import org.keycloak.sessions.AuthenticationSessionModel; import javax.ws.rs.core.Response; @@ -26,8 +27,8 @@ import javax.ws.rs.core.Response; */ public class ErrorPage { - public static Response error(KeycloakSession session, String message, Object... parameters) { - return session.getProvider(LoginFormsProvider.class).setError(message, parameters).createErrorPage(); + public static Response error(KeycloakSession session, AuthenticationSessionModel authenticationSession, String message, Object... parameters) { + return session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authenticationSession).setError(message, parameters).createErrorPage(); } diff --git a/services/src/main/java/org/keycloak/services/ErrorPageException.java b/services/src/main/java/org/keycloak/services/ErrorPageException.java index 51ee9c84dc..b006718069 100644 --- a/services/src/main/java/org/keycloak/services/ErrorPageException.java +++ b/services/src/main/java/org/keycloak/services/ErrorPageException.java @@ -18,6 +18,7 @@ package org.keycloak.services; import org.keycloak.models.KeycloakSession; +import org.keycloak.sessions.AuthenticationSessionModel; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; @@ -30,18 +31,28 @@ public class ErrorPageException extends WebApplicationException { private final KeycloakSession session; private final String errorMessage; private final Object[] parameters; + private final AuthenticationSessionModel authSession; + public ErrorPageException(KeycloakSession session, String errorMessage, Object... parameters) { this.session = session; this.errorMessage = errorMessage; this.parameters = parameters; + this.authSession = null; + } + + public ErrorPageException(KeycloakSession session, AuthenticationSessionModel authSession, String errorMessage, Object... parameters) { + this.session = session; + this.errorMessage = errorMessage; + this.parameters = parameters; + this.authSession = authSession; } @Override public Response getResponse() { - return ErrorPage.error(session, errorMessage, parameters); + return ErrorPage.error(session, authSession, errorMessage, parameters); } } diff --git a/services/src/main/java/org/keycloak/services/ServicesLogger.java b/services/src/main/java/org/keycloak/services/ServicesLogger.java index 2d6c473842..98420abc29 100644 --- a/services/src/main/java/org/keycloak/services/ServicesLogger.java +++ b/services/src/main/java/org/keycloak/services/ServicesLogger.java @@ -451,4 +451,8 @@ public interface ServicesLogger extends BasicLogger { @Message(id=102, value= "URL '%s' doesn't match any trustedHost or trustedDomain") void urlDoesntMatch(String url); + @LogMessage(level = DEBUG) + @Message(id=103, value="Failed to reset password. User is temporarily disabled") + void passwordResetFailed(@Cause Throwable t); + } diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 19c188a008..981a47f9c9 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -710,7 +710,7 @@ public class AuthenticationManager { } if (authSession.getAuthNote(END_AFTER_REQUIRED_ACTIONS) != null) { - LoginFormsProvider infoPage = session.getProvider(LoginFormsProvider.class) + LoginFormsProvider infoPage = session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authSession) .setSuccess(Messages.ACCOUNT_UPDATED); if (authSession.getAuthNote(SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS) != null) { if (authSession.getRedirectUri() != null) { @@ -848,6 +848,7 @@ public class AuthenticationManager { authSession.setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution); return session.getProvider(LoginFormsProvider.class) + .setAuthenticationSession(authSession) .setExecution(execution) .setClientSessionCode(accessCode.getOrGenerateCode()) .setAccessRequest(realmRoles, resourceRoles, protocolMappers) diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index 20e23de99b..078cca776b 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -317,12 +317,12 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal return response; } } catch (IdentityBrokerException e) { - return redirectToErrorPage(Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerId); + return redirectToErrorPage(authSession, Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerId); } catch (Exception e) { - return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_REQUEST, e, providerId); + return redirectToErrorPage(authSession, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST, e, providerId); } - return redirectToErrorPage(Messages.COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST); + return redirectToErrorPage(authSession, Messages.COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST); } @@ -545,7 +545,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal return Response.status(302).location(redirect).build(); } else { - Response response = validateUser(federatedUser, realmModel); + Response response = validateUser(authenticationSession, federatedUser, realmModel); if (response != null) { return response; } @@ -558,15 +558,15 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal } - public Response validateUser(UserModel user, RealmModel realm) { + public Response validateUser(AuthenticationSessionModel authSession, UserModel user, RealmModel realm) { if (!user.isEnabled()) { event.error(Errors.USER_DISABLED); - return ErrorPage.error(session, Messages.ACCOUNT_DISABLED); + return ErrorPage.error(session, authSession, Messages.ACCOUNT_DISABLED); } if (realm.isBruteForceProtected()) { if (session.getProvider(BruteForceProtector.class).isTemporarilyDisabled(session, realm, user)) { event.error(Errors.USER_TEMPORARILY_DISABLED); - return ErrorPage.error(session, Messages.ACCOUNT_DISABLED); + return ErrorPage.error(session, authSession, Messages.ACCOUNT_DISABLED); } } return null; @@ -670,7 +670,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal return finishOrRedirectToPostBrokerLogin(authSession, context, true, clientSessionCode); } catch (Exception e) { - return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e); + return redirectToErrorPage(authSession,Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e); } } @@ -734,7 +734,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal return afterPostBrokerLoginFlowSuccess(authenticationSession, context, wasFirstBrokerLogin, parsedCode.clientSessionCode); } catch (IdentityBrokerException e) { - return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e); + return redirectToErrorPage(authenticationSession, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e); } } @@ -752,7 +752,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realmModel, authSession); if (!linkingUser.getId().equals(federatedUser.getId())) { - return redirectToErrorPage(Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, federatedUser.getUsername(), linkingUser.getUsername()); + return redirectToErrorPage(authSession, Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, federatedUser.getUsername(), linkingUser.getUsername()); } SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE); @@ -866,7 +866,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal } if (!authenticatedUser.hasRole(this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getRole(AccountRoles.MANAGE_ACCOUNT))) { - return redirectToErrorPage(Messages.INSUFFICIENT_PERMISSION); + return redirectToErrorPage(authSession, Messages.INSUFFICIENT_PERMISSION); } if (!authenticatedUser.isEnabled()) { @@ -919,7 +919,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal if (authSession.getClient() != null && authSession.getClient().getClientId().equals(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)) { return redirectToAccountErrorPage(authSession, message, parameters); } else { - return redirectToErrorPage(message, parameters); // Should rather redirect to app instead and display error here? + return redirectToErrorPage(authSession, message, parameters); // Should rather redirect to app instead and display error here? } } @@ -1057,11 +1057,15 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal return Urls.identityProviderAuthnResponse(this.uriInfo.getBaseUri(), providerId, this.realmModel.getName()).toString(); } + private Response redirectToErrorPage(AuthenticationSessionModel authSession,String message, Object ... parameters) { + return redirectToErrorPage(authSession, message, null, parameters); + } + private Response redirectToErrorPage(String message, Object ... parameters) { - return redirectToErrorPage(message, null, parameters); + return redirectToErrorPage(null, message, null, parameters); } - private Response redirectToErrorPage(String message, Throwable throwable, Object ... parameters) { + private Response redirectToErrorPage(AuthenticationSessionModel authSession, String message, Throwable throwable, Object ... parameters) { if (message == null) { message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR; } @@ -1073,7 +1077,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal return webEx.getResponse(); } - return ErrorPage.error(this.session, message, parameters); + return ErrorPage.error(this.session, authSession, message, parameters); } private Response redirectToAccountErrorPage(AuthenticationSessionModel authSession, String message, Object ... parameters) { diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index c4ba764843..2117d280ad 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -340,7 +340,7 @@ public class LoginActionsService { if (!realm.isResetPasswordAllowed()) { event.event(EventType.RESET_PASSWORD); event.error(Errors.NOT_ALLOWED); - return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED); + return ErrorPage.error(session, authSession, Messages.RESET_CREDENTIAL_NOT_ALLOWED); } authSession = createAuthenticationSessionForClient(); @@ -384,7 +384,7 @@ public class LoginActionsService { if (!realm.isResetPasswordAllowed()) { event.error(Errors.NOT_ALLOWED); - return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED); + return ErrorPage.error(session, authSession, Messages.RESET_CREDENTIAL_NOT_ALLOWED); } @@ -553,7 +553,7 @@ public class LoginActionsService { } else if (RESET_CREDENTIALS_PATH.equals(flowPath)) { return processResetCredentials(false, null, authSession, errorMessage); } else { - return ErrorPage.error(session, errorMessage == null ? Messages.INVALID_REQUEST : errorMessage); + return ErrorPage.error(session, authSession, errorMessage == null ? Messages.INVALID_REQUEST : errorMessage); } } @@ -577,7 +577,7 @@ public class LoginActionsService { event .detail(Details.REASON, ex == null ? "" : ex.getMessage()) .error(eventError == null ? Errors.INVALID_CODE : eventError); - return ErrorPage.error(session, errorMessage == null ? Messages.INVALID_CODE : errorMessage); + return ErrorPage.error(session, null, errorMessage == null ? Messages.INVALID_CODE : errorMessage); } protected Response processResetCredentials(boolean actionRequest, String execution, AuthenticationSessionModel authSession, String errorMessage) { @@ -626,7 +626,7 @@ public class LoginActionsService { event.event(EventType.REGISTER); if (!realm.isRegistrationAllowed()) { event.error(Errors.REGISTRATION_DISABLED); - return ErrorPage.error(session, Messages.REGISTRATION_NOT_ALLOWED); + return ErrorPage.error(session, null, Messages.REGISTRATION_NOT_ALLOWED); } SessionCodeChecks checks = checksForCode(code, execution, clientId, REGISTRATION_PATH); @@ -692,7 +692,7 @@ public class LoginActionsService { SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, noteKey); if (serializedCtx == null) { ServicesLogger.LOGGER.notFoundSerializedCtxInClientSession(noteKey); - throw new WebApplicationException(ErrorPage.error(session, "Not found serialized context in authenticationSession.")); + throw new WebApplicationException(ErrorPage.error(session, authSession, "Not found serialized context in authenticationSession.")); } BrokeredIdentityContext brokerContext = serializedCtx.deserialize(session, authSession); final String identityProviderAlias = brokerContext.getIdpConfig().getAlias(); @@ -700,12 +700,12 @@ public class LoginActionsService { String flowId = firstBrokerLogin ? brokerContext.getIdpConfig().getFirstBrokerLoginFlowId() : brokerContext.getIdpConfig().getPostBrokerLoginFlowId(); if (flowId == null) { ServicesLogger.LOGGER.flowNotConfigForIDP(identityProviderAlias); - throw new WebApplicationException(ErrorPage.error(session, "Flow not configured for identity provider")); + throw new WebApplicationException(ErrorPage.error(session, authSession, "Flow not configured for identity provider")); } AuthenticationFlowModel brokerLoginFlow = realm.getAuthenticationFlowById(flowId); if (brokerLoginFlow == null) { ServicesLogger.LOGGER.flowNotFoundForIDP(flowId, identityProviderAlias); - throw new WebApplicationException(ErrorPage.error(session, "Flow not found for identity provider")); + throw new WebApplicationException(ErrorPage.error(session, authSession, "Flow not found for identity provider")); } event.detail(Details.IDENTITY_PROVIDER, identityProviderAlias) @@ -886,7 +886,7 @@ public class LoginActionsService { if (factory == null) { ServicesLogger.LOGGER.actionProviderNull(); event.error(Errors.INVALID_CODE); - throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE)); + throw new WebApplicationException(ErrorPage.error(session, authSession, Messages.INVALID_CODE)); } RequiredActionProvider provider = factory.create(session); diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java index e330d29b53..345f64a137 100644 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java @@ -116,7 +116,7 @@ public class LoginActionsServiceChecks { UserSessionModel userSession = context.getSession().sessions().getUserSession(context.getRealm(), authSessionId); if (userSession != null) { - LoginFormsProvider loginForm = context.getSession().getProvider(LoginFormsProvider.class) + LoginFormsProvider loginForm = context.getSession().getProvider(LoginFormsProvider.class).setAuthenticationSession(context.getAuthenticationSession()) .setSuccess(Messages.ALREADY_LOGGED_IN); if (context.getSession().getContext().getClient() == null) { diff --git a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java index 3f68dd3272..321915a2e0 100644 --- a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java +++ b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java @@ -123,12 +123,12 @@ public class SessionCodeChecks { // Basic realm checks if (!checkSsl()) { event.error(Errors.SSL_REQUIRED); - response = ErrorPage.error(session, Messages.HTTPS_REQUIRED); + response = ErrorPage.error(session, null, Messages.HTTPS_REQUIRED); return null; } if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - response = ErrorPage.error(session, Messages.REALM_NOT_ENABLED); + response = ErrorPage.error(session, null, Messages.REALM_NOT_ENABLED); return null; } @@ -154,7 +154,7 @@ public class SessionCodeChecks { UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId); if (userSession != null) { - LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class) + LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authSession) .setSuccess(Messages.ALREADY_LOGGED_IN); if (client == null) { @@ -190,7 +190,7 @@ public class SessionCodeChecks { ClientModel client = authSession.getClient(); if (client == null) { event.error(Errors.CLIENT_NOT_FOUND); - response = ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER); + response = ErrorPage.error(session, authSession, Messages.UNKNOWN_LOGIN_REQUESTER); clientCode.removeExpiredClientSession(); return false; } @@ -200,7 +200,7 @@ public class SessionCodeChecks { if (!client.isEnabled()) { event.error(Errors.CLIENT_DISABLED); - response = ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED); + response = ErrorPage.error(session,authSession, Messages.LOGIN_REQUESTER_NOT_ENABLED); clientCode.removeExpiredClientSession(); return false; } @@ -285,7 +285,7 @@ public class SessionCodeChecks { return false; } else { logger.errorf("Bad action. Expected action '%s', current action '%s'", expectedAction, authSession.getAction()); - response = ErrorPage.error(session, Messages.EXPIRED_CODE); + response = ErrorPage.error(session, authSession, Messages.EXPIRED_CODE); return false; } } @@ -370,7 +370,7 @@ public class SessionCodeChecks { } else { // Finally need to show error as all the fallbacks failed event.error(Errors.INVALID_CODE); - return ErrorPage.error(session, Messages.INVALID_CODE); + return ErrorPage.error(session, authSession, Messages.INVALID_CODE); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index ebc89bea62..3d09f93b8c 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -833,6 +833,9 @@ public class RealmAdminResource { if (user.getEmail() == null) { return ErrorResponse.error("Logged in user does not have an e-mail.", Response.Status.INTERNAL_SERVER_ERROR); } + if (ComponentRepresentation.SECRET_VALUE.equals(settings.get("password"))) { + settings.put("password", realm.getSmtpConfig().get("password")); + } session.getProvider(EmailTemplateProvider.class).sendSmtpTestEmail(settings, user); } catch (Exception e) { e.printStackTrace(); diff --git a/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java b/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java index 489f73f584..9a9c802ef2 100644 --- a/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java +++ b/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java @@ -56,7 +56,7 @@ public class AuthenticationFlowURLHelper { logger.debugf("Redirecting to 'page expired' now. Will use last step URL: %s", lastStepUrl); - return session.getProvider(LoginFormsProvider.class) + return session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authSession) .setActionUri(lastStepUrl) .setExecution(getExecutionId(authSession)) .createLoginExpiredPage(); diff --git a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java index 1f456cfc31..afcbf4ecdc 100755 --- a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java +++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java @@ -191,14 +191,13 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider3.5.3 2.4.2 2.3.1 - 2.1.0.Beta1 + 2.1.0.Final 1.0.1.Final 1.2.0.Beta2 2.2.6 @@ -56,7 +56,7 @@ 1.9.8.Final 2.2.1.Final 2.5.5.Final - + 1.8 1.8 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/common/install-adapters.sh b/testsuite/integration-arquillian/servers/app-server/jboss/common/install-adapters.sh index bd9e65b25e..d170a1ea7d 100755 --- a/testsuite/integration-arquillian/servers/app-server/jboss/common/install-adapters.sh +++ b/testsuite/integration-arquillian/servers/app-server/jboss/common/install-adapters.sh @@ -26,6 +26,8 @@ do if [ "$ELYTRON_SUPPORTED" = true ]; then ./jboss-cli.sh -c --file="adapter-elytron-install.cli" + else + ./jboss-cli.sh -c --command="/subsystem=elytron:remove" fi if [ $? -ne 0 ]; then RESULT=1; fi diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/crossdc/cross-dc-setup.cli b/testsuite/integration-arquillian/servers/auth-server/jboss/common/crossdc/cross-dc-setup.cli new file mode 100644 index 0000000000..836cde1d82 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/crossdc/cross-dc-setup.cli @@ -0,0 +1,129 @@ +embed-server --server-config=standalone-ha.xml + +echo **** Begin **** + +echo *** Update jgoups subsystem *** +/subsystem=jgroups/stack=udp/transport=UDP:write-attribute(name=site, value=${jboss.site.name}) + +echo *** Update infinispan subsystem *** +/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan) + +echo ** Update replicated-cache work element ** +/subsystem=infinispan/cache-container=keycloak/replicated-cache=work/store=custom:add( \ + class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \ + passivation=false, \ + fetch-state=false, \ + purge=false, \ + preload=false, \ + shared=true \ +) + +/subsystem=infinispan/cache-container=keycloak/replicated-cache=work/store=custom:write-attribute( \ + name=properties, value={ \ + rawValues=true, \ + marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, \ + transportFactory=org.keycloak.models.sessions.infinispan.remotestore.KeycloakTcpTransportFactory, \ + remoteServers=localhost:${remote.cache.port}, \ + remoteCacheName=work, \ + sessionCache=false \ + } \ +) + +/subsystem=infinispan/cache-container=keycloak/replicated-cache=work:write-attribute(name=statistics-enabled,value=true) + +echo ** Update distributed-cache sessions element ** +/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions/store=custom:add( \ + class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \ + passivation=false, \ + fetch-state=false, \ + purge=false, \ + preload=false, \ + shared=true \ +) + +/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions/store=custom:write-attribute( \ + name=properties, value={ \ + remoteCacheName=sessions, \ + useConfigTemplateFromCache=work, \ + sessionCache=true \ + } \ +) + +/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=statistics-enabled,value=true) + +echo ** Update distributed-cache offlineSessions element ** +/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions/store=custom:add( \ + class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \ + passivation=false, \ + fetch-state=false, \ + purge=false, \ + preload=false, \ + shared=true \ +) + +/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions/store=custom:write-attribute( \ + name=properties, value={ \ + remoteCacheName=offlineSessions, \ + useConfigTemplateFromCache=work, \ + sessionCache=true \ + } \ +) + +/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=statistics-enabled,value=true) + +echo ** Update distributed-cache loginFailures element ** +/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=custom:add( \ + class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \ + passivation=false, \ + fetch-state=false, \ + purge=false, \ + preload=false, \ + shared=true \ +) + +/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=custom:write-attribute( \ + name=properties, value={ \ + remoteCacheName=loginFailures, \ + useConfigTemplateFromCache=work, \ + sessionCache=true \ + } \ +) + +/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=statistics-enabled,value=true) + +echo ** Update distributed-cache actionTokens element ** +/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/store=custom:add( \ + class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \ + passivation=false, \ + fetch-state=false, \ + purge=false, \ + preload=true, \ + shared=true \ +) + +/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/store=custom:write-attribute( \ + name=properties, value={ \ + remoteCacheName=actionTokens, \ + useConfigTemplateFromCache=work, \ + sessionCache=false \ + } \ +) + +/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=statistics-enabled,value=true) + +echo ** Update distributed-cache authenticationSessions element ** +/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true) + +echo *** Enable debug logging *** +/subsystem=logging/logger=org.keycloak.cluster.infinispan:add(level=DEBUG) + +/subsystem=logging/logger=org.keycloak.connections.infinispan:add(level=DEBUG) + +/subsystem=logging/logger=org.keycloak.models.cache.infinispan:add(level=DEBUG) + +/subsystem=logging/logger=org.keycloak.models.sessions.infinispan:add(level=DEBUG) + +echo *** Update undertow subsystem *** +/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true) + +echo **** End **** diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/eap/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/eap/pom.xml index 47e6a7fc9f..95b33ba2c0 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/eap/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/eap/pom.xml @@ -26,11 +26,11 @@ 4.0.0 pom - + integration-arquillian-servers-auth-server-eap - + Auth Server - JBoss - EAP - + eap ${project.build.directory}/unpacked/${product.unpacked.folder.name} @@ -57,6 +57,7 @@ enforce + false product.version @@ -71,5 +72,5 @@ - + diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml index ae7d87a657..26a3323144 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml @@ -191,6 +191,7 @@ ${auth.server.home}/standalone/configuration standalone.xml + standalone-ha.xml ${common.resources}/keycloak-server-subsystem.xsl ${auth.server.home}/standalone/configuration @@ -575,6 +576,120 @@ + + + auth-servers-crossdc-jboss + + jdbc:h2:tcp://localhost:9092/mem:keycloak-dc-shared;DB_CLOSE_DELAY=-1 + + + + + maven-enforcer-plugin + + true + + + + + + + maven-enforcer-plugin + + + enforce-profile-activation + + enforce + + + + + auth.server.jboss + Profile "auth-servers-crossdc-jboss" requires activation of another profile: either "auth-server-wildfly" or "auth-server-eap". + (wildfly|eap) + + + + + + + + org.codehaus.mojo + xml-maven-plugin + + + jpa-h2-tcp + process-resources + + transform + + + ${skip.h2.tcp} + + + ${auth.server.home}/standalone/configuration + + standalone-ha.xml + + ${common.resources}/datasource-jdbc-url.xsl + ${auth.server.home}/standalone/configuration + + + pool.name + KeycloakDS + + + jdbc.url + ${crossdc.jboss.jdbc.url} + + + + + + + + + + org.codehaus.mojo + exec-maven-plugin + + + crossdc-setup + process-resources + + exec + + + ${auth.server.home}/bin/jboss-cli.sh + + --file=${common.resources}/crossdc/cross-dc-setup.cli + + + + + remove-temp-data-crossdc-setup + process-resources + + exec + + + ${auth.server.home}/standalone/ + rm + + -rf + data + log + tmp + + + + + + + + + + auth-server-cluster diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/wildfly/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/wildfly/pom.xml index c4f8f525aa..ab6f60fa90 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/wildfly/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/wildfly/pom.xml @@ -26,9 +26,9 @@ 4.0.0 pom - + integration-arquillian-servers-auth-server-wildfly - + Auth Server - JBoss - Wildfly @@ -38,9 +38,20 @@ zip - + wildfly - + + + + + maven-enforcer-plugin + + false + + + + + diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml index 1e4b3f762c..467601a3f9 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml @@ -33,6 +33,7 @@ + diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java index 3da888afe3..26be8f79ae 100644 --- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java +++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java @@ -17,7 +17,12 @@ package org.keycloak.testsuite.arquillian.undertow.lb; +import java.lang.reflect.Field; import java.net.URI; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -34,6 +39,7 @@ import io.undertow.server.handlers.proxy.ProxyHandler; import io.undertow.util.AttachmentKey; import io.undertow.util.Headers; import org.jboss.logging.Logger; +import org.keycloak.common.util.reflections.Reflections; import org.keycloak.services.managers.AuthenticationSessionManager; import java.util.LinkedHashMap; import java.util.StringTokenizer; @@ -94,7 +100,7 @@ public class SimpleUndertowLoadBalancer { .build(); undertow.start(); - log.infof("Loadbalancer started and ready to serve requests on http://%s:%d", host, port); + log.infof("#### Loadbalancer started and ready to serve requests on http://%s:%d ####", host, port); } catch (Exception e) { throw new RuntimeException(e); } @@ -110,11 +116,12 @@ public class SimpleUndertowLoadBalancer { lb.removeHost(uri); lb.addHost(uri, route); }); + log.infof("Load balancer: enable all nodes. All enabled nodes: %s", lb.toString()); } public void disableAllBackendNodes() { - log.debugf("Load balancer: disabling all nodes"); backendNodes.values().forEach(lb::removeHost); + log.infof("Load balancer: disabling all nodes"); } public void enableBackendNodeByName(String nodeName) { @@ -122,8 +129,8 @@ public class SimpleUndertowLoadBalancer { if (uri == null) { throw new IllegalArgumentException("Invalid node: " + nodeName); } - log.debugf("Load balancer: enabling node %s", nodeName); lb.addHost(uri, nodeName); + log.infof("Load balancer: enabled node '%s', All enabled nodes: %s", nodeName, lb.toString()); } public void disableBackendNodeByName(String nodeName) { @@ -131,8 +138,8 @@ public class SimpleUndertowLoadBalancer { if (uri == null) { throw new IllegalArgumentException("Invalid node: " + nodeName); } - log.debugf("Load balancer: disabling node %s", nodeName); lb.removeHost(uri); + log.infof("Load balancer: disabled node '%s', All enabled nodes: %s", nodeName, lb.toString()); } static Map parseNodes(String nodes) { @@ -225,6 +232,19 @@ public class SimpleUndertowLoadBalancer { } + // For now, overriden just this "addHost" method to avoid adding duplicates + @Override + public synchronized LoadBalancingProxyClient addHost(URI host, String jvmRoute) { + List current = getCurrentHostRoutes(); + if (current.contains(jvmRoute)) { + log.infof("Route '%s' already present. Skip adding", jvmRoute); + return this; + } else { + return super.addHost(host, jvmRoute); + } + } + + @Override public void getConnection(ProxyTarget target, HttpServerExchange exchange, ProxyCallback callback, long timeout, TimeUnit timeUnit) { long timeoutMs = timeUnit.toMillis(timeout); @@ -233,6 +253,31 @@ public class SimpleUndertowLoadBalancer { super.getConnection(target, exchange, callbackDelegate, timeout, timeUnit); } + + @Override + public String toString() { + return getCurrentHostRoutes().toString(); + } + + + private List getCurrentHostRoutes() { + Field hostsField = Reflections.findDeclaredField(LoadBalancingProxyClient.class, "hosts"); + hostsField.setAccessible(true); + Host[] hosts = (Host[]) Reflections.getFieldValue(hostsField, this); + + if (hosts == null) { + return Collections.emptyList(); + } + + List hostRoutes = new LinkedList<>(); + for (Host host : hosts) { + Field hostField = Reflections.findDeclaredField(Host.class, "jvmRoute"); + hostField.setAccessible(true); + String route = Reflections.getFieldValue(hostField, host).toString(); + hostRoutes.add(route); + } + return hostRoutes; + } } diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index bd7b3d7aa3..9b262543eb 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -228,6 +228,13 @@ + + + maven-antrun-plugin + + false + + diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java index 5b0f6d8fd7..d2092ffe02 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java @@ -135,6 +135,11 @@ public class AuthServerTestEnricher { return managementClient; } + + public void distinguishContainersInConsoleOutput(@Observes(precedence = 5) StartContainer event) { + log.info("*****************************************************************" + + "*****************************************************************************"); + } public void initializeSuiteContext(@Observes(precedence = 2) BeforeSuite event) { Set containers = containerRegistry.get().getContainers().stream() @@ -165,15 +170,16 @@ public class AuthServerTestEnricher { } containers.stream() - .filter(c -> c.getQualifier().startsWith(AUTH_SERVER_CONTAINER + "-cross-dc-")) - .sorted((a, b) -> a.getQualifier().compareTo(b.getQualifier())) - .forEach(c -> { - String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0"); - String dcString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("dataCenter", "0"); - updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString)); - suiteContext.addAuthServerBackendsInfo(Integer.valueOf(dcString), c); - }); + .filter(c -> c.getQualifier().startsWith("auth-server-" + System.getProperty("node.name") + "-")) + .sorted((a, b) -> a.getQualifier().compareTo(b.getQualifier())) + .forEach(c -> { + String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0"); + updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString)); + String dcString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("dataCenter", "0"); + suiteContext.addAuthServerBackendsInfo(Integer.valueOf(dcString), c); + }); + containers.stream() .filter(c -> c.getQualifier().startsWith("cache-server-cross-dc-")) .sorted((a, b) -> a.getQualifier().compareTo(b.getQualifier())) diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/CacheStatisticsControllerEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/CacheStatisticsControllerEnricher.java index 33af2f2de7..04c611042b 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/CacheStatisticsControllerEnricher.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/CacheStatisticsControllerEnricher.java @@ -1,23 +1,18 @@ package org.keycloak.testsuite.arquillian; -import org.keycloak.connections.infinispan.InfinispanConnectionProvider; -import org.keycloak.testsuite.Retry; -import java.util.Map; -import org.jboss.arquillian.core.api.Instance; -import org.jboss.arquillian.core.api.annotation.Inject; +import java.io.IOException; +import java.io.NotSerializableException; +import java.lang.management.ManagementFactory; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.net.MalformedURLException; -import javax.management.MBeanServerConnection; -import javax.management.ObjectName; -import javax.management.remote.JMXConnector; -import javax.management.remote.JMXServiceURL; -import org.jboss.arquillian.container.spi.Container; -import org.jboss.arquillian.container.spi.ContainerRegistry; -import org.jboss.arquillian.test.spi.TestEnricher; -import java.io.IOException; import java.lang.reflect.Parameter; +import java.net.MalformedURLException; import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.management.Attribute; import javax.management.AttributeNotFoundException; @@ -26,21 +21,26 @@ import javax.management.IntrospectionException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanException; import javax.management.MBeanInfo; +import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; import javax.management.ReflectionException; +import javax.management.remote.JMXServiceURL; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.jboss.arquillian.container.spi.Container; +import org.jboss.arquillian.container.spi.ContainerRegistry; +import org.jboss.arquillian.core.api.Instance; +import org.jboss.arquillian.core.api.annotation.Inject; +import org.jboss.arquillian.core.spi.Validate; +import org.jboss.arquillian.test.spi.TestEnricher; +import org.jboss.logging.Logger; +import org.keycloak.connections.infinispan.InfinispanConnectionProvider; +import org.keycloak.testsuite.Retry; import org.keycloak.testsuite.arquillian.annotation.JmxInfinispanCacheStatistics; -import java.util.Set; import org.keycloak.testsuite.arquillian.annotation.JmxInfinispanChannelStatistics; import org.keycloak.testsuite.arquillian.jmx.JmxConnectorRegistry; import org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow; import org.keycloak.testsuite.crossdc.DC; -import java.io.NotSerializableException; -import java.lang.management.ManagementFactory; -import java.util.Objects; -import java.util.concurrent.TimeUnit; -import org.apache.commons.lang3.reflect.FieldUtils; -import org.jboss.arquillian.core.spi.Validate; -import org.jboss.logging.Logger; /** * @@ -81,8 +81,6 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { } private InfinispanStatistics getInfinispanCacheStatistics(JmxInfinispanCacheStatistics annotation) throws MalformedObjectNameException, IOException, MalformedURLException { - MBeanServerConnection mbsc = getJmxServerConnection(annotation); - ObjectName mbeanName = new ObjectName(String.format( "%s:type=%s,name=\"%s(%s)\",manager=\"%s\",component=%s", annotation.domain().isEmpty() ? getDefaultDomain(annotation.dc().getDcIndex(), annotation.dcNodeIndex()) : InfinispanConnectionProvider.JMX_DOMAIN, @@ -93,7 +91,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { annotation.component() )); - InfinispanStatistics value = new InfinispanCacheStatisticsImpl(mbsc, mbeanName); + InfinispanStatistics value = new InfinispanCacheStatisticsImpl(getJmxServerConnection(annotation), mbeanName); if (annotation.domain().isEmpty()) { try { @@ -101,7 +99,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { } catch (RuntimeException ex) { if (annotation.dc() != DC.UNDEFINED && annotation.dcNodeIndex() != -1 && suiteContext.get().getAuthServerBackendsInfo(annotation.dc().getDcIndex()).get(annotation.dcNodeIndex()).isStarted()) { - LOG.warn("Could not reset statistics for " + mbeanName); + LOG.warn("Could not reset statistics for " + mbeanName + ". The reason is: \"" + ex.getMessage() + "\""); } } } @@ -110,8 +108,6 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { } private InfinispanStatistics getJGroupsChannelStatistics(JmxInfinispanChannelStatistics annotation) throws MalformedObjectNameException, IOException, MalformedURLException { - MBeanServerConnection mbsc = getJmxServerConnection(annotation); - ObjectName mbeanName = new ObjectName(String.format( "%s:type=%s,cluster=\"%s\"", annotation.domain().isEmpty() ? getDefaultDomain(annotation.dc().getDcIndex(), annotation.dcNodeIndex()) : InfinispanConnectionProvider.JMX_DOMAIN, @@ -119,7 +115,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { annotation.cluster() )); - InfinispanStatistics value = new InfinispanChannelStatisticsImpl(mbsc, mbeanName); + InfinispanStatistics value = new InfinispanChannelStatisticsImpl(getJmxServerConnection(annotation), mbeanName); if (annotation.domain().isEmpty()) { try { @@ -127,7 +123,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { } catch (RuntimeException ex) { if (annotation.dc() != DC.UNDEFINED && annotation.dcNodeIndex() != -1 && suiteContext.get().getAuthServerBackendsInfo(annotation.dc().getDcIndex()).get(annotation.dcNodeIndex()).isStarted()) { - LOG.warn("Could not reset statistics for " + mbeanName); + LOG.warn("Could not reset statistics for " + mbeanName + ". The reason is: \"" + ex.getMessage() + "\""); } } } @@ -162,12 +158,20 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { private String getDefaultDomain(int dcIndex, int dcNodeIndex) { if (dcIndex != -1 && dcNodeIndex != -1) { + if (Boolean.parseBoolean(System.getProperty("auth.server.jboss.crossdc"))) { + //backend-jboss-server + return "org.wildfly.clustering.infinispan"; + } + + //backend-undertow-server return InfinispanConnectionProvider.JMX_DOMAIN + "-" + suiteContext.get().getAuthServerBackendsInfo(dcIndex).get(dcNodeIndex).getQualifier(); } + + //cache-server return InfinispanConnectionProvider.JMX_DOMAIN; } - private MBeanServerConnection getJmxServerConnection(JmxInfinispanCacheStatistics annotation) throws MalformedURLException, IOException { + private Supplier getJmxServerConnection(JmxInfinispanCacheStatistics annotation) throws MalformedURLException { final String host; final int port; @@ -175,7 +179,46 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { ContainerInfo node = suiteContext.get().getAuthServerBackendsInfo(annotation.dc().getDcIndex()).get(annotation.dcNodeIndex()); Container container = node.getArquillianContainer(); if (container.getDeployableContainer() instanceof KeycloakOnUndertow) { - return ManagementFactory.getPlatformMBeanServer(); + return () -> ManagementFactory.getPlatformMBeanServer(); + } + host = "localhost"; + port = container.getContainerConfiguration().getContainerProperties().containsKey("managementPort") + ? Integer.valueOf(container.getContainerConfiguration().getContainerProperties().get("managementPort")) + : 9990; + } else { + host = annotation.host().isEmpty() + ? System.getProperty((annotation.hostProperty().isEmpty() + ? "keycloak.connectionsInfinispan.remoteStoreServer" + : annotation.hostProperty())) + : annotation.host(); + + port = annotation.managementPort() == -1 + ? Integer.valueOf(System.getProperty((annotation.managementPortProperty().isEmpty() + ? "cache.server.management.port" + : annotation.managementPortProperty()))) + : annotation.managementPort(); + } + + + JMXServiceURL url = new JMXServiceURL("service:jmx:remote+http://" + host + ":" + port); + return () -> { + try { + return jmxConnectorRegistry.get().getConnection(url).getMBeanServerConnection(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + }; + } + + private Supplier getJmxServerConnection(JmxInfinispanChannelStatistics annotation) throws MalformedURLException { + final String host; + final int port; + + if (annotation.dc() != DC.UNDEFINED && annotation.dcNodeIndex() != -1) { + ContainerInfo node = suiteContext.get().getAuthServerBackendsInfo(annotation.dc().getDcIndex()).get(annotation.dcNodeIndex()); + Container container = node.getArquillianContainer(); + if (container.getDeployableContainer() instanceof KeycloakOnUndertow) { + return () -> ManagementFactory.getPlatformMBeanServer(); } host = "localhost"; port = container.getContainerConfiguration().getContainerProperties().containsKey("managementPort") @@ -196,79 +239,50 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { } JMXServiceURL url = new JMXServiceURL("service:jmx:remote+http://" + host + ":" + port); - JMXConnector jmxc = jmxConnectorRegistry.get().getConnection(url); - - return jmxc.getMBeanServerConnection(); - } - - private MBeanServerConnection getJmxServerConnection(JmxInfinispanChannelStatistics annotation) throws MalformedURLException, IOException { - final String host; - final int port; - - if (annotation.dc() != DC.UNDEFINED && annotation.dcNodeIndex() != -1) { - ContainerInfo node = suiteContext.get().getAuthServerBackendsInfo(annotation.dc().getDcIndex()).get(annotation.dcNodeIndex()); - Container container = node.getArquillianContainer(); - if (container.getDeployableContainer() instanceof KeycloakOnUndertow) { - return ManagementFactory.getPlatformMBeanServer(); + return () -> { + try { + return jmxConnectorRegistry.get().getConnection(url).getMBeanServerConnection(); + } catch (IOException ex) { + throw new RuntimeException(ex); } - host = "localhost"; - port = container.getContainerConfiguration().getContainerProperties().containsKey("managementPort") - ? Integer.valueOf(container.getContainerConfiguration().getContainerProperties().get("managementPort")) - : 9990; - } else { - host = annotation.host().isEmpty() - ? System.getProperty((annotation.hostProperty().isEmpty() - ? "keycloak.connectionsInfinispan.remoteStoreServer" - : annotation.hostProperty())) - : annotation.host(); - - port = annotation.managementPort() == -1 - ? Integer.valueOf(System.getProperty((annotation.managementPortProperty().isEmpty() - ? "cache.server.management.port" - : annotation.managementPortProperty()))) - : annotation.managementPort(); - } - - String jmxUrl = "service:jmx:remote+http://" + host + ":" + port; - LOG.infof("JMX Service URL: %s", jmxUrl); - - JMXServiceURL url = new JMXServiceURL(jmxUrl); - JMXConnector jmxc = jmxConnectorRegistry.get().getConnection(url); - - return jmxc.getMBeanServerConnection(); + }; } private static abstract class CacheStatisticsImpl implements InfinispanStatistics { - protected final MBeanServerConnection mbsc; + private final Supplier mbscCreateor; private final ObjectName mbeanNameTemplate; private ObjectName mbeanName; - public CacheStatisticsImpl(MBeanServerConnection mbsc, ObjectName mbeanNameTemplate) { - this.mbsc = mbsc; + public CacheStatisticsImpl(Supplier mbscCreateor, ObjectName mbeanNameTemplate) { + this.mbscCreateor = mbscCreateor; this.mbeanNameTemplate = mbeanNameTemplate; } + protected MBeanServerConnection getConnection() { + return mbscCreateor.get(); + } + @Override public boolean exists() { try { getMbeanName(); return true; - } catch (Exception ex) { + } catch (IOException | RuntimeException ex) { return false; } } - + @Override public Map getStatistics() { try { - MBeanInfo mBeanInfo = mbsc.getMBeanInfo(getMbeanName()); + MBeanInfo mBeanInfo = getConnection().getMBeanInfo(getMbeanName()); String[] statAttrs = Arrays.asList(mBeanInfo.getAttributes()).stream() .filter(MBeanAttributeInfo::isReadable) .map(MBeanAttributeInfo::getName) .collect(Collectors.toList()) .toArray(new String[] {}); - return mbsc.getAttributes(getMbeanName(), statAttrs) + return getConnection().getAttributes(getMbeanName(), statAttrs) .asList() .stream() .collect(Collectors.toMap(Attribute::getName, Attribute::getValue)); @@ -279,7 +293,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { protected ObjectName getMbeanName() throws IOException, RuntimeException { if (this.mbeanName == null) { - Set queryNames = mbsc.queryNames(mbeanNameTemplate, null); + Set queryNames = getConnection().queryNames(mbeanNameTemplate, null); if (queryNames.isEmpty()) { throw new RuntimeException("No MBean of template " + mbeanNameTemplate + " found at JMX server"); } @@ -292,7 +306,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { @Override public Comparable getSingleStatistics(String statisticsName) { try { - return (Comparable) mbsc.getAttribute(getMbeanName(), statisticsName); + return (Comparable) getConnection().getAttribute(getMbeanName(), statisticsName); } catch (IOException | InstanceNotFoundException | MBeanException | ReflectionException | AttributeNotFoundException ex) { throw new RuntimeException(ex); } @@ -305,7 +319,7 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { try { getMbeanName(); if (! isAvailable()) throw new RuntimeException("Not available"); - } catch (Exception ex) { + } catch (IOException | RuntimeException ex) { throw new RuntimeException("Timed out while waiting for " + mbeanNameTemplate + " to become available", ex); } }, 1 + (int) timeInMillis / 100, 100); @@ -316,14 +330,14 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { private static class InfinispanCacheStatisticsImpl extends CacheStatisticsImpl { - public InfinispanCacheStatisticsImpl(MBeanServerConnection mbsc, ObjectName mbeanName) { - super(mbsc, mbeanName); + public InfinispanCacheStatisticsImpl(Supplier mbscCreator, ObjectName mbeanName) { + super(mbscCreator, mbeanName); } @Override public void reset() { try { - mbsc.invoke(getMbeanName(), "resetStatistics", new Object[] {}, new String[] {}); + getConnection().invoke(getMbeanName(), "resetStatistics", new Object[] {}, new String[] {}); } catch (IOException | InstanceNotFoundException | MBeanException | ReflectionException ex) { throw new RuntimeException(ex); } @@ -337,14 +351,14 @@ public class CacheStatisticsControllerEnricher implements TestEnricher { private static class InfinispanChannelStatisticsImpl extends CacheStatisticsImpl { - public InfinispanChannelStatisticsImpl(MBeanServerConnection mbsc, ObjectName mbeanName) { - super(mbsc, mbeanName); + public InfinispanChannelStatisticsImpl(Supplier mbscCreator, ObjectName mbeanName) { + super(mbscCreator, mbeanName); } @Override public void reset() { try { - mbsc.invoke(getMbeanName(), "resetStats", new Object[] {}, new String[] {}); + getConnection().invoke(getMbeanName(), "resetStats", new Object[] {}, new String[] {}); } catch (NotSerializableException ex) { // Ignore return value not serializable, the invocation has already done its job } catch (IOException | InstanceNotFoundException | MBeanException | ReflectionException ex) { diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentTargetModifier.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentTargetModifier.java index f7fc8cfc06..339ded7512 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentTargetModifier.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentTargetModifier.java @@ -22,6 +22,7 @@ import org.jboss.arquillian.container.spi.client.deployment.TargetDescription; import org.jboss.arquillian.container.test.impl.client.deployment.AnnotationDeploymentScenarioGenerator; import org.jboss.arquillian.test.spi.TestClass; import org.jboss.logging.Logger; +import org.keycloak.common.util.StringPropertyReplacer; import java.util.List; @@ -73,12 +74,23 @@ public class DeploymentTargetModifier extends AnnotationDeploymentScenarioGenera if (deployment.getTarget() != null) { String containerQualifier = deployment.getTarget().getName(); if (AUTH_SERVER_CURRENT.equals(containerQualifier)) { - String authServerQualifier = AuthServerTestEnricher.AUTH_SERVER_CONTAINER; - log.infof("Setting target container for deployment %s.%s: %s", testClass.getName(), deployment.getName(), authServerQualifier); - deployment.setTarget(new TargetDescription(authServerQualifier)); + String newAuthServerQualifier = AuthServerTestEnricher.AUTH_SERVER_CONTAINER; + updateAuthServerQualifier(deployment, testClass, newAuthServerQualifier); + } else { + String newAuthServerQualifier = StringPropertyReplacer.replaceProperties(containerQualifier); + if (!newAuthServerQualifier.equals(containerQualifier)) { + updateAuthServerQualifier(deployment, testClass, newAuthServerQualifier); + } } + + } } } + private void updateAuthServerQualifier(DeploymentDescription deployment, TestClass testClass, String newAuthServerQualifier) { + log.infof("Setting target container for deployment %s.%s: %s", testClass.getName(), deployment.getName(), newAuthServerQualifier); + deployment.setTarget(new TargetDescription(newAuthServerQualifier)); + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistryCreator.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistryCreator.java index 50c9b965c6..e5d1d2ac8f 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistryCreator.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jmx/JmxConnectorRegistryCreator.java @@ -27,6 +27,7 @@ import org.jboss.arquillian.core.api.annotation.ApplicationScoped; import org.jboss.arquillian.core.api.annotation.Inject; import org.jboss.arquillian.core.api.annotation.Observes; import org.jboss.arquillian.test.spi.event.suite.BeforeSuite; +import org.jboss.logging.Logger; /** * @@ -34,6 +35,8 @@ import org.jboss.arquillian.test.spi.event.suite.BeforeSuite; */ public class JmxConnectorRegistryCreator { + private final Logger log = Logger.getLogger(JmxConnectorRegistryCreator.class); + @Inject @ApplicationScoped private InstanceProducer connectorRegistry; @@ -46,6 +49,7 @@ public class JmxConnectorRegistryCreator { @Override public JMXConnector getConnection(JMXServiceURL url) { + JMXConnector res = connectors.get(url); if (res == null) { try { @@ -55,7 +59,10 @@ public class JmxConnectorRegistryCreator { res = conn; } res.connect(); + log.infof("Connected to JMX Service URL: %s", url); } catch (IOException ex) { + //remove conn from connectors in case something goes wrong. The connection will be established on-demand + connectors.remove(url, res); throw new RuntimeException("Could not instantiate JMX connector for " + url, ex); } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java index 533dfbb08f..7c61206b28 100755 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java @@ -90,14 +90,14 @@ public class KeycloakTestingClient { public T fetch(FetchOnServer function, Class clazz) throws RunOnServerException { try { - String s = fetch(function); + String s = fetchString(function); return JsonSerialization.readValue(s, clazz); } catch (Exception e) { throw new RuntimeException(e); } } - public String fetch(FetchOnServer function) throws RunOnServerException { + public String fetchString(FetchOnServer function) throws RunOnServerException { String encoded = SerializationUtil.encode(function); String result = testing(realm != null ? realm : "master").runOnServer(encoded); diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/TwitterLoginPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/TwitterLoginPage.java index 9806b277e6..c052b9baad 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/TwitterLoginPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/TwitterLoginPage.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.pages.social; +import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; @@ -35,9 +36,15 @@ public class TwitterLoginPage extends AbstractSocialLoginPage { @Override public void login(String user, String password) { - usernameInput.clear(); - usernameInput.sendKeys(user); - passwordInput.sendKeys(password); - loginButton.click(); + try { + usernameInput.clear(); + usernameInput.sendKeys(user); + passwordInput.sendKeys(password); + } + catch (NoSuchElementException e) { // at some conditions we are already logged in and just need to confirm it + } + finally { + loginButton.click(); + } } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/DroneUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/DroneUtils.java index 8efb968fa6..729c685ba1 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/DroneUtils.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/DroneUtils.java @@ -20,11 +20,33 @@ package org.keycloak.testsuite.util; import org.jboss.arquillian.graphene.context.GrapheneContext; import org.openqa.selenium.WebDriver; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Stack; + /** * @author Vaclav Muzikar */ public final class DroneUtils { + private static Queue driverQueue = new LinkedList<>(); + public static WebDriver getCurrentDriver() { - return GrapheneContext.lastContext().getWebDriver(); + if (driverQueue.isEmpty()) { + return GrapheneContext.lastContext().getWebDriver(); + } + + return driverQueue.peek(); + } + + public static void addWebDriver(WebDriver driver) { + driverQueue.add(driver); + } + + public static void removeWebDriver() { + driverQueue.poll(); + } + + public static void resetQueue() { + driverQueue = new LinkedList<>(); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index 9b3e1f9e73..dddeba77b6 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java @@ -64,6 +64,7 @@ import org.keycloak.testsuite.auth.page.login.OIDCLogin; import org.keycloak.testsuite.auth.page.login.UpdatePassword; import org.keycloak.testsuite.client.KeycloakTestingClient; import org.keycloak.testsuite.util.AdminClientUtil; +import org.keycloak.testsuite.util.DroneUtils; import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.TestCleanup; import org.keycloak.testsuite.util.TestEventsLogger; @@ -213,6 +214,9 @@ public abstract class AbstractKeycloakTest { } testContext.getCleanups().clear(); } + + // Remove all browsers from queue + DroneUtils.resetQueue(); } protected TestCleanup getCleanup(String realmName) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java index 2c6b772b71..7fa6a3db6a 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java @@ -207,6 +207,17 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest { events.clear(); } + @Test + public void referrerEscaped() { + profilePage.open(); + loginPage.login("test-user@localhost", "password"); + + driver.navigate().to(profilePage.getPath() + "?referrer=test-app&referrer_uri=http://localhost:8180/auth/realms/master/app/auth/test%2Ffkrenu%22%3E%3Cscript%3Ealert%281%29%3C%2fscript%3E"); + Assert.assertTrue(profilePage.isCurrent()); + + Assert.assertFalse(driver.getPageSource().contains("