diff --git a/server-spi-private/src/main/java/org/keycloak/events/Errors.java b/server-spi-private/src/main/java/org/keycloak/events/Errors.java index 807434a9ed..429be9143c 100755 --- a/server-spi-private/src/main/java/org/keycloak/events/Errors.java +++ b/server-spi-private/src/main/java/org/keycloak/events/Errors.java @@ -30,6 +30,7 @@ public interface Errors { String CLIENT_DISABLED = "client_disabled"; String INVALID_CLIENT_CREDENTIALS = "invalid_client_credentials"; String INVALID_CLIENT = "invalid_client"; + String UNAUTHORIZED_CLIENT ="unauthorized_client"; String CONSENT_DENIED = "consent_denied"; String RESOLVE_REQUIRED_ACTIONS = "resolve_required_actions"; 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 6bb4630d69..6e08031256 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 @@ -364,8 +364,7 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe if (client == null) { event.error(Errors.INVALID_REQUEST); - throw new ErrorPageException(session, null, Response.Status.BAD_REQUEST, Messages.MISSING_PARAMETER, - OIDCLoginProtocol.CLIENT_ID_PARAM); + throw new ErrorResponseException(Errors.INVALID_REQUEST, "Missing parameters:"+ OIDCLoginProtocol.CLIENT_ID_PARAM,Response.Status.BAD_REQUEST); } checkClient(client.getClientId()); @@ -376,8 +375,7 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe private ClientModel checkClient(String clientId) { if (clientId == null) { event.error(Errors.INVALID_REQUEST); - throw new ErrorPageException(session, null, Response.Status.BAD_REQUEST, - Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM); + throw new ErrorResponseException(Errors.INVALID_REQUEST, "Missing parameters:"+ OIDCLoginProtocol.CLIENT_ID_PARAM, Response.Status.BAD_REQUEST); } event.client(clientId); @@ -385,24 +383,22 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe ClientModel client = realm.getClientByClientId(clientId); if (client == null) { event.error(Errors.CLIENT_NOT_FOUND); - throw new ErrorPageException(session, null, Response.Status.BAD_REQUEST, - Messages.CLIENT_NOT_FOUND); + throw new ErrorResponseException(Errors.INVALID_CLIENT, "Client not found.", Response.Status.BAD_REQUEST); } if (!client.isEnabled()) { event.error(Errors.CLIENT_DISABLED); - throw new ErrorPageException(session, null, Response.Status.BAD_REQUEST, Messages.CLIENT_DISABLED); + throw new ErrorResponseException(Errors.INVALID_CLIENT, "Client disabled.", Response.Status.BAD_REQUEST); } if (!realm.getOAuth2DeviceConfig().isOAuth2DeviceAuthorizationGrantEnabled(client)) { event.error(Errors.NOT_ALLOWED); - throw new ErrorPageException(session, null, Response.Status.BAD_REQUEST, - Messages.OAUTH2_DEVICE_AUTHORIZATION_GRANT_DISABLED); + throw new ErrorResponseException(Errors.UNAUTHORIZED_CLIENT, "Client is not allowed to initiate OAuth 2.0 Device Authorization Grant. The flow is disabled for the client.", Response.Status.BAD_REQUEST); } if (client.isBearerOnly()) { event.error(Errors.NOT_ALLOWED); - throw new ErrorPageException(session, null, Response.Status.FORBIDDEN, Messages.BEARER_ONLY); + throw new ErrorResponseException(Errors.UNAUTHORIZED_CLIENT, "Bearer-only applications are not allowed to initiate browser login.", Response.Status.FORBIDDEN); } String protocol = client.getProtocol(); @@ -413,7 +409,7 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe } if (!protocol.equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) { event.error(Errors.INVALID_CLIENT); - throw new ErrorPageException(session, null, Response.Status.BAD_REQUEST, "Wrong client protocol."); + throw new ErrorResponseException(Errors.UNAUTHORIZED_CLIENT, "Wrong client protocol." , Response.Status.BAD_REQUEST); } session.getContext().setClient(client); 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 fa945d2461..7e9185c475 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 @@ -28,6 +28,7 @@ import org.junit.Test; import org.keycloak.OAuth2Constants; import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.events.Errors; import org.keycloak.models.ClientScopeModel; import org.keycloak.models.OAuth2DeviceConfig; import org.keycloak.models.utils.KeycloakModelUtils; @@ -851,6 +852,51 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest { verificationPage.assertInvalidUserCodePage(); } + @Test + public void testNotFoundClient() throws Exception { + oauth.realm(REALM_NAME); + oauth.clientId("test-device-public2"); + OAuthClient.DeviceAuthorizationResponse response = oauth.doDeviceAuthorizationRequest("test-device-public2", null); + + Assert.assertEquals(400, response.getStatusCode()); + Assert.assertEquals(Errors.INVALID_CLIENT, response.getError()); + Assert.assertEquals("Invalid client credentials", response.getErrorDescription()); + } + @Test + public void testClientWithErrors() throws Exception { + try { + ClientResource client = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), DEVICE_APP_PUBLIC); + ClientRepresentation clientRepresentation = client.toRepresentation(); + clientRepresentation.getAttributes().put(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "false"); + client.update(clientRepresentation); + oauth.realm(REALM_NAME); + oauth.clientId(DEVICE_APP_PUBLIC); + + //DeviceAuthorizationGrant not enabled + OAuthClient.DeviceAuthorizationResponse response = oauth.doDeviceAuthorizationRequest(DEVICE_APP_PUBLIC, null); + Assert.assertEquals(400, response.getStatusCode()); + Assert.assertEquals(Errors.UNAUTHORIZED_CLIENT, response.getError()); + Assert.assertEquals("Client is not allowed to initiate OAuth 2.0 Device Authorization Grant. The flow is disabled for the client.", response.getErrorDescription()); + + clientRepresentation.getAttributes().put(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true"); + clientRepresentation.setBearerOnly(true); + client.update(clientRepresentation); + + //BearerOnly client + response = oauth.doDeviceAuthorizationRequest(DEVICE_APP_PUBLIC, null); + Assert.assertEquals(403, response.getStatusCode()); + Assert.assertEquals(Errors.UNAUTHORIZED_CLIENT, response.getError()); + Assert.assertEquals("Bearer-only applications are not allowed to initiate browser login.", response.getErrorDescription()); + + } finally { + ClientResource client = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), DEVICE_APP_PUBLIC); + ClientRepresentation clientRepresentation = client.toRepresentation(); + clientRepresentation.getAttributes().put(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true"); + clientRepresentation.setBearerOnly(false); + client.update(clientRepresentation); + } + } + private void openVerificationPage(String verificationUri) { driver.navigate().to(verificationUri); }