Error handle for the Json request in createErrorPage
Closes #13368 These changes introduce a new error handler for building error based on the media type. - It should create error form response when it is valid HTML request - It could create error response with JSON if content type matches Signed-off-by: Lex Cao <lexcao@foxmail.com>
This commit is contained in:
parent
7c63c561db
commit
f83756b177
8 changed files with 167 additions and 76 deletions
|
@ -152,14 +152,7 @@ public class UserSessionLimitsAuthenticator implements Authenticator {
|
|||
.orElse(SESSION_LIMIT_EXCEEDED);
|
||||
|
||||
context.getEvent().error(Errors.GENERIC_AUTHENTICATION_ERROR);
|
||||
Response challenge = null;
|
||||
if(context.getFlowPath() == null) {
|
||||
OAuth2ErrorRepresentation errorRep = new OAuth2ErrorRepresentation(Errors.GENERIC_AUTHENTICATION_ERROR, errorMessage);
|
||||
challenge = Response.status(Response.Status.UNAUTHORIZED.getStatusCode()).entity(errorRep).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
}
|
||||
else {
|
||||
challenge = context.form().setError(errorMessage).createErrorPage(Response.Status.FORBIDDEN);
|
||||
}
|
||||
Response challenge = context.form().setError(errorMessage).createErrorPage(Response.Status.FORBIDDEN);
|
||||
context.failure(AuthenticationFlowError.GENERIC_AUTHENTICATION_ERROR, challenge, eventDetails, errorMessage);
|
||||
break;
|
||||
|
||||
|
|
|
@ -16,19 +16,6 @@
|
|||
*/
|
||||
package org.keycloak.forms.login.freemarker;
|
||||
|
||||
import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PASSWORD;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import jakarta.ws.rs.core.MultivaluedHashMap;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
@ -45,21 +32,22 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
|
|||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.forms.login.LoginFormsPages;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.forms.login.MessageType;
|
||||
import org.keycloak.forms.login.freemarker.model.AuthenticationContextBean;
|
||||
import org.keycloak.forms.login.freemarker.model.AuthenticationSessionBean;
|
||||
import org.keycloak.forms.login.freemarker.model.RecoveryAuthnCodeInputLoginBean;
|
||||
import org.keycloak.forms.login.freemarker.model.RecoveryAuthnCodesBean;
|
||||
import org.keycloak.forms.login.freemarker.model.ClientBean;
|
||||
import org.keycloak.forms.login.freemarker.model.CodeBean;
|
||||
import org.keycloak.forms.login.freemarker.model.EmailBean;
|
||||
import org.keycloak.forms.login.freemarker.model.FrontChannelLogoutBean;
|
||||
import org.keycloak.forms.login.freemarker.model.IdentityProviderBean;
|
||||
import org.keycloak.forms.login.freemarker.model.IdpReviewProfileBean;
|
||||
import org.keycloak.forms.login.freemarker.model.LoginBean;
|
||||
import org.keycloak.forms.login.freemarker.model.FrontChannelLogoutBean;
|
||||
import org.keycloak.forms.login.freemarker.model.LogoutConfirmBean;
|
||||
import org.keycloak.forms.login.freemarker.model.OAuthGrantBean;
|
||||
import org.keycloak.forms.login.freemarker.model.ProfileBean;
|
||||
import org.keycloak.forms.login.freemarker.model.RealmBean;
|
||||
import org.keycloak.forms.login.freemarker.model.RecoveryAuthnCodeInputLoginBean;
|
||||
import org.keycloak.forms.login.freemarker.model.RecoveryAuthnCodesBean;
|
||||
import org.keycloak.forms.login.freemarker.model.RegisterBean;
|
||||
import org.keycloak.forms.login.freemarker.model.RequiredActionUrlFormatterMethod;
|
||||
import org.keycloak.forms.login.freemarker.model.SAMLPostFormBean;
|
||||
|
@ -77,6 +65,7 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.rar.AuthorizationDetails;
|
||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.LoginActionsService;
|
||||
|
@ -87,11 +76,25 @@ import org.keycloak.theme.beans.AdvancedMessageFormatterMethod;
|
|||
import org.keycloak.theme.beans.LocaleBean;
|
||||
import org.keycloak.theme.beans.MessageBean;
|
||||
import org.keycloak.theme.beans.MessageFormatterMethod;
|
||||
import org.keycloak.forms.login.MessageType;
|
||||
import org.keycloak.theme.beans.MessagesPerFieldBean;
|
||||
import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
||||
import org.keycloak.userprofile.UserProfileContext;
|
||||
import org.keycloak.utils.MediaType;
|
||||
import org.keycloak.utils.MediaTypeMatcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PASSWORD;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -115,7 +118,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
protected boolean detachedAuthSession = false;
|
||||
|
||||
protected KeycloakSession session;
|
||||
/** authenticationSession can be null for some renderings, mainly error pages */
|
||||
/**
|
||||
* authenticationSession can be null for some renderings, mainly error pages
|
||||
*/
|
||||
protected AuthenticationSessionModel authenticationSession;
|
||||
protected RealmModel realm;
|
||||
protected ClientModel client;
|
||||
|
@ -164,8 +169,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
|
||||
break;
|
||||
case UPDATE_EMAIL:
|
||||
UpdateProfileContext updateEmailContext = new UserUpdateProfileContext(realm,user);
|
||||
attributes.put("user",new ProfileBean(updateEmailContext,formData));
|
||||
UpdateProfileContext updateEmailContext = new UserUpdateProfileContext(realm, user);
|
||||
attributes.put("user", new ProfileBean(updateEmailContext, formData));
|
||||
actionMessage = Messages.UPDATE_EMAIL;
|
||||
page = LoginFormsPages.UPDATE_EMAIL;
|
||||
break;
|
||||
|
@ -175,8 +180,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
page = LoginFormsPages.LOGIN_UPDATE_PASSWORD;
|
||||
break;
|
||||
case VERIFY_EMAIL:
|
||||
UpdateProfileContext userBasedContext1 = new UserUpdateProfileContext(realm,user);
|
||||
attributes.put("user",new ProfileBean(userBasedContext1,formData));
|
||||
UpdateProfileContext userBasedContext1 = new UserUpdateProfileContext(realm, user);
|
||||
attributes.put("user", new ProfileBean(userBasedContext1, formData));
|
||||
actionMessage = Messages.VERIFY_EMAIL;
|
||||
page = LoginFormsPages.LOGIN_VERIFY_EMAIL;
|
||||
break;
|
||||
|
@ -225,8 +230,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
if (!isDetachedAuthenticationSession()) {
|
||||
if ((AuthenticationSessionModel.Action.AUTHENTICATE.name().equals(authenticationSession.getAction())) ||
|
||||
(AuthenticationSessionModel.Action.REQUIRED_ACTIONS.name().equals(authenticationSession.getAction())) ||
|
||||
(AuthenticationSessionModel.Action.OAUTH_GRANT.name().equals(authenticationSession.getAction()))) {
|
||||
(AuthenticationSessionModel.Action.REQUIRED_ACTIONS.name().equals(authenticationSession.getAction())) ||
|
||||
(AuthenticationSessionModel.Action.OAUTH_GRANT.name().equals(authenticationSession.getAction()))) {
|
||||
setAttribute("authenticationSession", new AuthenticationSessionBean(authenticationSession.getParentSession().getId(), authenticationSession.getTabId()));
|
||||
}
|
||||
}
|
||||
|
@ -273,7 +278,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
attributes.put("configuredOtpCredentials", new TotpLoginBean(session, realm, user, (String) this.attributes.get(OTPFormAuthenticator.SELECTED_OTP_CREDENTIAL_ID)));
|
||||
break;
|
||||
case REGISTER:
|
||||
RegisterBean rb = new RegisterBean(formData,session);
|
||||
RegisterBean rb = new RegisterBean(formData, session);
|
||||
//legacy bean for static template
|
||||
attributes.put("register", rb);
|
||||
//bean for dynamic template
|
||||
|
@ -308,7 +313,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
return processTemplate(theme, Templates.getTemplate(page), locale);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get sure that correct hostname and path is used for totp form.
|
||||
* Relevant when running in proxy mode.
|
||||
|
@ -344,9 +349,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
/**
|
||||
* 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
|
||||
* @return base uri builder
|
||||
*/
|
||||
protected UriBuilder prepareBaseUriBuilder(boolean resetRequestUriParams) {
|
||||
String requestURI = uriInfo.getBaseUri().getPath();
|
||||
|
@ -366,7 +371,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
/**
|
||||
* Get Theme used for page rendering.
|
||||
*
|
||||
*
|
||||
* @return theme for page rendering, never null
|
||||
* @throws IOException in case of Theme loading problem
|
||||
*/
|
||||
|
@ -376,8 +381,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
/**
|
||||
* Load message bundle and place it into <code>msg</code> template attribute. Also load Theme properties and place them into <code>properties</code> template attribute.
|
||||
*
|
||||
* @param theme actual Theme to load bundle from
|
||||
*
|
||||
* @param theme actual Theme to load bundle from
|
||||
* @param locale to load bundle for
|
||||
* @return message bundle for other use
|
||||
*/
|
||||
|
@ -403,8 +408,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
/**
|
||||
* Handle messages to be shown on the page - set them to template attributes
|
||||
*
|
||||
* @param locale to be used for message text loading
|
||||
*
|
||||
* @param locale to be used for message text loading
|
||||
* @param messagesBundle to be used for message text loading
|
||||
* @see #messageType
|
||||
* @see #messages
|
||||
|
@ -429,29 +434,17 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
@Override
|
||||
public String getMessage(String message) {
|
||||
Theme theme;
|
||||
try {
|
||||
theme = getTheme();
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to create theme", e);
|
||||
throw new RuntimeException("Failed to create theme");
|
||||
}
|
||||
|
||||
Locale locale = session.getContext().resolveLocale(user);
|
||||
Properties messagesBundle = handleThemeResources(theme, locale);
|
||||
FormMessage msg = new FormMessage(null, message);
|
||||
return formatMessage(msg, messagesBundle, locale);
|
||||
return formatMessage(new FormMessage(null, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create common attributes used in all templates.
|
||||
*
|
||||
* @param theme actual Theme used (provided by <code>getTheme()</code>)
|
||||
* @param locale actual locale
|
||||
*
|
||||
* @param theme actual Theme used (provided by <code>getTheme()</code>)
|
||||
* @param locale actual locale
|
||||
* @param messagesBundle actual message bundle (provided by <code>handleThemeResources()</code>)
|
||||
* @param baseUriBuilder actual base uri builder (provided by <code>prepareBaseUriBuilder()</code>)
|
||||
* @param page in case if common page is rendered, is null if called from <code>createForm()</code>
|
||||
*
|
||||
* @param page in case if common page is rendered, is null if called from <code>createForm()</code>
|
||||
*/
|
||||
protected void createCommonAttributes(Theme theme, Locale locale, Properties messagesBundle, UriBuilder baseUriBuilder, LoginFormsPages page) {
|
||||
URI baseUri = baseUriBuilder.build();
|
||||
|
@ -538,10 +531,10 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
/**
|
||||
* Process FreeMarker template and prepare Response. Some fields are used for rendering also.
|
||||
*
|
||||
* @param theme to be used (provided by <code>getTheme()</code>)
|
||||
*
|
||||
* @param theme to be used (provided by <code>getTheme()</code>)
|
||||
* @param templateName name of the template to be rendered
|
||||
* @param locale to be used
|
||||
* @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) {
|
||||
|
@ -563,11 +556,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
return createResponse(LoginFormsPages.LOGIN);
|
||||
}
|
||||
|
||||
public Response createLoginUsername(){
|
||||
public Response createLoginUsername() {
|
||||
return createResponse(LoginFormsPages.LOGIN_USERNAME);
|
||||
};
|
||||
}
|
||||
|
||||
public Response createLoginPassword(){
|
||||
;
|
||||
|
||||
public Response createLoginPassword() {
|
||||
return createResponse(LoginFormsPages.LOGIN_PASSWORD);
|
||||
}
|
||||
|
||||
|
@ -580,7 +575,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
return createResponse(LoginFormsPages.LOGIN_RESET_PASSWORD);
|
||||
}
|
||||
|
||||
@Override public Response createOtpReset() {
|
||||
@Override
|
||||
public Response createOtpReset() {
|
||||
return createResponse(LoginFormsPages.LOGIN_RESET_OTP);
|
||||
}
|
||||
|
||||
|
@ -606,7 +602,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
if (this.formData == null) {
|
||||
this.formData = new MultivaluedHashMap<>();
|
||||
}
|
||||
if(this.realm.isRegistrationEmailAsUsername()) {
|
||||
if (this.realm.isRegistrationEmailAsUsername()) {
|
||||
String value = this.formData.getFirst("email");
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
this.formData.putSingle("email", loginHint);
|
||||
|
@ -670,6 +666,16 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
@Override
|
||||
public Response createErrorPage(Response.Status status) {
|
||||
if (MediaTypeMatcher.isJsonRequest(session.getContext().getRequestHeaders())) {
|
||||
OAuth2ErrorRepresentation errorRep = new OAuth2ErrorRepresentation();
|
||||
errorRep.setError(formatMessage(getFirstMessage()));
|
||||
|
||||
return Response.status(status)
|
||||
.type(MediaType.APPLICATION_JSON_TYPE)
|
||||
.entity(errorRep)
|
||||
.build();
|
||||
}
|
||||
|
||||
this.status = status;
|
||||
return createResponse(LoginFormsPages.ERROR);
|
||||
}
|
||||
|
@ -738,6 +744,20 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
return formMessage == null ? null : formMessage.getMessage();
|
||||
}
|
||||
|
||||
protected String formatMessage(FormMessage message) {
|
||||
Theme theme;
|
||||
try {
|
||||
theme = getTheme();
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to create theme", e);
|
||||
throw new RuntimeException("Failed to create theme");
|
||||
}
|
||||
|
||||
Locale locale = session.getContext().resolveLocale(user);
|
||||
Properties messagesBundle = handleThemeResources(theme, locale);
|
||||
return formatMessage(message, messagesBundle, locale);
|
||||
}
|
||||
|
||||
protected String formatMessage(FormMessage message, Properties messagesBundle, Locale locale) {
|
||||
if (message == null)
|
||||
return null;
|
||||
|
@ -875,7 +895,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
return this;
|
||||
}
|
||||
|
||||
public LoginFormsProvider setAuthContext(AuthenticationFlowContext context){
|
||||
public LoginFormsProvider setAuthContext(AuthenticationFlowContext context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -664,6 +664,7 @@ public class TokenEndpoint {
|
|||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||
processor.setAuthenticationSession(authSession)
|
||||
.setFlowId(flowId)
|
||||
.setFlowPath("token")
|
||||
.setConnection(clientConnection)
|
||||
.setEventBuilder(event)
|
||||
.setRealm(realm)
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
package org.keycloak.utils;
|
||||
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
public class MediaTypeMatcher {
|
||||
|
||||
public static boolean isHtmlRequest(HttpHeaders headers) {
|
||||
for (jakarta.ws.rs.core.MediaType m : headers.getAcceptableMediaTypes()) {
|
||||
if (!m.isWildcardType() && m.isCompatible(jakarta.ws.rs.core.MediaType.TEXT_HTML_TYPE)) {
|
||||
return isAcceptMediaType(headers, MediaType.TEXT_HTML_TYPE);
|
||||
}
|
||||
|
||||
public static boolean isJsonRequest(HttpHeaders headers) {
|
||||
return isAcceptMediaType(headers, MediaType.APPLICATION_JSON_TYPE);
|
||||
}
|
||||
|
||||
private static boolean isAcceptMediaType(HttpHeaders headers, MediaType textHtmlType) {
|
||||
for (MediaType m : headers.getAcceptableMediaTypes()) {
|
||||
if (!m.isWildcardType() && m.isCompatible(textHtmlType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ import org.keycloak.testsuite.runonserver.RunOnServerException;
|
|||
import org.keycloak.util.BasicAuthHelper;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
import org.keycloak.utils.MediaType;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
@ -604,6 +605,8 @@ public class OAuthClient {
|
|||
try (CloseableHttpClient client = httpClient.get()) {
|
||||
HttpPost post = new HttpPost(getResourceOwnerPasswordCredentialGrantUrl(realm));
|
||||
|
||||
post.addHeader("Accept", MediaType.APPLICATION_JSON);
|
||||
|
||||
if (requestHeaders != null) {
|
||||
for (Map.Entry<String, String> header : requestHeaders.entrySet()) {
|
||||
post.addHeader(header.getKey(), header.getValue());
|
||||
|
|
|
@ -8,6 +8,8 @@ import org.keycloak.authentication.authenticators.access.DenyAccessAuthenticator
|
|||
import org.keycloak.authentication.authenticators.browser.PasswordFormFactory;
|
||||
import org.keycloak.authentication.authenticators.browser.UsernameFormFactory;
|
||||
import org.keycloak.authentication.authenticators.conditional.ConditionalRoleAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.directgrant.ValidatePassword;
|
||||
import org.keycloak.authentication.authenticators.directgrant.ValidateUsername;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
|
@ -19,12 +21,15 @@ import org.keycloak.testsuite.pages.ErrorPage;
|
|||
import org.keycloak.testsuite.pages.LoginUsernameOnlyPage;
|
||||
import org.keycloak.testsuite.pages.PasswordPage;
|
||||
import org.keycloak.testsuite.util.FlowUtil;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.keycloak.testsuite.forms.BrowserFlowTest.revertFlows;
|
||||
|
||||
/**
|
||||
|
@ -329,6 +334,25 @@ public class AllowDenyAuthenticatorTest extends AbstractTestRealmKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDenyAccessInDirectGrantFlow() throws Exception {
|
||||
String flowAlias = "direct grant - deny defaultMessage";
|
||||
String user = "test-user@localhost";
|
||||
String clientId = "direct-grant";
|
||||
|
||||
configureDirectGrantFlowWithDenyAccess(flowAlias, new HashMap<>());
|
||||
|
||||
try {
|
||||
oauth.clientId(clientId);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", user, "password");
|
||||
assertEquals(401, response.getStatusCode());
|
||||
assertEquals("Access denied", response.getError());
|
||||
assertNull(response.getErrorDescription());
|
||||
} finally {
|
||||
DirectGrantFlowTest.revertFlows(testRealm(), flowAlias);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This flow contains:
|
||||
* UsernameForm REQUIRED
|
||||
|
@ -412,4 +436,25 @@ public class AllowDenyAuthenticatorTest extends AbstractTestRealmKeycloakTest {
|
|||
.defineAsBrowserFlow() // Activate this new flow
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This flow contains:
|
||||
* Validate Username REQUIRED
|
||||
* Validate Password REQUIRED
|
||||
* Deny Access REQUIRED
|
||||
*
|
||||
* @param newFlowAlias
|
||||
* @param denyConfig
|
||||
*/
|
||||
private void configureDirectGrantFlowWithDenyAccess(String newFlowAlias, Map<String, String> denyConfig) {
|
||||
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session).copyDirectGrantFlow(newFlowAlias));
|
||||
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session)
|
||||
.selectFlow(newFlowAlias)
|
||||
.clear()
|
||||
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, ValidateUsername.PROVIDER_ID)
|
||||
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, ValidatePassword.PROVIDER_ID)
|
||||
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, DenyAccessAuthenticatorFactory.PROVIDER_ID, config -> config.setConfig(denyConfig))
|
||||
.defineAsDirectGrantFlow() // Activate this new flow
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,17 +22,23 @@ import org.jboss.arquillian.drone.api.annotation.Drone;
|
|||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.authentication.authenticators.browser.OTPFormAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.directgrant.ValidatePassword;
|
||||
import org.keycloak.authentication.authenticators.directgrant.ValidateUsername;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest;
|
||||
import org.keycloak.testsuite.util.FlowUtil;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
|
@ -85,5 +91,22 @@ public class DirectGrantFlowTest extends AbstractTestRealmKeycloakTest {
|
|||
assertEquals("Account is not fully set up", response.getErrorDescription());
|
||||
}
|
||||
|
||||
public static void revertFlows(RealmResource realmResource, String flowToDeleteAlias) {
|
||||
List<AuthenticationFlowRepresentation> flows = realmResource.flows().getFlows();
|
||||
|
||||
// Set default direct grant flow
|
||||
RealmRepresentation realm = realmResource.toRepresentation();
|
||||
realm.setDirectGrantFlow(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW);
|
||||
realmResource.update(realm);
|
||||
|
||||
AuthenticationFlowRepresentation flowRepresentation = AbstractAuthenticationTest.findFlowByAlias(flowToDeleteAlias, flows);
|
||||
|
||||
// Throw error if flow doesn't exist to ensure we did not accidentally use different alias of non-existing flow when
|
||||
// calling this method
|
||||
if (flowRepresentation == null) {
|
||||
throw new IllegalArgumentException("The flow with alias " + flowToDeleteAlias + " did not exist");
|
||||
}
|
||||
|
||||
realmResource.flows().deleteFlow(flowRepresentation.getId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,9 +211,8 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
|
|||
assertEquals(200, response.getStatusCode());
|
||||
|
||||
response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
|
||||
assertEquals(401, response.getStatusCode());
|
||||
assertEquals(Errors.GENERIC_AUTHENTICATION_ERROR, response.getError());
|
||||
assertEquals(ERROR_TO_DISPLAY, response.getErrorDescription());
|
||||
assertEquals(403, response.getStatusCode());
|
||||
assertEquals(ERROR_TO_DISPLAY, response.getError());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -247,9 +246,8 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
|
|||
assertEquals(200, response.getStatusCode());
|
||||
|
||||
response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
|
||||
assertEquals(401, response.getStatusCode());
|
||||
assertEquals(Errors.GENERIC_AUTHENTICATION_ERROR, response.getError());
|
||||
assertEquals(ERROR_TO_DISPLAY, response.getErrorDescription());
|
||||
assertEquals(403, response.getStatusCode());
|
||||
assertEquals(ERROR_TO_DISPLAY, response.getError());
|
||||
} finally {
|
||||
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "0");
|
||||
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "1");
|
||||
|
|
Loading…
Reference in a new issue