Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ private static Response.Status getResponseStatus(Throwable throwable) {
return Response.Status.CONFLICT;
}

if (throwable instanceof IllegalArgumentException && isMediaTypeParsingError(throwable)) {
return Response.Status.BAD_REQUEST;
}

return Response.Status.INTERNAL_SERVER_ERROR;
}

Expand All @@ -146,13 +150,37 @@ private static String getErrorCode(Throwable throwable) {
return "conflict";
}

if (throwable instanceof IllegalArgumentException && isMediaTypeParsingError(throwable)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this breaks when run with the current Quarkus main considering I fixed the logic upstream to get 415, now Quarkus REST do not throw IllegalArgumentException.

return OAuthErrorException.INVALID_REQUEST;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it desirable to return the invalid request description for every Keycloak request with a malformed content type? Should admin API requests receive 415 instead?

}

if (throwable instanceof WebApplicationException && throwable.getMessage() != null) {
return throwable.getMessage();
}

return "unknown_error";
}

private static boolean isMediaTypeParsingError(Throwable throwable) {
Throwable current = throwable;
while (current != null) {
String msg = current.getMessage();
if (msg != null) {
String lower = msg.toLowerCase(Locale.ROOT);
if (lower.contains("media type") || lower.contains("content-type") ||
lower.contains("could not find") || lower.contains("invalid")) {
return true;
}
}
Class<?> cls = current.getClass();
if (cls.getName().contains("MediaType") || cls.getName().contains("BadRequest")) {
return true;
}
current = current.getCause();
}
return false;
}

private static RealmModel resolveRealm(KeycloakSession session) {
String path = session.getContext().getUri().getPath();
Matcher m = realmNamePattern.matcher(path);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.keycloak.tests.error;

import java.io.IOException;
import java.net.URI;

import org.keycloak.OAuthErrorException;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.testframework.annotations.InjectHttpClient;
import org.keycloak.testframework.annotations.InjectKeycloakUrls;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.server.KeycloakUrls;
import org.keycloak.util.JsonSerialization;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

@KeycloakIntegrationTest
class MalformedContentTypeTest {

@InjectKeycloakUrls
KeycloakUrls keycloakUrls;

@InjectHttpClient
HttpClient httpClient;

@InjectRealm(attachTo = "master")
ManagedRealm masterRealm;

@Test
void malformedContentTypeOnTokenEndpoint() throws IOException {
HttpPost post = new HttpPost(tokenUri());
post.setHeader("Content-Type", "invalid/@@##");
post.setEntity(new StringEntity("grant_type=password&client_id=admin-cli&username=admin&password=admin"));

HttpResponse response = httpClient.execute(post);
assertThat("status code should be 400", response.getStatusLine().getStatusCode(), is(400));

OAuth2ErrorRepresentation error = JsonSerialization.readValue(
response.getEntity().getContent(), OAuth2ErrorRepresentation.class);
assertThat("error code", error.getError(), is(OAuthErrorException.INVALID_REQUEST));
}

@Test
void emptyContentTypeOnTokenEndpoint() throws IOException {
HttpPost post = new HttpPost(tokenUri());
post.setHeader("Content-Type", "");
post.setEntity(new StringEntity("grant_type=password&client_id=admin-cli&username=admin&password=admin"));

HttpResponse response = httpClient.execute(post);
assertThat("status code should be 400", response.getStatusLine().getStatusCode(), is(400));

OAuth2ErrorRepresentation error = JsonSerialization.readValue(
response.getEntity().getContent(), OAuth2ErrorRepresentation.class);
assertThat("error code", error.getError(), is(OAuthErrorException.INVALID_REQUEST));
}

@Test
void xssPayloadContentTypeOnTokenEndpoint() throws IOException {
HttpPost post = new HttpPost(tokenUri());
post.setHeader("Content-Type", "</>\"alert(1)");
post.setEntity(new StringEntity("grant_type=password&client_id=admin-cli&username=admin&password=admin"));

HttpResponse response = httpClient.execute(post);
assertThat("status code should be 400", response.getStatusLine().getStatusCode(), is(400));

OAuth2ErrorRepresentation error = JsonSerialization.readValue(
response.getEntity().getContent(), OAuth2ErrorRepresentation.class);
assertThat("error code", error.getError(), is(OAuthErrorException.INVALID_REQUEST));
}

@Test
void xssScriptPayloadContentTypeOnTokenEndpoint() throws IOException {
HttpPost post = new HttpPost(tokenUri());
post.setHeader("Content-Type", "</><script>alert(1)</script>");
post.setEntity(new StringEntity("grant_type=password&client_id=admin-cli&username=admin&password=admin"));

HttpResponse response = httpClient.execute(post);
assertThat("status code should be 400", response.getStatusLine().getStatusCode(), is(400));

OAuth2ErrorRepresentation error = JsonSerialization.readValue(
response.getEntity().getContent(), OAuth2ErrorRepresentation.class);
assertThat("error code", error.getError(), is(OAuthErrorException.INVALID_REQUEST));
}

private URI tokenUri() {
return KeycloakUriBuilder.fromUri(keycloakUrls.getMasterRealm())
.path("protocol/openid-connect/token")
.build();
}
}
Loading