keycloak-aplcache
Changes
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java 10(+10 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java 88(+0 -88)
Details
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
index 51e9a45..df13c0b 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
@@ -35,6 +35,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
+import java.util.Map;
/**
* @author rodrigo.sasaki@icarros.com.br
@@ -132,4 +133,13 @@ public interface UserResource {
@Path("role-mappings")
public RoleMappingResource roles();
+
+ @GET
+ @Path("consents")
+ public List<Map<String, Object>> getConsents();
+
+ @DELETE
+ @Path("consents/{client}")
+ public void revokeConsent(@PathParam("client") String clientId);
+
}
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index c585c62..7fae66d 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -156,6 +156,8 @@ public class Messages {
public static final String STALE_CODE = "staleCodeMessage";
+ public static final String STALE_CODE_ACCOUNT = "staleCodeAccountMessage";
+
public static final String IDENTITY_PROVIDER_NOT_UNIQUE = "identityProviderNotUniqueMessage";
public static final String REALM_SUPPORTS_NO_CREDENTIALS = "realmSupportsNoCredentialsMessage";
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 9834ede..d186f28 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -547,6 +547,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
ClientSessionCode clientCode = parsedCode.clientSessionCode;
+ Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), Messages.CONSENT_DENIED);
+ if (accountManagementFailedLinking != null) {
+ return accountManagementFailedLinking;
+ }
+
return browserAuthentication(clientCode.getClientSession(), null);
}
@@ -558,6 +563,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
ClientSessionCode clientCode = parsedCode.clientSessionCode;
+ Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), message);
+ if (accountManagementFailedLinking != null) {
+ return accountManagementFailedLinking;
+ }
+
return browserAuthentication(clientCode.getClientSession(), message);
}
@@ -638,7 +648,12 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
if (!clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
logger.debugf("Authorization code is not valid. Client session ID: %s, Client session's action: %s", clientSession.getId(), clientSession.getAction());
- Response staleCodeError = redirectToErrorPage(Messages.STALE_CODE);
+
+ // Check if error happened during login or during linking from account management
+ Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), Messages.STALE_CODE_ACCOUNT);
+ Response staleCodeError = (accountManagementFailedLinking != null) ? accountManagementFailedLinking : redirectToErrorPage(Messages.STALE_CODE);
+
+
return ParsedCodeContext.response(staleCodeError);
}
@@ -655,6 +670,20 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return ParsedCodeContext.response(staleCodeError);
}
+ private Response checkAccountManagementFailedLinking(ClientSessionModel clientSession, String error, Object... parameters) {
+ if (clientSession.getUserSession() != null && clientSession.getClient() != null && clientSession.getClient().getClientId().equals(ACCOUNT_MANAGEMENT_CLIENT_ID)) {
+
+ this.event.event(EventType.FEDERATED_IDENTITY_LINK);
+ UserModel user = clientSession.getUserSession().getUser();
+ this.event.user(user);
+ this.event.detail(Details.USERNAME, user.getUsername());
+
+ return redirectToAccountErrorPage(clientSession, error, parameters);
+ } else {
+ return null;
+ }
+ }
+
private AuthenticationRequest createAuthenticationRequest(String providerId, ClientSessionCode clientSessionCode) {
ClientSessionModel clientSession = null;
String relayState = null;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
index e2fbfa2..63cf639 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
@@ -17,16 +17,12 @@
package org.keycloak.testsuite.broker;
-import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
-import org.keycloak.admin.client.resource.RealmResource;
-import org.keycloak.common.util.Time;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessTokenResponse;
-import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.Urls;
@@ -41,10 +37,8 @@ import org.openqa.selenium.NoSuchElementException;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
-import java.util.List;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
@@ -141,88 +135,6 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
keycloak.realm("realm-with-broker").identityProviders().get("kc-oidc-idp").update(idp);
}
-
- @Test
- public void testConsentDeniedWithExpiredClientSession() throws Exception {
- // Disable update profile
- setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
-
- Keycloak keycloak1 = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", org.keycloak.models.Constants.ADMIN_CLI_CLIENT_ID);
- Keycloak keycloak2 = Keycloak.getInstance("http://localhost:8082/auth", "master", "admin", "admin", org.keycloak.models.Constants.ADMIN_CLI_CLIENT_ID);
-
- // Require broker to show consent screen
- RealmResource brokeredRealm = keycloak2.realm("realm-with-oidc-identity-provider");
- List<ClientRepresentation> clients = brokeredRealm.clients().findByClientId("broker-app");
- Assert.assertEquals(1, clients.size());
- ClientRepresentation brokerApp = clients.get(0);
- brokerApp.setConsentRequired(true);
- brokeredRealm.clients().get(brokerApp.getId()).update(brokerApp);
-
- // Change timeouts on realm-with-broker to lower values
- RealmResource realmWithBroker = keycloak1.realm("realm-with-broker");
- RealmRepresentation realmBackup = realmWithBroker.toRepresentation();
- RealmRepresentation realm = realmWithBroker.toRepresentation();
- realm.setAccessCodeLifespanLogin(30);;
- realm.setAccessCodeLifespan(30);
- realm.setAccessCodeLifespanUserAction(30);
- realmWithBroker.update(realm);
-
- // Login to broker
- loginIDP("test-user");
-
- // Set time offset
- Time.setOffset(60);
- try {
- // User rejected consent
- grantPage.assertCurrent();
- grantPage.cancel();
-
- // Assert error page with backToApplication link displayed
- errorPage.assertCurrent();
- errorPage.clickBackToApplication();
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
-
- // Login to broker again
- loginIDP("test-user");
-
- // Set time offset and manually remove expiredSessions TODO: Will require custom endpoint when migrate to integration-arquillian
- Time.setOffset(120);
-
- brokerServerRule.stopSession(this.session, true);
- this.session = brokerServerRule.startSession();
-
- session.sessions().removeExpired(getRealm());
-
- brokerServerRule.stopSession(this.session, true);
- this.session = brokerServerRule.startSession();
-
- // User rejected consent
- grantPage.assertCurrent();
- grantPage.cancel();
-
- // Assert error page without backToApplication link (clientSession expired and was removed on the server)
- errorPage.assertCurrent();
- try {
- errorPage.clickBackToApplication();
- fail("Not expected to have link backToApplication available");
- } catch (NoSuchElementException nsee) {
- // Expected;
- }
-
- } finally {
- Time.setOffset(0);
- }
-
- // Revert consent
- brokerApp.setConsentRequired(false);
- brokeredRealm.clients().get(brokerApp.getId()).update(brokerApp);
-
- // Revert timeouts
- realmWithBroker.update(realmBackup);
- }
-
@Test
public void testSuccessfulAuthenticationWithoutUpdateProfile() {
super.testSuccessfulAuthenticationWithoutUpdateProfile();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java
new file mode 100644
index 0000000..d648cdd
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.broker;
+
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.common.util.Time;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.KeycloakServer;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.openqa.selenium.NoSuchElementException;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OIDCKeycloakServerBrokerWithConsentTest extends AbstractIdentityProviderTest {
+
+ private static final int PORT = 8082;
+
+ private static Keycloak keycloak1;
+ private static Keycloak keycloak2;
+
+ @ClassRule
+ public static AbstractKeycloakRule oidcServerRule = new AbstractKeycloakRule() {
+
+ @Override
+ protected void configureServer(KeycloakServer server) {
+ server.getConfig().setPort(PORT);
+ }
+
+ @Override
+ protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+ server.importRealm(getClass().getResourceAsStream("/broker-test/test-broker-realm-with-kc-oidc.json"));
+
+ // Disable update profile
+ RealmModel realm = getRealm(session);
+ setUpdateProfileFirstLogin(realm, IdentityProviderRepresentation.UPFLM_OFF);
+ }
+
+ @Override
+ protected String[] getTestRealms() {
+ return new String[] { "realm-with-oidc-identity-provider" };
+ }
+ };
+
+
+ @BeforeClass
+ public static void before() {
+ keycloak1 = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", org.keycloak.models.Constants.ADMIN_CLI_CLIENT_ID);
+ keycloak2 = Keycloak.getInstance("http://localhost:8082/auth", "master", "admin", "admin", org.keycloak.models.Constants.ADMIN_CLI_CLIENT_ID);
+
+ // Require broker to show consent screen
+ RealmResource brokeredRealm = keycloak2.realm("realm-with-oidc-identity-provider");
+ List<ClientRepresentation> clients = brokeredRealm.clients().findByClientId("broker-app");
+ Assert.assertEquals(1, clients.size());
+ ClientRepresentation brokerApp = clients.get(0);
+ brokerApp.setConsentRequired(true);
+ brokeredRealm.clients().get(brokerApp.getId()).update(brokerApp);
+
+
+ // Change timeouts on realm-with-broker to lower values
+ RealmResource realmWithBroker = keycloak1.realm("realm-with-broker");
+ RealmRepresentation realmRep = realmWithBroker.toRepresentation();
+ realmRep.setAccessCodeLifespanLogin(30);;
+ realmRep.setAccessCodeLifespan(30);
+ realmRep.setAccessCodeLifespanUserAction(30);
+ realmWithBroker.update(realmRep);
+ }
+
+
+ @Override
+ protected String getProviderId() {
+ return "kc-oidc-idp";
+ }
+
+
+ // KEYCLOAK-2769
+ @Test
+ public void testConsentDeniedWithExpiredClientSession() throws Exception {
+ // Login to broker
+ loginIDP("test-user");
+
+ // Set time offset
+ Time.setOffset(60);
+ try {
+ // User rejected consent
+ grantPage.assertCurrent();
+ grantPage.cancel();
+
+ // Assert error page with backToApplication link displayed
+ errorPage.assertCurrent();
+ errorPage.clickBackToApplication();
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+ } finally {
+ Time.setOffset(0);
+ }
+ }
+
+
+ // KEYCLOAK-2769
+ @Test
+ public void testConsentDeniedWithExpiredAndClearedClientSession() throws Exception {
+ // Login to broker again
+ loginIDP("test-user");
+
+ // Set time offset
+ Time.setOffset(60);
+ try {
+ // Manually remove expiredSessions TODO: Will require custom endpoint when migrate to integration-arquillian
+ brokerServerRule.stopSession(this.session, true);
+ this.session = brokerServerRule.startSession();
+
+ session.sessions().removeExpired(getRealm());
+
+ brokerServerRule.stopSession(this.session, true);
+ this.session = brokerServerRule.startSession();
+
+ // User rejected consent
+ grantPage.assertCurrent();
+ grantPage.cancel();
+
+ // Assert error page without backToApplication link (clientSession expired and was removed on the server)
+ errorPage.assertCurrent();
+ try {
+ errorPage.clickBackToApplication();
+ fail("Not expected to have link backToApplication available");
+ } catch (NoSuchElementException nsee) {
+ // Expected;
+ }
+
+ } finally {
+ Time.setOffset(0);
+ }
+ }
+
+
+ // KEYCLOAK-2801
+ @Test
+ public void testAccountManagementLinkingAndExpiredClientSession() throws Exception {
+ // Login as pedroigor to account management
+ loginToAccountManagement("pedroigor");
+
+ // Link my "pedroigor" identity with "test-user" from brokered Keycloak
+ accountFederatedIdentityPage.clickAddProvider(getProviderId());
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+ this.loginPage.login("test-user", "password");
+
+ // Set time offset
+ Time.setOffset(60);
+ try {
+ // User rejected consent
+ grantPage.assertCurrent();
+ grantPage.cancel();
+
+ // Assert account error page with "staleCodeAccount" error displayed
+ accountFederatedIdentityPage.assertCurrent();
+ Assert.assertEquals("The page expired. Please try one more time.", accountFederatedIdentityPage.getError());
+
+
+ // Try to link one more time
+ accountFederatedIdentityPage.clickAddProvider(getProviderId());
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+ this.loginPage.login("test-user", "password");
+
+ Time.setOffset(120);
+
+ // User granted consent
+ grantPage.assertCurrent();
+ grantPage.accept();
+
+ // Assert account error page with "staleCodeAccount" error displayed
+ accountFederatedIdentityPage.assertCurrent();
+ Assert.assertEquals("The page expired. Please try one more time.", accountFederatedIdentityPage.getError());
+
+ } finally {
+ Time.setOffset(0);
+ }
+
+ // Revoke consent
+ RealmResource brokeredRealm = keycloak2.realm("realm-with-oidc-identity-provider");
+ List<UserRepresentation> users = brokeredRealm.users().search("test-user", 0, 1);
+ brokeredRealm.users().get(users.get(0).getId()).revokeConsent("broker-app");
+ }
+
+
+ @Test
+ public void testLoginCancelConsent() throws Exception {
+ // Try to login
+ loginIDP("test-user");
+
+ // User rejected consent
+ grantPage.assertCurrent();
+ grantPage.cancel();
+
+ // Assert back on login page
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/"));
+ assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+ }
+
+
+ // KEYCLOAK-2802
+ @Test
+ public void testAccountManagementLinkingCancelConsent() throws Exception {
+ // Login as pedroigor to account management
+ loginToAccountManagement("pedroigor");
+
+ // Link my "pedroigor" identity with "test-user" from brokered Keycloak
+ accountFederatedIdentityPage.clickAddProvider(getProviderId());
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+ this.loginPage.login("test-user", "password");
+
+ // User rejected consent
+ grantPage.assertCurrent();
+ grantPage.cancel();
+
+ // Assert account error page with "consentDenied" error displayed
+ accountFederatedIdentityPage.assertCurrent();
+ Assert.assertEquals("Consent denied.", accountFederatedIdentityPage.getError());
+ }
+
+
+ private void loginToAccountManagement(String username) {
+ accountFederatedIdentityPage.realm("realm-with-broker");
+ accountFederatedIdentityPage.open();
+ assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+ loginPage.login(username, "password");
+ assertTrue(accountFederatedIdentityPage.isCurrent());
+ }
+}
diff --git a/themes/src/main/resources/theme/base/account/messages/messages_en.properties b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
index 5803146..8c0727f 100755
--- a/themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -135,6 +135,8 @@ federatedIdentityRemovingLastProviderMessage=You can''t remove last federated id
identityProviderRedirectErrorMessage=Failed to redirect to identity provider.
identityProviderRemovedMessage=Identity provider removed successfully.
identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
+staleCodeAccountMessage=The page expired. Please try one more time.
+consentDenied=Consent denied.
accountDisabledMessage=Account is disabled, contact admin.