[KEYCLOAK-11330] - Properly handling POST formdata and UriInfo

This commit is contained in:
Pedro Igor 2020-06-01 16:27:50 -03:00 committed by Stian Thorgersen
parent 90b29b0e31
commit e8dc10b4a1
7 changed files with 52 additions and 29 deletions

View file

@ -28,6 +28,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.services.ServicesLogger;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
@ -97,38 +98,42 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
throw new AuthenticationFlowException("Execution not found", AuthenticationFlowError.INTERNAL_ERROR);
}
MultivaluedMap<String, String> inputData = processor.getRequest().getDecodedFormParameters();
String authExecId = inputData.getFirst(Constants.AUTHENTICATION_EXECUTION);
if (HttpMethod.POST.equals(processor.getRequest().getHttpMethod())) {
MultivaluedMap<String, String> inputData = processor.getRequest().getDecodedFormParameters();
String authExecId = inputData.getFirst(Constants.AUTHENTICATION_EXECUTION);
// User clicked on "try another way" link
if (inputData.containsKey("tryAnotherWay")) {
logger.trace("User clicked on link 'Try Another Way'");
// User clicked on "try another way" link
if (inputData.containsKey("tryAnotherWay")) {
logger.trace("User clicked on link 'Try Another Way'");
List<AuthenticationSelectionOption> selectionOptions = createAuthenticationSelectionList(model);
List<AuthenticationSelectionOption> selectionOptions = createAuthenticationSelectionList(model);
AuthenticationProcessor.Result result = processor.createAuthenticatorContext(model, null, null);
result.setAuthenticationSelections(selectionOptions);
return result.form().createSelectAuthenticator();
}
AuthenticationProcessor.Result result = processor.createAuthenticatorContext(model, null, null);
result.setAuthenticationSelections(selectionOptions);
return result.form().createSelectAuthenticator();
}
// check if the user has switched to a new authentication execution, and if so switch to it.
if (authExecId != null && !authExecId.isEmpty()) {
// check if the user has switched to a new authentication execution, and if so switch to it.
if (authExecId != null && !authExecId.isEmpty()) {
List<AuthenticationSelectionOption> selectionOptions = createAuthenticationSelectionList(model);
List<AuthenticationSelectionOption> selectionOptions = createAuthenticationSelectionList(model);
// Check if switch to the requested authentication execution is allowed
selectionOptions.stream()
.filter(authSelectionOption -> authExecId.equals(authSelectionOption.getAuthExecId()))
.findFirst()
.orElseThrow(() -> new AuthenticationFlowException("Requested authentication execution is not allowed", AuthenticationFlowError.INTERNAL_ERROR)
);
// Check if switch to the requested authentication execution is allowed
selectionOptions.stream()
.filter(authSelectionOption -> authExecId.equals(authSelectionOption.getAuthExecId()))
.findFirst()
.orElseThrow(() -> new AuthenticationFlowException("Requested authentication execution is not allowed",
AuthenticationFlowError.INTERNAL_ERROR)
);
model = processor.getRealm().getAuthenticationExecutionById(authExecId);
model = processor.getRealm().getAuthenticationExecutionById(authExecId);
Response response = processSingleFlowExecutionModel(model, false);
if (response == null) {
return continueAuthenticationAfterSuccessfulAction(model);
} else return response;
Response response = processSingleFlowExecutionModel(model, false);
if (response == null) {
return continueAuthenticationAfterSuccessfulAction(model);
} else
return response;
}
}
//handle case where execution is a flow - This can happen during user registration for example

View file

@ -53,7 +53,7 @@ public class X509ClientAuthenticator extends AbstractClientAuthenticator {
boolean hasFormData = mediaType != null && mediaType.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
MultivaluedMap<String, String> formData = hasFormData ? context.getHttpRequest().getDecodedFormParameters() : null;
MultivaluedMap<String, String> queryParams = context.getHttpRequest().getUri().getQueryParameters();
MultivaluedMap<String, String> queryParams = context.getSession().getContext().getUri().getQueryParameters();
if (formData != null) {
client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);

View file

@ -91,12 +91,14 @@ import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.util.TokenUtil;
import org.keycloak.utils.ProfileHelper;
import javax.ws.rs.Consumes;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@ -160,11 +162,18 @@ public class TokenEndpoint {
this.event = event;
}
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@POST
public Response processGrantRequest() {
cors = Cors.add(request).auth().allowedMethods("POST").auth().exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS);
formParams = request.getDecodedFormParameters();
MultivaluedMap<String, String> formParameters = request.getDecodedFormParameters();
if (formParameters == null) {
formParameters = new MultivaluedHashMap<>();
}
formParams = formParameters;
grantType = formParams.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM);
// https://tools.ietf.org/html/rfc6749#section-5.1

View file

@ -35,6 +35,7 @@ import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.util.List;
@ -61,10 +62,11 @@ public class AccountLoader {
Theme theme = getTheme(session);
boolean deprecatedAccount = isDeprecatedFormsAccountConsole(theme);
UriInfo uriInfo = session.getContext().getUri();
if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
return new CorsPreflightService(request);
} else if ((accepts.contains(MediaType.APPLICATION_JSON_TYPE) || MediaType.APPLICATION_JSON_TYPE.equals(content)) && !request.getUri().getPath().endsWith("keycloak.json")) {
} else if ((accepts.contains(MediaType.APPLICATION_JSON_TYPE) || MediaType.APPLICATION_JSON_TYPE.equals(content)) && !uriInfo.getPath().endsWith("keycloak.json")) {
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session);
if (authResult == null) {
throw new NotAuthorizedException("Bearer token required");

View file

@ -35,6 +35,7 @@ import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakUriInfo;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -53,6 +54,7 @@ public abstract class AbstractResourceService {
protected final PermissionTicketStore ticketStore;
protected final ResourceStore resourceStore;
protected final ScopeStore scopeStore;
protected final KeycloakUriInfo uriInfo;
protected HttpRequest request;
protected Auth auth;
@ -64,6 +66,7 @@ public abstract class AbstractResourceService {
ticketStore = provider.getStoreFactory().getPermissionTicketStore();
resourceStore = provider.getStoreFactory().getResourceStore();
scopeStore = provider.getStoreFactory().getScopeStore();
uriInfo = session.getContext().getUri();
}
protected Response cors(Response.ResponseBuilder response) {

View file

@ -219,14 +219,14 @@ public class ResourcesService extends AbstractResourceService {
if (nextPage) {
links.add(Link.fromUri(
KeycloakUriBuilder.fromUri(request.getUri().getRequestUri()).replaceQuery("first={first}&max={max}")
KeycloakUriBuilder.fromUri(uriInfo.getRequestUri()).replaceQuery("first={first}&max={max}")
.build(first + max, max))
.rel("next").build());
}
if (first > 0) {
links.add(Link.fromUri(
KeycloakUriBuilder.fromUri(request.getUri().getRequestUri()).replaceQuery("first={first}&max={max}")
KeycloakUriBuilder.fromUri(uriInfo.getRequestUri()).replaceQuery("first={first}&max={max}")
.build(Math.max(first - max, 0), max))
.rel("prev").build());
}

View file

@ -65,6 +65,9 @@ import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@ -610,6 +613,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest extends AbstractKeycloakT
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
HttpPost post = new HttpPost(oauth.getResourceOwnerPasswordCredentialGrantUrl());
post.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED);
OAuthClient.AccessTokenResponse response = new OAuthClient.AccessTokenResponse(client.execute(post));
assertEquals(400, response.getStatusCode());