[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.services.ServicesLogger;
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.ArrayList; import java.util.ArrayList;
@ -97,6 +98,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
throw new AuthenticationFlowException("Execution not found", AuthenticationFlowError.INTERNAL_ERROR); throw new AuthenticationFlowException("Execution not found", AuthenticationFlowError.INTERNAL_ERROR);
} }
if (HttpMethod.POST.equals(processor.getRequest().getHttpMethod())) {
MultivaluedMap<String, String> inputData = processor.getRequest().getDecodedFormParameters(); MultivaluedMap<String, String> inputData = processor.getRequest().getDecodedFormParameters();
String authExecId = inputData.getFirst(Constants.AUTHENTICATION_EXECUTION); String authExecId = inputData.getFirst(Constants.AUTHENTICATION_EXECUTION);
@ -120,7 +122,8 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
selectionOptions.stream() selectionOptions.stream()
.filter(authSelectionOption -> authExecId.equals(authSelectionOption.getAuthExecId())) .filter(authSelectionOption -> authExecId.equals(authSelectionOption.getAuthExecId()))
.findFirst() .findFirst()
.orElseThrow(() -> new AuthenticationFlowException("Requested authentication execution is not allowed", AuthenticationFlowError.INTERNAL_ERROR) .orElseThrow(() -> new AuthenticationFlowException("Requested authentication execution is not allowed",
AuthenticationFlowError.INTERNAL_ERROR)
); );
model = processor.getRealm().getAuthenticationExecutionById(authExecId); model = processor.getRealm().getAuthenticationExecutionById(authExecId);
@ -128,7 +131,9 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
Response response = processSingleFlowExecutionModel(model, false); Response response = processSingleFlowExecutionModel(model, false);
if (response == null) { if (response == null) {
return continueAuthenticationAfterSuccessfulAction(model); return continueAuthenticationAfterSuccessfulAction(model);
} else return response; } else
return response;
}
} }
//handle case where execution is a flow - This can happen during user registration for example //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); boolean hasFormData = mediaType != null && mediaType.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
MultivaluedMap<String, String> formData = hasFormData ? context.getHttpRequest().getDecodedFormParameters() : null; 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) { if (formData != null) {
client_id = formData.getFirst(OAuth2Constants.CLIENT_ID); 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.util.TokenUtil;
import org.keycloak.utils.ProfileHelper; import org.keycloak.utils.ProfileHelper;
import javax.ws.rs.Consumes;
import javax.ws.rs.OPTIONS; import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
@ -160,11 +162,18 @@ public class TokenEndpoint {
this.event = event; this.event = event;
} }
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@POST @POST
public Response processGrantRequest() { public Response processGrantRequest() {
cors = Cors.add(request).auth().allowedMethods("POST").auth().exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS); 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); grantType = formParams.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM);
// https://tools.ietf.org/html/rfc6749#section-5.1 // 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.NotFoundException;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -61,10 +62,11 @@ public class AccountLoader {
Theme theme = getTheme(session); Theme theme = getTheme(session);
boolean deprecatedAccount = isDeprecatedFormsAccountConsole(theme); boolean deprecatedAccount = isDeprecatedFormsAccountConsole(theme);
UriInfo uriInfo = session.getContext().getUri();
if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) { if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
return new CorsPreflightService(request); 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); AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session);
if (authResult == null) { if (authResult == null) {
throw new NotAuthorizedException("Bearer token required"); 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.authorization.store.ScopeStore;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakUriInfo;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
@ -53,6 +54,7 @@ public abstract class AbstractResourceService {
protected final PermissionTicketStore ticketStore; protected final PermissionTicketStore ticketStore;
protected final ResourceStore resourceStore; protected final ResourceStore resourceStore;
protected final ScopeStore scopeStore; protected final ScopeStore scopeStore;
protected final KeycloakUriInfo uriInfo;
protected HttpRequest request; protected HttpRequest request;
protected Auth auth; protected Auth auth;
@ -64,6 +66,7 @@ public abstract class AbstractResourceService {
ticketStore = provider.getStoreFactory().getPermissionTicketStore(); ticketStore = provider.getStoreFactory().getPermissionTicketStore();
resourceStore = provider.getStoreFactory().getResourceStore(); resourceStore = provider.getStoreFactory().getResourceStore();
scopeStore = provider.getStoreFactory().getScopeStore(); scopeStore = provider.getStoreFactory().getScopeStore();
uriInfo = session.getContext().getUri();
} }
protected Response cors(Response.ResponseBuilder response) { protected Response cors(Response.ResponseBuilder response) {

View file

@ -219,14 +219,14 @@ public class ResourcesService extends AbstractResourceService {
if (nextPage) { if (nextPage) {
links.add(Link.fromUri( 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)) .build(first + max, max))
.rel("next").build()); .rel("next").build());
} }
if (first > 0) { if (first > 0) {
links.add(Link.fromUri( 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)) .build(Math.max(first - max, 0), max))
.rel("prev").build()); .rel("prev").build());
} }

View file

@ -65,6 +65,9 @@ import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; 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> * @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()) { try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
HttpPost post = new HttpPost(oauth.getResourceOwnerPasswordCredentialGrantUrl()); HttpPost post = new HttpPost(oauth.getResourceOwnerPasswordCredentialGrantUrl());
post.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED);
OAuthClient.AccessTokenResponse response = new OAuthClient.AccessTokenResponse(client.execute(post)); OAuthClient.AccessTokenResponse response = new OAuthClient.AccessTokenResponse(client.execute(post));
assertEquals(400, response.getStatusCode()); assertEquals(400, response.getStatusCode());