Details
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 0db650c..b484d5f 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -40,6 +40,7 @@ import org.keycloak.models.utils.SessionTimeoutHelper;
import org.keycloak.models.utils.SystemClientUtil;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocol.Error;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ServicesLogger;
@@ -53,6 +54,7 @@ import org.keycloak.services.util.P3PHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.CommonClientSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel;
+import org.keycloak.util.TokenUtil;
import javax.crypto.SecretKey;
import javax.ws.rs.core.Cookie;
@@ -863,7 +865,7 @@ public class AuthenticationManager {
if (client.isConsentRequired()) {
- UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user.getId(), client.getId());
+ UserConsentModel grantedConsent = getEffectiveGrantedConsent(session, authSession);
// See if any clientScopes need to be approved on consent screen
List<ClientScopeModel> clientScopesToApprove = getClientScopesToApproveOnConsentScreen(realm, grantedConsent, authSession);
@@ -881,6 +883,21 @@ public class AuthenticationManager {
}
+ private static UserConsentModel getEffectiveGrantedConsent(KeycloakSession session, AuthenticationSessionModel authSession) {
+ // If prompt=consent, we ignore existing persistent consent
+ String prompt = authSession.getClientNote(OIDCLoginProtocol.PROMPT_PARAM);
+ if (TokenUtil.hasPrompt(prompt, OIDCLoginProtocol.PROMPT_VALUE_CONSENT)) {
+ return null;
+ } else {
+ final RealmModel realm = authSession.getRealm();
+ final UserModel user = authSession.getAuthenticatedUser();
+ final ClientModel client = authSession.getClient();
+
+ return session.users().getConsentByClient(realm, user.getId(), client.getId());
+ }
+ }
+
+
public static Response actionRequired(final KeycloakSession session, final AuthenticationSessionModel authSession,
final ClientConnection clientConnection,
final HttpRequest request, final UriInfo uriInfo, final EventBuilder event) {
@@ -906,7 +923,7 @@ public class AuthenticationManager {
if (client.isConsentRequired()) {
- UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user.getId(), client.getId());
+ UserConsentModel grantedConsent = getEffectiveGrantedConsent(session, authSession);
List<ClientScopeModel> clientScopesToApprove = getClientScopesToApproveOnConsentScreen(realm, grantedConsent, authSession);
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index a7abd4e..90a4ecb 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -834,16 +834,24 @@ public class LoginActionsService {
session.users().addConsent(realm, user.getId(), grantedConsent);
}
+ // Update may not be required if all clientScopes were already granted (May happen for example with prompt=consent)
+ boolean updateConsentRequired = false;
+
for (String clientScopeId : authSession.getClientScopes()) {
ClientScopeModel clientScope = KeycloakModelUtils.findClientScopeById(realm, clientScopeId);
if (clientScope != null) {
- grantedConsent.addGrantedClientScope(clientScope);
+ if (!grantedConsent.isClientScopeGranted(clientScope)) {
+ grantedConsent.addGrantedClientScope(clientScope);
+ updateConsentRequired = true;
+ }
} else {
- logger.warn("Client scope with ID '%s' not found");
+ logger.warnf("Client scope with ID '%s' not found", clientScopeId);
}
}
- session.users().updateConsent(realm, user.getId(), grantedConsent);
+ if (updateConsentRequired) {
+ session.users().updateConsent(realm, user.getId(), grantedConsent);
+ }
event.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED);
event.success();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
index a3f7e66..65d3897 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
@@ -26,6 +26,7 @@ import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.common.util.Time;
import org.keycloak.events.Details;
@@ -67,6 +68,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import javax.ws.rs.core.UriBuilder;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -288,6 +291,10 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
.assertEvent();
} finally {
+ // Revert consent
+ UserResource user = ApiUtil.findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
+ user.revokeConsent("test-app");
+
// revert require consent
ClientManager.realm(adminClient.realm("test")).clientId("test-app").consentRequired(false);
}
@@ -363,6 +370,68 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
Assert.assertTrue(errorPage.getError().startsWith("You are already authenticated as different user"));
}
+
+ // prompt=consent
+ @Test
+ public void promptConsent() {
+ // Require consent
+ ClientManager.realm(adminClient.realm("test")).clientId("test-app").consentRequired(true);
+
+ try {
+ // Login user
+ loginPage.open();
+ loginPage.login("test-user@localhost", "password");
+
+ // Grant consent
+ grantPage.assertCurrent();
+ grantPage.accept();
+
+ appPage.assertCurrent();
+ Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+ events.expectLogin()
+ .detail(Details.USERNAME, "test-user@localhost")
+ .detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED)
+ .assertEvent();
+
+
+ // Re-login without prompt=consent. The previous persistent consent was used
+ driver.navigate().to(oauth.getLoginFormUrl());
+ Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+ events.expectLogin()
+ .detail(Details.USERNAME, "test-user@localhost")
+ .detail(Details.CONSENT, Details.CONSENT_VALUE_PERSISTED_CONSENT)
+ .assertEvent();
+
+ // Re-login with prompt=consent.
+ String loginFormUri = UriBuilder.fromUri(oauth.getLoginFormUrl())
+ .queryParam(OIDCLoginProtocol.PROMPT_PARAM, OIDCLoginProtocol.PROMPT_VALUE_CONSENT)
+ .build().toString();
+ driver.navigate().to(loginFormUri);
+
+ // Assert grant page displayed again. Will need to grant consent again
+ grantPage.assertCurrent();
+ grantPage.accept();
+
+ appPage.assertCurrent();
+ Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+ events.expectLogin()
+ .detail(Details.USERNAME, "test-user@localhost")
+ .detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED)
+ .assertEvent();
+
+ } finally {
+ // Revert consent
+ UserResource user = ApiUtil.findUserByUsernameId(adminClient.realm("test"), "test-user@localhost");
+ user.revokeConsent("test-app");
+
+ // revert require consent
+ ClientManager.realm(adminClient.realm("test")).clientId("test-app").consentRequired(false);
+ }
+ }
+
+
// DISPLAY & OTHERS
@Test