Unbounded login_hint parameter Can Corrupt KC_RESTART Cookie

closes #40857

Signed-off-by: mposolda <mposolda@gmail.com>
(cherry picked from commit 5a05d2123ee14f36b64b6aac08041ef7623734cf)
This commit is contained in:
mposolda 2025-09-08 09:05:48 +02:00 committed by Marek Posolda
parent 8e8072d57a
commit ebdfe4cd3f
9 changed files with 234 additions and 31 deletions

View File

@ -54,6 +54,8 @@
:adminguide_clearcache_link: {adminguide_link}#_clear-cache
:apidocs_name: API Documentation
:apidocs_link: https://www.keycloak.org/docs/{project_version}/api_documentation/
:allproviderconfigguide_name: All provider configuration Guide
:allproviderconfigguide_link: https://www.keycloak.org/server/all-provider-config
:bootstrapadminrecovery_name: Admin Bootstrap and Recovery
:bootstrapadminrecovery_link: https://www.keycloak.org/server/bootstrap-admin-recovery
:client_certificate_lookup_link: https://www.keycloak.org/server/reverseproxy#_enabling_client_certificate_lookup

View File

@ -0,0 +1,14 @@
// ------------------------ Notable changes ------------------------ //
== Notable changes
Notable changes where an internal behavior changed to prevent common misconfigurations, fix bugs or simplify running {project_name}.
=== Maximum length of the parameters in the OIDC authentication request
When the OIDC authentication request (or OAuth2 authorization request) is sent, there is now limit for the maximum length of every standard OIDC/OAuth2 parameter. The maximum length of each standard parameter is 4000 characters,
which is very big number and can be lowered in the future releases. For now, it is kept big for the backwards compatibility. The only exception is the `login_hint` parameter, which is limited
to the maximum length of 255 characters. This is aligned with the maximum length for the `username` and `email` attributes configured in the default user profile configuration.
If you want to make those number higher or lower, you can start the server with the option `req-params-default-max-size` for the default maximum length of the standard
OIDC/OAuth2 parameters or you can use something like `req-params-max-size` for one specific parameter. See the `login-protocol` provider configuration
of the link:{allproviderconfigguide_link}[{allproviderconfigguide_name}] for more details.

View File

@ -1,7 +1,9 @@
[[migration-changes]]
== Migration Changes
=== Migrating to 26.0.13
=== Migrating to 26.0.16
include::changes-26_0_16.adoc[leveloffset=2]
include::changes-26_0_13.adoc[leveloffset=2]

View File

@ -46,6 +46,8 @@ import org.keycloak.protocol.oidc.mappers.UserPropertyMapper;
import org.keycloak.protocol.oidc.mappers.UserRealmRoleMappingMapper;
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
import org.keycloak.protocol.oidc.mappers.SubMapper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ServicesLogger;
@ -53,11 +55,17 @@ import org.keycloak.services.managers.AuthenticationManager;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_ID;
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_USERNAME;
import static org.keycloak.protocol.oidc.OIDCProviderConfig.DEFAULT_ADDITIONAL_REQ_PARAMS_FAIL_FAST;
import static org.keycloak.protocol.oidc.OIDCProviderConfig.DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_NUMBER;
import static org.keycloak.protocol.oidc.OIDCProviderConfig.DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_OVERALL_SIZE;
import static org.keycloak.protocol.oidc.OIDCProviderConfig.DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_SIZE;
import static org.keycloak.protocol.oidc.OIDCProviderConfig.DEFAULT_REQ_PARAMS_DEFAULT_MAX_SIZE;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -109,10 +117,12 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
public static final String ROLES_SCOPE_CONSENT_TEXT = "${rolesScopeConsentText}";
public static final String ORGANIZATION_SCOPE_CONSENT_TEXT = "${organizationScopeConsentText}";
public static final String CONFIG_OIDC_REQ_PARAMS_MAX_NUMBER = "add-req-params-max-number";
public static final String CONFIG_OIDC_REQ_PARAMS_MAX_SIZE = "add-req-params-max-size";
public static final String CONFIG_OIDC_REQ_PARAMS_MAX_OVERALL_SIZE = "add-req-params-max-overall-size";
public static final String CONFIG_OIDC_REQ_PARAMS_FAIL_FAST = "add-req-params-fail-fast";
public static final String CONFIG_OIDC_REQ_PARAMS_DEFAULT_MAX_SIZE = "req-params-default-max-size";
public static final String CONFIG_OIDC_REQ_PARAMS_MAX_SIZE_PREFIX = "req-params-max-size";
public static final String CONFIG_OIDC_ADD_REQ_PARAMS_MAX_NUMBER = "add-req-params-max-number";
public static final String CONFIG_OIDC_ADD_REQ_PARAMS_MAX_SIZE = "add-req-params-max-size";
public static final String CONFIG_OIDC_ADD_REQ_PARAMS_MAX_OVERALL_SIZE = "add-req-params-max-overall-size";
public static final String CONFIG_OIDC_ADD_REQ_PARAMS_FAIL_FAST = "add-req-params-fail-fast";
private OIDCProviderConfig providerConfig;
@ -500,4 +510,48 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
}
}
@Override
public List<ProviderConfigProperty> getConfigMetadata() {
return ProviderConfigurationBuilder.create()
.property()
.name(CONFIG_OIDC_REQ_PARAMS_DEFAULT_MAX_SIZE)
.type("int")
.helpText("Maximum default length of the standard OIDC parameter sent to the OIDC authentication request. This applies to most of the standard parameters like for example 'state', 'nonce' etc." +
" The exception is 'login_hint' parameter, which has maximum length of 255 characters.")
.defaultValue(DEFAULT_REQ_PARAMS_DEFAULT_MAX_SIZE)
.add()
.property()
.name(CONFIG_OIDC_REQ_PARAMS_MAX_SIZE_PREFIX + "--" + OIDCLoginProtocol.LOGIN_HINT_PARAM)
.type("int")
.helpText("Maximum length of the standard OIDC authentication request parameter overriden for the specified parameter. Useful if some standard OIDC parameter should have different limit than '" + CONFIG_OIDC_REQ_PARAMS_DEFAULT_MAX_SIZE +
"'. It is needed to add the name of the parameter after this prefix into the configuration. In this example, the '" + OIDCLoginProtocol.LOGIN_HINT_PARAM + "' parameter is used, but this format is supported for any known standard OIDC/OAuth2 parameter.")
.add()
.property()
.name(CONFIG_OIDC_ADD_REQ_PARAMS_MAX_NUMBER)
.type("int")
.helpText("Maximum number of additional request parameters sent to the OIDC authentication request. As 'additional request parameter' is meant some custom parameter not directly treated as standard OIDC/OAuth2 protocol parameter. Additional parameters might be useful for example to add custom claims to the OIDC token (in case that also particular protocol mappers are configured).")
.defaultValue(DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_NUMBER)
.add()
.property()
.name(CONFIG_OIDC_ADD_REQ_PARAMS_MAX_SIZE)
.type("int")
.helpText("Maximum size of single additional request parameter value See '" + CONFIG_OIDC_ADD_REQ_PARAMS_MAX_NUMBER + "' for more details about additional request parameters")
.defaultValue(DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_SIZE)
.add()
.property()
.name(CONFIG_OIDC_ADD_REQ_PARAMS_MAX_OVERALL_SIZE)
.type("int")
.helpText("Maximum size of all additional request parameters values together. See '" + CONFIG_OIDC_ADD_REQ_PARAMS_MAX_NUMBER + "' for more details about additional request parameters")
.defaultValue(DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_OVERALL_SIZE)
.add()
.property()
.name(CONFIG_OIDC_ADD_REQ_PARAMS_FAIL_FAST)
.type("boolean")
.helpText("Whether the fail-fast strategy should be enforced in case if the limit for some standard OIDC parameter or additional OIDC parameter is not met for the parameters sent to the OIDC authentication request." +
" If false, then all additional request parameters to not meet the configuration are silently ignored. If true, an exception will be raised and OIDC authentication request will not be allowed.")
.defaultValue(DEFAULT_ADDITIONAL_REQ_PARAMS_FAIL_FAST)
.add()
.build();
}
}

View File

@ -1,5 +1,7 @@
package org.keycloak.protocol.oidc;
import java.util.Map;
import org.keycloak.Config;
/**
@ -7,6 +9,24 @@ import org.keycloak.Config;
*/
public class OIDCProviderConfig {
private final Config.Scope config;
/**
* Maximum default length of the standard OIDC parameter sent to the OIDC authentication request.
*/
public static final int DEFAULT_REQ_PARAMS_DEFAULT_MAX_SIZE = 4000;
private final int reqParamsDefaultMaxSize;
/**
* Overriden values for maximum sizes of specified standard OIDC parameters. The value for the specified parameter can be still overriden
* by administrator in the configuration of the {@link OIDCLoginProtocolFactory}. In case that value is not overriden in the configuration or in this map,
* then the value specified by the {@link OIDCLoginProtocolFactory#CONFIG_OIDC_REQ_PARAMS_DEFAULT_MAX_SIZE} is used
*/
private Map<String, Integer> DEFAULT_MAX_PARAMS_SIZES = Map.of(
OIDCLoginProtocol.LOGIN_HINT_PARAM, 255 // Aligned with user-profile configuration for username and email
);
/**
* Default value for {@link #additionalReqParamsMaxNumber} if case no configuration property is set.
*/
@ -50,10 +70,12 @@ public class OIDCProviderConfig {
public OIDCProviderConfig(Config.Scope config) {
this.additionalReqParamsMaxNumber = config.getInt(OIDCLoginProtocolFactory.CONFIG_OIDC_REQ_PARAMS_MAX_NUMBER, DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_NUMBER);
this.additionalReqParamsMaxSize = config.getInt(OIDCLoginProtocolFactory.CONFIG_OIDC_REQ_PARAMS_MAX_SIZE, DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_SIZE);
this.additionalReqParamsMaxOverallSize = config.getInt(OIDCLoginProtocolFactory.CONFIG_OIDC_REQ_PARAMS_MAX_OVERALL_SIZE, DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_OVERALL_SIZE);
this.additionalReqParamsFailFast = config.getBoolean(OIDCLoginProtocolFactory.CONFIG_OIDC_REQ_PARAMS_FAIL_FAST, DEFAULT_ADDITIONAL_REQ_PARAMS_FAIL_FAST);
this.config = config;
this.reqParamsDefaultMaxSize = config.getInt(OIDCLoginProtocolFactory.CONFIG_OIDC_REQ_PARAMS_DEFAULT_MAX_SIZE, DEFAULT_REQ_PARAMS_DEFAULT_MAX_SIZE);
this.additionalReqParamsMaxNumber = config.getInt(OIDCLoginProtocolFactory.CONFIG_OIDC_ADD_REQ_PARAMS_MAX_NUMBER, DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_NUMBER);
this.additionalReqParamsMaxSize = config.getInt(OIDCLoginProtocolFactory.CONFIG_OIDC_ADD_REQ_PARAMS_MAX_SIZE, DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_SIZE);
this.additionalReqParamsMaxOverallSize = config.getInt(OIDCLoginProtocolFactory.CONFIG_OIDC_ADD_REQ_PARAMS_MAX_OVERALL_SIZE, DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_OVERALL_SIZE);
this.additionalReqParamsFailFast = config.getBoolean(OIDCLoginProtocolFactory.CONFIG_OIDC_ADD_REQ_PARAMS_FAIL_FAST, DEFAULT_ADDITIONAL_REQ_PARAMS_FAIL_FAST);
}
public int getAdditionalReqParamsMaxNumber() {
@ -71,4 +93,27 @@ public class OIDCProviderConfig {
public int getAdditionalReqParamsMaxOverallSize() {
return additionalReqParamsMaxOverallSize;
}
/**
* @param paramName Parameter name. Expected to be one of the known OIDC parameters
*
* @return maximum length for the specified OIDC parameter
*/
public int getMaxLengthForTheParameter(String paramName) {
// Configured value for the particular OIDC parameter
Integer paramMaxSize = config.getInt(OIDCLoginProtocolFactory.CONFIG_OIDC_REQ_PARAMS_MAX_SIZE_PREFIX + "--" + paramName);
// Stick to default. See if we have default value overriden
if (paramMaxSize == null) {
paramMaxSize = DEFAULT_MAX_PARAMS_SIZES.get(paramName);
}
// Fallback to default for all standard OIDC parameters
if (paramMaxSize == null) {
paramMaxSize = reqParamsDefaultMaxSize;
}
return paramMaxSize;
}
}

View File

@ -60,6 +60,7 @@ public abstract class AuthzEndpointRequestParser {
private static final Logger logger = Logger.getLogger(AuthzEndpointRequestParser.class);
protected final OIDCProviderConfig config;
protected final int additionalReqParamsMaxNumber;
protected final int additionalReqParamsMaxSize;
protected final boolean additionalReqParamsFailFast;
@ -101,7 +102,7 @@ public abstract class AuthzEndpointRequestParser {
protected AuthzEndpointRequestParser(KeycloakSession keycloakSession) {
OIDCLoginProtocol loginProtocol = (OIDCLoginProtocol) keycloakSession.getProvider(LoginProtocol.class, OIDCLoginProtocol.LOGIN_PROTOCOL);
OIDCProviderConfig config = loginProtocol.getConfig();
this.config = loginProtocol.getConfig();
this.additionalReqParamsMaxNumber = config.getAdditionalReqParamsMaxNumber();
this.additionalReqParamsMaxSize = config.getAdditionalReqParamsMaxSize();
this.additionalReqParamsFailFast = config.isAdditionalReqParamsFailFast();
@ -109,7 +110,7 @@ public abstract class AuthzEndpointRequestParser {
}
public void parseRequest(AuthorizationEndpointRequest request) {
String clientId = getParameter(OIDCLoginProtocol.CLIENT_ID_PARAM);
String clientId = getAndValidateParameter(OIDCLoginProtocol.CLIENT_ID_PARAM);
if (clientId != null && request.clientId != null && !request.clientId.equals(clientId)) {
throw new IllegalArgumentException("The client_id parameter doesn't match the one from OIDC 'request' or 'request_uri'");
}
@ -117,31 +118,30 @@ public abstract class AuthzEndpointRequestParser {
request.clientId = clientId;
}
String responseType = getParameter(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
String responseType = getAndValidateParameter(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
validateResponseTypeParameter(responseType, request);
if (responseType != null) {
request.responseType = responseType;
}
request.responseMode = replaceIfNotNull(request.responseMode, getParameter(OIDCLoginProtocol.RESPONSE_MODE_PARAM));
request.redirectUriParam = replaceIfNotNull(request.redirectUriParam, getParameter(OIDCLoginProtocol.REDIRECT_URI_PARAM));
request.state = replaceIfNotNull(request.state, getParameter(OIDCLoginProtocol.STATE_PARAM));
request.scope = replaceIfNotNull(request.scope, getParameter(OIDCLoginProtocol.SCOPE_PARAM));
request.loginHint = replaceIfNotNull(request.loginHint, getParameter(OIDCLoginProtocol.LOGIN_HINT_PARAM));
request.prompt = replaceIfNotNull(request.prompt, getParameter(OIDCLoginProtocol.PROMPT_PARAM));
request.idpHint = replaceIfNotNull(request.idpHint, getParameter(AdapterConstants.KC_IDP_HINT));
request.action = replaceIfNotNull(request.action, getParameter(Constants.KC_ACTION));
request.nonce = replaceIfNotNull(request.nonce, getParameter(OIDCLoginProtocol.NONCE_PARAM));
request.responseMode = replaceIfNotNull(request.responseMode, getAndValidateParameter(OIDCLoginProtocol.RESPONSE_MODE_PARAM));
request.redirectUriParam = replaceIfNotNull(request.redirectUriParam, getAndValidateParameter(OIDCLoginProtocol.REDIRECT_URI_PARAM));
request.state = replaceIfNotNull(request.state, getAndValidateParameter(OIDCLoginProtocol.STATE_PARAM));
request.scope = replaceIfNotNull(request.scope, getAndValidateParameter(OIDCLoginProtocol.SCOPE_PARAM));
request.loginHint = replaceIfNotNull(request.loginHint, getAndValidateParameter(OIDCLoginProtocol.LOGIN_HINT_PARAM));
request.prompt = replaceIfNotNull(request.prompt, getAndValidateParameter(OIDCLoginProtocol.PROMPT_PARAM));
request.idpHint = replaceIfNotNull(request.idpHint, getAndValidateParameter(AdapterConstants.KC_IDP_HINT));
request.action = replaceIfNotNull(request.action, getAndValidateParameter(Constants.KC_ACTION));
request.nonce = replaceIfNotNull(request.nonce, getAndValidateParameter(OIDCLoginProtocol.NONCE_PARAM));
request.maxAge = replaceIfNotNull(request.maxAge, getIntParameter(OIDCLoginProtocol.MAX_AGE_PARAM));
request.claims = replaceIfNotNull(request.claims, getParameter(OIDCLoginProtocol.CLAIMS_PARAM));
request.acr = replaceIfNotNull(request.acr, getParameter(OIDCLoginProtocol.ACR_PARAM));
request.display = replaceIfNotNull(request.display, getParameter(OAuth2Constants.DISPLAY));
request.uiLocales = replaceIfNotNull(request.uiLocales, getParameter(OAuth2Constants.UI_LOCALES_PARAM));
request.claims = replaceIfNotNull(request.claims, getAndValidateParameter(OIDCLoginProtocol.CLAIMS_PARAM));
request.acr = replaceIfNotNull(request.acr, getAndValidateParameter(OIDCLoginProtocol.ACR_PARAM));
request.display = replaceIfNotNull(request.display, getAndValidateParameter(OAuth2Constants.DISPLAY));
request.uiLocales = replaceIfNotNull(request.uiLocales, getAndValidateParameter(OAuth2Constants.UI_LOCALES_PARAM));
// https://tools.ietf.org/html/rfc7636#section-6.1
request.codeChallenge = replaceIfNotNull(request.codeChallenge, getParameter(OIDCLoginProtocol.CODE_CHALLENGE_PARAM));
request.codeChallengeMethod = replaceIfNotNull(request.codeChallengeMethod, getParameter(OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM));
request.codeChallenge = replaceIfNotNull(request.codeChallenge, getAndValidateParameter(OIDCLoginProtocol.CODE_CHALLENGE_PARAM));
request.codeChallengeMethod = replaceIfNotNull(request.codeChallengeMethod, getAndValidateParameter(OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM));
extractAdditionalReqParams(request.additionalReqParams);
}
@ -215,6 +215,24 @@ public abstract class AuthzEndpointRequestParser {
return newVal==null ? previousVal : newVal;
}
protected String getAndValidateParameter(String paramName) {
String paramValue = getParameter(paramName);
if (paramValue != null) {
int maxLength = config.getMaxLengthForTheParameter(paramName);
if (paramValue.length() > maxLength) {
logger.warnf("The size of OIDC parameter '%s' size is longer (%d) than allowed (%d). %s", paramName, paramValue.length(), maxLength, additionalReqParamsFailFast ? "Request not allowed." : "Ignoring the parameter.");
if (additionalReqParamsFailFast) {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "The size of OIDC parameter '" + paramName + "' is longer than allowed.", Response.Status.BAD_REQUEST);
} else {
return null;
}
}
}
return paramValue;
}
protected abstract String getParameter(String paramName);
protected abstract Integer getIntParameter(String paramName);

View File

@ -28,4 +28,12 @@ public class LoginPage extends AbstractPage {
submitButton.click();
}
public String getUsername() {
return usernameInput.getAttribute("value");
}
public void clearUsernameInput() {
usernameInput.clear();
}
}

View File

@ -176,6 +176,8 @@ public class OAuthClient {
private String clientSessionHost;
private String loginHint;
private String maxAge;
private String prompt;
@ -289,6 +291,7 @@ public class OAuthClient {
responseType = OAuth2Constants.CODE;
responseMode = null;
nonce = null;
loginHint = null;
request = null;
requestUri = null;
claims = null;
@ -1543,6 +1546,9 @@ public class OAuthClient {
if (nonce != null) {
b.queryParam(OIDCLoginProtocol.NONCE_PARAM, nonce);
}
if (loginHint != null) {
b.queryParam(LOGIN_HINT_PARAM, loginHint);
}
String scopeParam = openid ? TokenUtil.attachOIDCScope(scope) : scope;
if (scopeParam != null && !scopeParam.isEmpty()) {
@ -1769,6 +1775,11 @@ public class OAuthClient {
return this;
}
public OAuthClient loginHint(String loginHint) {
this.loginHint = loginHint;
return this;
}
public OAuthClient maxAge(String maxAge) {
this.maxAge = maxAge;
return this;

View File

@ -3,14 +3,18 @@ package org.keycloak.testsuite.authz;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang.RandomStringUtils;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.util.SecretGenerator;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.Matchers;
import org.keycloak.testsuite.util.RealmBuilder;
import java.util.HashMap;
import org.keycloak.testsuite.util.OAuthClient;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@ -19,6 +23,9 @@ import static org.hamcrest.Matchers.is;
public class AuthzEndpointRequestParserTest extends AbstractTestRealmKeycloakTest {
@Page
protected LoginPage loginPage;
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
}
@ -46,4 +53,46 @@ public class AuthzEndpointRequestParserTest extends AbstractTestRealmKeycloakTes
}
@Test
public void testParamsLength() {
// Login hint with length 200 allowed, state with length 200 allowed
String loginHint200 = SecretGenerator.getInstance().randomString(200);
String state200 = SecretGenerator.getInstance().randomString(200);
oauth
.loginHint(loginHint200)
.stateParamHardcoded(state200)
.openLoginForm();
assertLogin(loginHint200, state200);
// Login hint with length 500 not allowed, state with length 500 allowed
String loginHint500 = SecretGenerator.getInstance().randomString(500);
String state500 = SecretGenerator.getInstance().randomString(500);
oauth
.loginHint(loginHint500)
.stateParamHardcoded(state500)
.openLoginForm();
assertLogin("", state500);
// state with length 4100 not allowed
String state4100 = SecretGenerator.getInstance().randomString(4100);
oauth
.stateParamHardcoded(state4100)
.openLoginForm();
assertLogin("", null);
}
protected void assertLogin(String loginHintExpected, String stateExpected) {
loginPage.assertCurrent();
Assert.assertEquals(loginHintExpected, loginPage.getUsername());
loginPage.login("test-user@localhost", "password");
// String currentUrl = driver.getCurrentUrl();
OAuthClient.AuthorizationEndpointResponse response = new OAuthClient.AuthorizationEndpointResponse(oauth);
String state = response.getState();
Assert.assertEquals(stateExpected, state);
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost");
user.logout();
}
}