KEYCLOAK-19237 Avoid using stream that has been operated

This commit is contained in:
Hiroyuki Wada 2021-09-07 03:34:36 +09:00 committed by Marek Posolda
parent 10c3e149d3
commit 884471c729
2 changed files with 48 additions and 3 deletions

View file

@ -273,15 +273,14 @@ public class DeviceGrantType {
// Compute client scopes again from scope parameter. Check if user still has them granted // 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) // (but in device_code-to-token request, it could just theoretically happen that they are not available)
String scopeParam = deviceCodeModel.getScope(); String scopeParam = deviceCodeModel.getScope();
Stream<ClientScopeModel> clientScopes = TokenManager.getRequestedClientScopes(scopeParam, client); if (!TokenManager.verifyConsentStillAvailable(session, user, client, TokenManager.getRequestedClientScopes(scopeParam, client))) {
if (!TokenManager.verifyConsentStillAvailable(session, user, client, clientScopes)) {
event.error(Errors.NOT_ALLOWED); event.error(Errors.NOT_ALLOWED);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE,
"Client no longer has requested consent from user", Response.Status.BAD_REQUEST); "Client no longer has requested consent from user", Response.Status.BAD_REQUEST);
} }
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndClientScopes(clientSession, 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 // Set nonce as an attribute in the ClientSessionContext. Will be used for the token generation
clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, deviceCodeModel.getNonce()); clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, deviceCodeModel.getNonce());

View file

@ -27,6 +27,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.OAuth2DeviceConfig; import org.keycloak.models.OAuth2DeviceConfig;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; 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 REALM_NAME = "test";
public static final String DEVICE_APP = "test-device"; 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 = "test-device-public";
public static final String DEVICE_APP_PUBLIC_CUSTOM_CONSENT = "test-device-public-custom-consent";
@Rule @Rule
public AssertEvents events = new AssertEvents(this); public AssertEvents events = new AssertEvents(this);
@ -93,6 +95,14 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest {
.build(); .build();
realm.client(appPublic); 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(); userId = KeycloakModelUtils.generateId();
UserRepresentation user = UserBuilder.create() UserRepresentation user = UserBuilder.create()
.id(userId) .id(userId)
@ -192,6 +202,42 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest {
assertNotNull(token); 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 @Test
public void testNoRefreshToken() throws Exception { public void testNoRefreshToken() throws Exception {
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), DEVICE_APP); ClientResource client = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), DEVICE_APP);