From 884471c7296017d7c6dc2e060a8904b36f5fb811 Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Tue, 7 Sep 2021 03:34:36 +0900 Subject: [PATCH] KEYCLOAK-19237 Avoid using stream that has been operated --- .../oidc/grants/device/DeviceGrantType.java | 5 +- .../OAuth2DeviceAuthorizationGrantTest.java | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/device/DeviceGrantType.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/device/DeviceGrantType.java index 8c02540ccc..2f8dc599c1 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/device/DeviceGrantType.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/device/DeviceGrantType.java @@ -273,15 +273,14 @@ public class DeviceGrantType { // Compute client scopes again from scope parameter. Check if user still has them granted // (but in device_code-to-token request, it could just theoretically happen that they are not available) String scopeParam = deviceCodeModel.getScope(); - Stream clientScopes = TokenManager.getRequestedClientScopes(scopeParam, client); - if (!TokenManager.verifyConsentStillAvailable(session, user, client, clientScopes)) { + if (!TokenManager.verifyConsentStillAvailable(session, user, client, TokenManager.getRequestedClientScopes(scopeParam, client))) { event.error(Errors.NOT_ALLOWED); throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, "Client no longer has requested consent from user", Response.Status.BAD_REQUEST); } ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndClientScopes(clientSession, - clientScopes, session); + TokenManager.getRequestedClientScopes(scopeParam, client), session); // Set nonce as an attribute in the ClientSessionContext. Will be used for the token generation clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, deviceCodeModel.getNonce()); 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 50bfdf259a..a73434e78c 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 @@ -27,6 +27,7 @@ import org.junit.Rule; import org.junit.Test; import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.models.ClientScopeModel; import org.keycloak.models.OAuth2DeviceConfig; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; @@ -59,6 +60,7 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest { public static final String REALM_NAME = "test"; public static final String DEVICE_APP = "test-device"; public static final String DEVICE_APP_PUBLIC = "test-device-public"; + public static final String DEVICE_APP_PUBLIC_CUSTOM_CONSENT = "test-device-public-custom-consent"; @Rule public AssertEvents events = new AssertEvents(this); @@ -93,6 +95,14 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest { .build(); realm.client(appPublic); + ClientRepresentation appPublicCustomConsent = ClientBuilder.create().id(KeycloakModelUtils.generateId()).publicClient() + .clientId(DEVICE_APP_PUBLIC_CUSTOM_CONSENT).attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true") + .consentRequired(true) + .attribute(ClientScopeModel.DISPLAY_ON_CONSENT_SCREEN, "true") + .attribute(ClientScopeModel.CONSENT_SCREEN_TEXT, "This is the custom consent screen text.") + .build(); + realm.client(appPublicCustomConsent); + userId = KeycloakModelUtils.generateId(); UserRepresentation user = UserBuilder.create() .id(userId) @@ -192,6 +202,42 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest { assertNotNull(token); } + @Test + public void testPublicClientCustomConsent() throws Exception { + // Device Authorization Request from device + oauth.realm(REALM_NAME); + oauth.clientId(DEVICE_APP_PUBLIC_CUSTOM_CONSENT); + OAuthClient.DeviceAuthorizationResponse response = oauth.doDeviceAuthorizationRequest(DEVICE_APP_PUBLIC_CUSTOM_CONSENT, null); + + Assert.assertEquals(200, response.getStatusCode()); + assertNotNull(response.getDeviceCode()); + assertNotNull(response.getUserCode()); + assertNotNull(response.getVerificationUri()); + assertNotNull(response.getVerificationUriComplete()); + Assert.assertEquals(60, response.getExpiresIn()); + Assert.assertEquals(5, response.getInterval()); + + openVerificationPage(response.getVerificationUriComplete()); + + // Do Login + oauth.fillLoginForm("device-login", "password"); + + // Consent + Assert.assertTrue(grantPage.getDisplayedGrants().contains("This is the custom consent screen text.")); + grantPage.accept(); + + // Token request from device + OAuthClient.AccessTokenResponse tokenResponse = oauth.doDeviceTokenRequest(DEVICE_APP_PUBLIC_CUSTOM_CONSENT, null, response.getDeviceCode()); + + Assert.assertEquals(200, tokenResponse.getStatusCode()); + + String tokenString = tokenResponse.getAccessToken(); + assertNotNull(tokenString); + AccessToken token = oauth.verifyToken(tokenString); + + assertNotNull(token); + } + @Test public void testNoRefreshToken() throws Exception { ClientResource client = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), DEVICE_APP);