diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java index 9efa157cc5..662cb1c090 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java @@ -111,6 +111,12 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe AuthorizationEndpointRequest request = AuthorizationEndpointRequestParserProcessor.parseRequest(event, session, client, httpRequest.getDecodedFormParameters()); + if (request.getInvalidRequestMessage() != null) { + event.error(Errors.INVALID_REQUEST); + throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, + request.getInvalidRequestMessage(), Response.Status.BAD_REQUEST); + } + if (!TokenUtil.isOIDCRequest(request.getScope())) { ServicesLogger.LOGGER.oidcScopeMissing(); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuth2DeviceAuthorizationGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuth2DeviceAuthorizationGrantTest.java index 692124e878..a2367ec03c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuth2DeviceAuthorizationGrantTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuth2DeviceAuthorizationGrantTest.java @@ -51,7 +51,18 @@ import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.UserBuilder; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.impl.client.CloseableHttpClient; +import org.keycloak.util.BasicAuthHelper; + import java.util.List; +import java.util.LinkedList; + +import java.io.UnsupportedEncodingException; /** * @author Hiroyuki Wada @@ -576,6 +587,17 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest { } } + @Test + public void testDuplicatedRequestParams() throws Exception { + oauth.realm(REALM_NAME); + oauth.clientId(DEVICE_APP_PUBLIC); + OAuthClient.DeviceAuthorizationResponse response = doDeviceAuthorizationWithDuplicatedParams(DEVICE_APP_PUBLIC, null); + + Assert.assertEquals(400, response.getStatusCode()); + Assert.assertEquals("invalid_grant", response.getError()); + Assert.assertEquals("duplicated parameter", response.getErrorDescription()); + } + @Test public void testDeviceCodeLifespanPerClient() throws Exception { ClientResource client = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), DEVICE_APP); @@ -825,4 +847,31 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest { private void openVerificationPage(String verificationUri) { driver.navigate().to(verificationUri); } + + private OAuthClient.DeviceAuthorizationResponse doDeviceAuthorizationWithDuplicatedParams(String clientId, String clientSecret) throws Exception { + try (CloseableHttpClient client = HttpClientBuilder.create().build()) { + HttpPost post = new HttpPost(oauth.getDeviceAuthorizationUrl()); + + List parameters = new LinkedList<>(); + if (clientSecret != null) { + String authorization = BasicAuthHelper.createHeader(clientId, clientSecret); + post.setHeader("Authorization", authorization); + } else { + parameters.add(new BasicNameValuePair("client_id", clientId)); + } + + parameters.add(new BasicNameValuePair(OAuth2Constants.SCOPE, "profile")); + parameters.add(new BasicNameValuePair(OAuth2Constants.SCOPE, "foo")); + + UrlEncodedFormEntity formEntity; + try { + formEntity = new UrlEncodedFormEntity(parameters, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + post.setEntity(formEntity); + + return new OAuthClient.DeviceAuthorizationResponse(client.execute(post)); + } + } }