diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/realm/ClientConfigBuilder.java b/test-framework/core/src/main/java/org/keycloak/testframework/realm/ClientConfigBuilder.java index 1a52b0a5551..dec7db366c2 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/realm/ClientConfigBuilder.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/realm/ClientConfigBuilder.java @@ -5,7 +5,6 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation; import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; public class ClientConfigBuilder { @@ -127,19 +126,22 @@ public class ClientConfigBuilder { } public ClientConfigBuilder defaultClientScopes(String... defaultClientScopes) { - if (rep.getDefaultClientScopes() == null) { - rep.setDefaultClientScopes(new LinkedList<>()); - } - - rep.getDefaultClientScopes().addAll(List.of(defaultClientScopes)); + rep.setDefaultClientScopes(Collections.combine(rep.getDefaultClientScopes(), defaultClientScopes)); return this; } public ClientConfigBuilder protocolMappers(List mappers) { - if (rep.getProtocolMappers() == null) { - rep.setProtocolMappers(new LinkedList<>()); - } - rep.getProtocolMappers().addAll(mappers); + rep.setProtocolMappers(Collections.combine(rep.getProtocolMappers(), mappers)); + return this; + } + + public ClientConfigBuilder consentRequired(boolean enabled) { + rep.setConsentRequired(enabled); + return this; + } + + public ClientConfigBuilder webOrigins(String... webOrigins) { + rep.setWebOrigins(Collections.combine(rep.getWebOrigins(), webOrigins)); return this; } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java b/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java index 667e6153eeb..44f4e37ac85 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java @@ -2,6 +2,7 @@ package org.keycloak.testframework.realm; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RolesRepresentation; import org.keycloak.representations.idm.UserRepresentation; @@ -220,6 +221,11 @@ public class RealmConfigBuilder { return this; } + public RealmConfigBuilder identityProvider(IdentityProviderRepresentation identityProvider) { + rep.addIdentityProvider(identityProvider); + return this; + } + /** * Best practice is to use other convenience methods when configuring a realm, but while the framework is under * active development there may not be a way to perform all updates required. In these cases this method allows diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/ConsentPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/ConsentPage.java new file mode 100644 index 00000000000..8f68d0e7d00 --- /dev/null +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/ConsentPage.java @@ -0,0 +1,49 @@ +/* + * 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.testframework.ui.page; + +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +/** + * @author Marko Strukelj + */ +public class ConsentPage extends AbstractPage { + + @FindBy(id = "kc-login") + private WebElement submitButton; + + @FindBy(id = "kc-cancel") + private WebElement cancelButton; + + public ConsentPage(WebDriver driver) { super(driver); } + + public void confirm() { + submitButton.click(); + } + + public void cancel() { + cancelButton.click(); + } + + @Override + public String getExpectedPageId() { + return "login-login-oauth-grant"; + } +} diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java index 6f12c5b845e..5b02e7f05f2 100644 --- a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java @@ -1,5 +1,6 @@ package org.keycloak.testframework.ui.page; +import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; @@ -28,6 +29,16 @@ public class LoginPage extends AbstractPage { submitButton.click(); } + public void clickSocial(String alias) { + WebElement socialButton = findSocialButton(alias); + socialButton.click(); + } + + public WebElement findSocialButton(String alias) { + String id = "social-" + alias; + return driver.findElement(By.id(id)); + } + @Override public String getExpectedPageId() { return "login-login"; diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/ConsentsTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/ConsentsTest.java new file mode 100644 index 00000000000..78ea6e2b463 --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/admin/ConsentsTest.java @@ -0,0 +1,463 @@ +/* + * 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.tests.admin; + +import org.jboss.logging.Logger; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.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.admin.client.resource.UsersResource; +import org.keycloak.events.Details; +import org.keycloak.events.Errors; +import org.keycloak.events.EventType; +import org.keycloak.models.ClientModel; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ClientScopeRepresentation; +import org.keycloak.representations.idm.EventRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.UserSessionRepresentation; +import org.keycloak.testframework.annotations.InjectClient; +import org.keycloak.testframework.annotations.InjectEvents; +import org.keycloak.testframework.annotations.InjectRealm; +import org.keycloak.testframework.annotations.InjectUser; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.testframework.events.Events; +import org.keycloak.testframework.injection.LifeCycle; +import org.keycloak.testframework.oauth.OAuthClient; +import org.keycloak.testframework.oauth.annotations.InjectOAuthClient; +import org.keycloak.testframework.realm.ClientConfig; +import org.keycloak.testframework.realm.ClientConfigBuilder; +import org.keycloak.testframework.realm.ManagedClient; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.ManagedUser; +import org.keycloak.testframework.realm.RealmConfig; +import org.keycloak.testframework.realm.RealmConfigBuilder; +import org.keycloak.testframework.realm.UserConfig; +import org.keycloak.testframework.realm.UserConfigBuilder; +import org.keycloak.testframework.ui.annotations.InjectPage; +import org.keycloak.testframework.ui.annotations.InjectWebDriver; +import org.keycloak.testframework.ui.page.ConsentPage; +import org.keycloak.testframework.ui.page.LoginPage; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.keycloak.tests.utils.admin.ApiUtil.findClientByClientId; + +import org.keycloak.testsuite.util.AccountHelper; +import org.keycloak.testsuite.util.oauth.AccessTokenResponse; +import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; + +/** + * @author Marko Strukelj + */ +@KeycloakIntegrationTest +public class ConsentsTest { + + @InjectRealm(ref = "user") + ManagedRealm userRealm; + + @InjectRealm(ref = "consumer", config = ConsumerRealmConf.class, lifecycle = LifeCycle.METHOD) + ManagedRealm consumerRealm; + + @InjectRealm(ref = "provider", lifecycle = LifeCycle.METHOD) + ManagedRealm providerRealm; + + @InjectUser(ref = "user", realmRef = "user", config = UserRealmUserConf.class) + ManagedUser userFromUserRealm; + + @InjectUser(ref = "provider", realmRef = "provider", config = ProviderRealmUserConf.class) + ManagedUser userFromProviderRealm; + + @InjectClient(realmRef = "provider", config = ProviderRealmClientConf.class) + ManagedClient providerRealmClient; + + @InjectOAuthClient(ref = "user", realmRef = "user") + OAuthClient userRealmOAuth; + + @InjectOAuthClient(ref = "consumer", realmRef = "consumer") + OAuthClient consumerRealmOAuth; + + @InjectOAuthClient(ref = "provider", realmRef = "provider") + OAuthClient providerRealmOAuth; + + @InjectEvents(realmRef = "user") + Events userRealmEvents; + + @InjectWebDriver + WebDriver driver; + + @InjectPage + LoginPage loginPage; + + @InjectPage + ConsentPage consentPage; + + private static final Logger LOGGER = Logger.getLogger(ConsentsTest.class); + + private static final String REALM_PROV_NAME = "provider"; + private static final String REALM_CONS_NAME = "consumer"; + + private static final String IDP_OIDC_ALIAS = "kc-oidc-idp"; + private static final String IDP_OIDC_PROVIDER_ID = "keycloak-oidc"; + + private static final String CLIENT_ID = "brokerapp"; + private static final String CLIENT_SECRET = "secret"; + + @Test + public void testConsents() { + consumerRealmOAuth.openLoginForm(); + + LOGGER.debug("Clicking social " + IDP_OIDC_ALIAS); + loginPage.clickSocial(IDP_OIDC_ALIAS); + + if (!driver.getCurrentUrl().contains("/realms/" + providerRealm.getName() + "/")) { + LOGGER.debug("Not on provider realm page, url: " + driver.getCurrentUrl()); + } + + Assertions.assertTrue(driver.getCurrentUrl().contains("/realms/" + providerRealm.getName() + "/"), "Driver should be on the provider realm page right now"); + + LOGGER.debug("Logging in"); + loginPage.fillLogin(userFromProviderRealm.getUsername(), userFromProviderRealm.getPassword()); + loginPage.submit(); + + consentPage.waitForPage(); + consentPage.assertCurrent(); + consentPage.confirm(); + + assertTrue(driver.getPageSource().contains("Happy days"), "Test user should be successfully logged in."); + + UsersResource consumerUsers = consumerRealm.admin().users(); + Assertions.assertTrue(consumerUsers.count() > 0, "There must be at least one user"); + + List users = consumerUsers.search("", 0, 5); + + UserRepresentation foundUser = null; + for (UserRepresentation userRep : users) { + if (userRep.getUsername().equals(userFromProviderRealm.getUsername()) && userRep.getEmail().equals(userFromProviderRealm.admin().toRepresentation().getEmail())) { + foundUser = userRep; + break; + } + } + + Assertions.assertNotNull(foundUser, "There must be user " + userFromProviderRealm.getUsername() + " in realm " + consumerRealm.getName()); + + // get user with the same username from provider realm + users = providerRealm.admin().users().search(null, foundUser.getFirstName(), foundUser.getLastName(), null, 0, 1); + Assertions.assertEquals(1, users.size(), "Same user should be in provider realm"); + + String userId = users.get(0).getId(); + UserResource userResource = providerRealm.admin().users().get(userId); + + // list consents + List> consents = userResource.getConsents(); + Assertions.assertEquals(1, consents.size(), "There should be one consent"); + + Map consent = consents.get(0); + Assertions.assertEquals(CLIENT_ID, consent.get("clientId"), "Consent should be given to " + CLIENT_ID); + + // list sessions. Single client should be in user session + List sessions = userResource.getUserSessions(); + Assertions.assertEquals(1, sessions.size(), "There should be one active session"); + Assertions.assertEquals(1, sessions.get(0).getClients().size(), "There should be one client in user session"); + + // revoke consent + userResource.revokeConsent(CLIENT_ID); + + // list consents + consents = userResource.getConsents(); + Assertions.assertEquals(0, consents.size(), "There should be no consents"); + + // list sessions + sessions = userResource.getUserSessions(); + Assertions.assertEquals(1, sessions.size(), "There should be one active session"); + Assertions.assertEquals(0, sessions.get(0).getClients().size(), "There should be no client in user session"); + + AccountHelper.logout(providerRealm.admin(), userFromProviderRealm.getUsername()); + } + + /** + * KEYCLOAK-18954 + */ + @Test + public void testRetrieveConsentsForUserWithClientsWithGrantedOfflineAccess() throws Exception { + + RealmRepresentation providerRealmRep = providerRealm.admin().toRepresentation(); + providerRealmRep.setAccountTheme("keycloak"); + providerRealm.admin().update(providerRealmRep); + providerRealm.admin().clients().create(ClientConfigBuilder.create().clientId("test-app").redirectUris("*").publicClient(true).webOrigins("*").build()); + + ClientRepresentation providerAccountRep = providerRealm.admin().clients().findByClientId("test-app").get(0); + + // add offline_scope to default account-console client scope + ClientScopeRepresentation offlineAccessScope = providerRealm.admin().getDefaultOptionalClientScopes().stream() + .filter(csr -> csr.getName().equals(OAuth2Constants.OFFLINE_ACCESS)).findFirst().get(); + providerRealm.admin().clients().get(providerAccountRep.getId()).removeOptionalClientScope(offlineAccessScope.getId()); + providerRealm.admin().clients().get(providerAccountRep.getId()).addDefaultClientScope(offlineAccessScope.getId()); + + // enable consent required to explicitly grant offline access + providerAccountRep.setConsentRequired(true); + providerAccountRep.setDirectAccessGrantsEnabled(true); // for offline token retrieval + providerRealm.admin().clients().get(providerAccountRep.getId()).update(providerAccountRep); + + // navigate to account console and login + providerRealmOAuth.openLoginForm(); + + loginPage.waitForPage(); + LOGGER.debug("Logging in"); + loginPage.fillLogin(userFromProviderRealm.getUsername(), userFromProviderRealm.getPassword()); + loginPage.submit(); + + consentPage.waitForPage(); + LOGGER.debug("Grant consent for offline_access"); + consentPage.assertCurrent(); + consentPage.confirm(); + + // disable consent required again to enable direct grant token retrieval. + providerAccountRep.setConsentRequired(false); + providerRealm.admin().clients().get(providerAccountRep.getId()).update(providerAccountRep); + + LOGGER.debug("Obtain offline_token"); + AccessTokenResponse response = providerRealmOAuth + .scope(OAuth2Constants.SCOPE_OPENID +" " + OAuth2Constants.SCOPE_PROFILE + " " + OAuth2Constants.OFFLINE_ACCESS) + .doPasswordGrantRequest(userFromProviderRealm.getUsername(), userFromProviderRealm.getPassword()); + assertNotNull(response.getRefreshToken()); + + LOGGER.debug("Check for Offline Token in consents"); + List> consents = providerRealm.admin().users().get(userFromProviderRealm.getId()).getConsents(); + assertFalse(consents.isEmpty(), "Consents should not be empty"); + + assertTrue(consents.toString().contains("Offline Token")); + + AccountHelper.logout(providerRealm.admin(), userFromProviderRealm.getUsername()); + } + + @Test + public void testConsentCancel() { + // setup account client to require consent + ClientResource accountClient = findClientByClientId(providerRealm.admin(), "test-app"); + + ClientRepresentation clientRepresentation = accountClient.toRepresentation(); + clientRepresentation.setConsentRequired(true); + accountClient.update(clientRepresentation); + + // navigate to account console and login + providerRealmOAuth.openLoginForm(); + loginPage.fillLogin(userFromProviderRealm.getUsername(), userFromProviderRealm.getPassword()); + loginPage.submit(); + + consentPage.assertCurrent(); + consentPage.cancel(); + + // check an error page after cancelling the consent + assertTrue(driver.getPageSource().contains("Happy days")); + assertTrue(driver.getCurrentUrl().contains("error=access_denied")); + + providerRealmOAuth.openLoginForm(); + loginPage.fillLogin(userFromProviderRealm.getUsername(), userFromProviderRealm.getPassword()); + loginPage.submit(); + consentPage.confirm(); + + // successful login + assertFalse(driver.getCurrentUrl().contains("error")); + assertTrue(driver.getPageSource().contains("Happy days"), "Test user should be successfully logged in."); + } + + @Test + public void clientConsentRequiredAfterLogin() { + AuthorizationEndpointResponse response = userRealmOAuth.doLogin(userFromUserRealm.getUsername(), userFromUserRealm.getPassword()); + AccessTokenResponse accessTokenResponse = userRealmOAuth.doAccessTokenRequest(response.getCode()); + + Assertions.assertNotNull(userRealmOAuth.parseLoginResponse().getCode()); + assertTrue(driver.getPageSource().contains("Happy days"), "Test user should be successfully logged in."); + + EventRepresentation loginEvent = userRealmEvents.poll(); + Assertions.assertNotNull(loginEvent); + Assertions.assertEquals(userFromUserRealm.getId(), loginEvent.getUserId()); + Assertions.assertEquals(EventType.LOGIN.toString(), loginEvent.getType()); + loginEvent.getDetails().forEach((key, value) -> { + switch (key) { + case Details.CODE_ID -> + Assertions.assertTrue(isUUID(value)); + case Details.USERNAME -> Assertions.assertEquals(userFromUserRealm.getUsername(), value); + case Details.CONSENT -> Assertions.assertEquals(Details.CONSENT_VALUE_NO_CONSENT_REQUIRED, value); + case Details.REDIRECT_URI -> Assertions.assertEquals("http://127.0.0.1:8500/callback/oauth", value); + } + }); + + userRealm.updateWithCleanup(r -> r.enabledEventTypes("REFRESH_TOKEN_ERROR")); + String sessionId = loginEvent.getSessionId(); + + ClientRepresentation clientRepresentation = userRealm.admin().clients().findByClientId("test-app").get(0); + try { + clientRepresentation.setConsentRequired(true); + userRealm.admin().clients().get(clientRepresentation.getId()).update(clientRepresentation); + + userRealmEvents.clear(); + + // try to refresh the token + // this fails as client no longer has requested consent from user + AccessTokenResponse refreshTokenResponse = userRealmOAuth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken()); + Assertions.assertEquals(OAuthErrorException.INVALID_SCOPE, refreshTokenResponse.getError()); + Assertions.assertEquals("Client no longer has requested consent from user", refreshTokenResponse.getErrorDescription()); + + EventRepresentation refreshEvent = userRealmEvents.poll(); + Assertions.assertNotNull(refreshEvent); + Assertions.assertNull(refreshEvent.getUserId()); + Assertions.assertEquals(EventType.REFRESH_TOKEN_ERROR.toString(), refreshEvent.getType()); + Assertions.assertNull(refreshTokenResponse.getRefreshToken()); + Assertions.assertEquals(sessionId, refreshEvent.getSessionId()); + Assertions.assertEquals(Errors.INVALID_TOKEN, refreshEvent.getError()); + } finally { + clientRepresentation.setConsentRequired(false); + userRealm.admin().clients().get(clientRepresentation.getId()).update(clientRepresentation); + } + + AccountHelper.logout(userRealm.admin(), userFromUserRealm.getUsername()); + } + + @Test + public void testConsentWithAdditionalClientAttributes() { + // setup account client to require consent + ClientResource accountClient = findClientByClientId(providerRealm.admin(), "test-app"); + + ClientRepresentation clientRepresentation = accountClient.toRepresentation(); + clientRepresentation.setConsentRequired(true); + clientRepresentation.getAttributes().put(ClientModel.LOGO_URI,"https://www.keycloak.org/resources/images/keycloak_logo_480x108.png"); + clientRepresentation.getAttributes().put(ClientModel.POLICY_URI,"https://www.keycloak.org/policy"); + clientRepresentation.getAttributes().put(ClientModel.TOS_URI,"https://www.keycloak.org/tos"); + accountClient.update(clientRepresentation); + + // navigate to account console and login + providerRealmOAuth.openLoginForm(); + loginPage.fillLogin(userFromProviderRealm.getUsername(), userFromProviderRealm.getPassword()); + loginPage.submit(); + + consentPage.assertCurrent(); + + assertTrue(driver.findElement(By.xpath("//img[@src='https://www.keycloak.org/resources/images/keycloak_logo_480x108.png']")).isDisplayed(), "logoUri must be presented"); + assertTrue(driver.findElement(By.xpath("//a[@href='https://www.keycloak.org/policy']")).isDisplayed(), "policyUri must be presented"); + assertTrue(driver.findElement(By.xpath("//a[@href='https://www.keycloak.org/tos']")).isDisplayed(), "tosUri must be presented"); + + consentPage.confirm(); + + // successful login + assertTrue(driver.getPageSource().contains("Happy days"), "Test user should be successfully logged in."); + AccountHelper.logout(providerRealm.admin(), userFromProviderRealm.getUsername()); + } + + private static IdentityProviderRepresentation setUpIdentityProvider() { + IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID); + + Map config = idp.getConfig(); + + config.put("clientId", CLIENT_ID); + config.put("clientSecret", CLIENT_SECRET); + config.put("prompt", "login"); + config.put("authorizationUrl", "http://localhost:8080/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/auth"); + config.put("tokenUrl", "http://localhost:8080/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/token"); + config.put("logoutUrl", "http://localhost:8080/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/logout"); + config.put("userInfoUrl", "http://localhost:8080/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/userinfo"); + config.put("defaultScope", "email profile"); + config.put("backchannelSupported", "true"); + + return idp; + } + + private static IdentityProviderRepresentation createIdentityProvider(String alias, String providerId) { + IdentityProviderRepresentation identityProviderRepresentation = new IdentityProviderRepresentation(); + + identityProviderRepresentation.setAlias(alias); + identityProviderRepresentation.setDisplayName(providerId); + identityProviderRepresentation.setProviderId(providerId); + identityProviderRepresentation.setEnabled(true); + + return identityProviderRepresentation; + } + + private boolean isUUID(String uuid) { + return 36 == uuid.length() + && uuid.charAt(8) == '-' + && uuid.charAt(13) == '-' + && uuid.charAt(18) == '-' + && uuid.charAt(23) == '-'; + } + + private static class UserRealmUserConf implements UserConfig { + + @Override + public UserConfigBuilder configure(UserConfigBuilder builder) { + builder.username("user"); + builder.password("password"); + builder.email("user@local"); + builder.emailVerified(); + builder.name("Local", "User"); + + return builder; + } + } + + private static class ProviderRealmUserConf implements UserConfig { + + @Override + public UserConfigBuilder configure(UserConfigBuilder builder) { + builder.username("provider"); + builder.password("password"); + builder.email("provider@local"); + builder.emailVerified(); + builder.name("Provider", "User"); + + return builder; + } + } + + private static class ProviderRealmClientConf implements ClientConfig { + + @Override + public ClientConfigBuilder configure(ClientConfigBuilder builder) { + builder.clientId(CLIENT_ID); + builder.name(CLIENT_ID); + builder.secret(CLIENT_SECRET); + builder.consentRequired(true); + builder.redirectUris( "http://localhost:8080/realms/" + REALM_CONS_NAME + "/broker/" + IDP_OIDC_ALIAS + "/endpoint/*"); + builder.adminUrl("http://localhost:8080/realms/" + REALM_CONS_NAME + "/broker/" + IDP_OIDC_ALIAS + "/endpoint"); + + return builder; + } + } + + private static class ConsumerRealmConf implements RealmConfig { + + @Override + public RealmConfigBuilder configure(RealmConfigBuilder builder) { + builder.identityProvider(setUpIdentityProvider()); + + return builder; + } + } +} diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/user/UserRoleTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/user/UserRoleTest.java index 2b0f6452bdd..ea5d322fdd7 100644 --- a/tests/base/src/test/java/org/keycloak/tests/admin/user/UserRoleTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/admin/user/UserRoleTest.java @@ -9,7 +9,6 @@ import org.keycloak.events.admin.ResourceType; import org.keycloak.models.Constants; import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.MappingsRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.testframework.annotations.InjectRealm; import org.keycloak.testframework.annotations.KeycloakIntegrationTest; @@ -21,7 +20,6 @@ import org.keycloak.testframework.realm.ManagedRealm; import org.keycloak.testframework.realm.UserConfigBuilder; import org.keycloak.tests.utils.admin.AdminEventPaths; import org.keycloak.tests.utils.admin.ApiUtil; -import org.keycloak.testsuite.events.TestEventsListenerProviderFactory; import org.keycloak.testsuite.util.RoleBuilder; import java.util.Collections; @@ -42,10 +40,6 @@ public class UserRoleTest extends AbstractUserTest { @Test public void roleMappings() { RealmResource realm = managedRealm.admin(); - // Enable events - RealmRepresentation realmRep = addTestEventListener(managedRealm.admin().toRepresentation()); - managedRealm.admin().update(realmRep); - RoleRepresentation realmCompositeRole = RoleBuilder.create().name("realm-composite").singleAttribute("attribute1", "value1").build(); realm.roles().create(RoleBuilder.create().name("realm-role").build()); @@ -239,16 +233,4 @@ public class UserRoleTest extends AbstractUserTest { return null; } - - private RealmRepresentation addTestEventListener(RealmRepresentation rep) { - if (rep.getEventsListeners() == null) { - rep.setEventsListeners(new LinkedList()); - } - - if (!rep.getEventsListeners().contains(TestEventsListenerProviderFactory.PROVIDER_ID)) { - rep.getEventsListeners().add(TestEventsListenerProviderFactory.PROVIDER_ID); - } - - return rep; - } } diff --git a/tests/custom-providers/src/main/java/org/keycloak/testsuite/events/TestEventsListenerProvider.java b/tests/custom-providers/src/main/java/org/keycloak/testsuite/events/TestEventsListenerProvider.java deleted file mode 100644 index c03f5ac7e92..00000000000 --- a/tests/custom-providers/src/main/java/org/keycloak/testsuite/events/TestEventsListenerProvider.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.events; - -import org.keycloak.events.Event; -import org.keycloak.events.EventListenerProvider; -import org.keycloak.events.EventListenerTransaction; -import org.keycloak.events.admin.AdminEvent; -import org.keycloak.models.KeycloakSession; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -/** - * @author Marko Strukelj - */ -public class TestEventsListenerProvider implements EventListenerProvider { - - private static final BlockingQueue events = new LinkedBlockingQueue(); - private static final BlockingQueue adminEvents = new LinkedBlockingQueue<>(); - private final EventListenerTransaction tx = new EventListenerTransaction((event, includeRepre) -> adminEvents.add(event), events::add); - - public TestEventsListenerProvider(KeycloakSession session) { - session.getTransactionManager().enlistAfterCompletion(tx); - } - - @Override - public void onEvent(Event event) { - tx.addEvent(event); - } - - @Override - public void onEvent(AdminEvent event, boolean includeRepresentation) { - tx.addAdminEvent(event, includeRepresentation); - } - - @Override - public void close() { - - } - - public static Event poll() { - return events.poll(); - } - - public static AdminEvent pollAdminEvent() { - return adminEvents.poll(); - } - - public static void clear() { - events.clear(); - } - - public static void clearAdminEvents() { - adminEvents.clear(); - } -} diff --git a/tests/custom-providers/src/main/java/org/keycloak/testsuite/events/TestEventsListenerProviderFactory.java b/tests/custom-providers/src/main/java/org/keycloak/testsuite/events/TestEventsListenerProviderFactory.java deleted file mode 100644 index b22fc1d4497..00000000000 --- a/tests/custom-providers/src/main/java/org/keycloak/testsuite/events/TestEventsListenerProviderFactory.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.events; - -import org.keycloak.Config; -import org.keycloak.events.EventListenerProvider; -import org.keycloak.events.EventListenerProviderFactory; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; - -/** - * @author Marko Strukelj - */ -public class TestEventsListenerProviderFactory implements EventListenerProviderFactory { - - public static final String PROVIDER_ID = "event-queue"; - - @Override - public EventListenerProvider create(KeycloakSession session) { - return new TestEventsListenerProvider(session); - } - - @Override - public void init(Config.Scope config) { - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public void close() { - } - - @Override - public String getId() { - return PROVIDER_ID; - } -} diff --git a/tests/custom-providers/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory b/tests/custom-providers/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory deleted file mode 100644 index 602d96fab63..00000000000 --- a/tests/custom-providers/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory +++ /dev/null @@ -1,35 +0,0 @@ -# -# 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. -# - -# -# 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. -# - -org.keycloak.testsuite.events.TestEventsListenerProviderFactory diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ConsentsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ConsentsTest.java deleted file mode 100644 index 2b221dc79d2..00000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ConsentsTest.java +++ /dev/null @@ -1,505 +0,0 @@ -/* - * 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.admin; - -import org.jboss.arquillian.graphene.page.Page; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -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.RealmResource; -import org.keycloak.admin.client.resource.UserResource; -import org.keycloak.admin.client.resource.UsersResource; -import org.keycloak.events.Details; -import org.keycloak.events.Errors; -import org.keycloak.models.ClientModel; -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.ClientScopeRepresentation; -import org.keycloak.representations.idm.EventRepresentation; -import org.keycloak.representations.idm.IdentityProviderRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.representations.idm.UserSessionRepresentation; -import org.keycloak.testsuite.AbstractKeycloakTest; -import org.keycloak.testsuite.Assert; -import org.keycloak.testsuite.AssertEvents; -import org.keycloak.testsuite.pages.AppPage; -import org.keycloak.testsuite.pages.ConsentPage; -import org.keycloak.testsuite.pages.ErrorPage; -import org.keycloak.testsuite.pages.LoginPage; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.keycloak.testsuite.AbstractTestRealmKeycloakTest.TEST_REALM_NAME; -import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; -import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient; -import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId; -import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword; - -import org.keycloak.testsuite.util.ClientBuilder; -import org.keycloak.testsuite.util.oauth.AccessTokenResponse; -import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse; -import org.openqa.selenium.By; - -/** - * @author Marko Strukelj - */ -public class ConsentsTest extends AbstractKeycloakTest { - - final static String REALM_PROV_NAME = "provider"; - final static String REALM_CONS_NAME = "consumer"; - - final static String IDP_OIDC_ALIAS = "kc-oidc-idp"; - final static String IDP_OIDC_PROVIDER_ID = "keycloak-oidc"; - - final static String CLIENT_ID = "brokerapp"; - final static String CLIENT_SECRET = "secret"; - - final static String USER_LOGIN = "testuser"; - final static String USER_EMAIL = "user@localhost.com"; - final static String USER_PASSWORD = "password"; - final static String USER_FIRSTNAME = "User"; - final static String USER_LASTNAME = "Tester"; - - protected RealmRepresentation createProviderRealm() { - RealmRepresentation realm = new RealmRepresentation(); - realm.setRealm(REALM_PROV_NAME); - realm.setEnabled(true); - - return realm; - } - - protected RealmRepresentation createConsumerRealm() { - RealmRepresentation realm = new RealmRepresentation(); - realm.setRealm(REALM_CONS_NAME); - realm.setEnabled(true); - - return realm; - } - - protected List createProviderClients() { - ClientRepresentation client = new ClientRepresentation(); - client.setClientId(CLIENT_ID); - client.setName(CLIENT_ID); - client.setSecret(CLIENT_SECRET); - client.setEnabled(true); - client.setConsentRequired(true); - - client.setRedirectUris(Collections.singletonList(getAuthRoot() + - "/auth/realms/" + REALM_CONS_NAME + "/broker/" + IDP_OIDC_ALIAS + "/endpoint/*")); - - client.setAdminUrl(getAuthRoot() + - "/auth/realms/" + REALM_CONS_NAME + "/broker/" + IDP_OIDC_ALIAS + "/endpoint"); - - return Collections.singletonList(client); - } - - protected IdentityProviderRepresentation setUpIdentityProvider() { - IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID); - - Map config = idp.getConfig(); - - config.put("clientId", CLIENT_ID); - config.put("clientSecret", CLIENT_SECRET); - config.put("prompt", "login"); - config.put("authorizationUrl", getAuthRoot() + "/auth/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/auth"); - config.put("tokenUrl", getAuthRoot() + "/auth/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/token"); - config.put("logoutUrl", getAuthRoot() + "/auth/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/logout"); - config.put("userInfoUrl", getAuthRoot() + "/auth/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/userinfo"); - config.put("defaultScope", "email profile"); - config.put("backchannelSupported", "true"); - - return idp; - } - - protected String getUserLogin() { - return USER_LOGIN; - } - - protected String getUserPassword() { - return USER_PASSWORD; - } - - protected String getUserEmail() { - return USER_EMAIL; - } - - protected String getUserFirstName() { - return USER_FIRSTNAME; - } - - protected String getUserLastName() { - return USER_LASTNAME; - } - protected String providerRealmName() { - return REALM_PROV_NAME; - } - - protected String consumerRealmName() { - return REALM_CONS_NAME; - } - - protected String getIDPAlias() { - return IDP_OIDC_ALIAS; - } - - @Rule - public AssertEvents events = new AssertEvents(this); - - @Page - protected LoginPage accountLoginPage; - - @Page - protected ConsentPage consentPage; - - @Page - protected AppPage appPage; - - @Page - protected ErrorPage errorPage; - - @Override - public void addTestRealms(List testRealms) { - RealmRepresentation providerRealm = createProviderRealm(); - RealmRepresentation consumerRealm = createConsumerRealm(); - RealmRepresentation realmRepresentation = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); - - testRealms.add(providerRealm); - testRealms.add(consumerRealm); - testRealms.add(realmRepresentation); - } - - @Before - public void createUser() { - log.debug("creating user for realm " + providerRealmName()); - - UserRepresentation user = new UserRepresentation(); - user.setUsername(getUserLogin()); - user.setEmail(getUserEmail()); - user.setFirstName(getUserFirstName()); - user.setLastName(getUserLastName()); - user.setEmailVerified(true); - user.setEnabled(true); - - RealmResource realmResource = adminClient.realm(providerRealmName()); - String userId = createUserWithAdminClient(realmResource, user); - - resetUserPassword(realmResource.users().get(userId), getUserPassword(), false); - } - - @Before - public void addIdentityProviderToProviderRealm() { - log.debug("adding identity provider to realm " + consumerRealmName()); - - RealmResource realm = adminClient.realm(consumerRealmName()); - realm.identityProviders().create(setUpIdentityProvider()); - } - - @Before - public void addClients() { - List clients = createProviderClients(); - if (clients != null) { - RealmResource providerRealm = adminClient.realm(providerRealmName()); - for (ClientRepresentation client : clients) { - log.debug("adding client " + client.getName() + " to realm " + providerRealmName()); - - providerRealm.clients().create(client); - } - } - createAppClientInRealm(consumerRealmName()); - } - - protected String getAuthRoot() { - return suiteContext.getAuthServerInfo().getContextRoot().toString(); - } - - protected IdentityProviderRepresentation createIdentityProvider(String alias, String providerId) { - IdentityProviderRepresentation identityProviderRepresentation = new IdentityProviderRepresentation(); - - identityProviderRepresentation.setAlias(alias); - identityProviderRepresentation.setDisplayName(providerId); - identityProviderRepresentation.setProviderId(providerId); - identityProviderRepresentation.setEnabled(true); - - return identityProviderRepresentation; - } - - private void waitForPage(String title) { - long startAt = System.currentTimeMillis(); - - while (!driver.getTitle().toLowerCase().contains(title) - && System.currentTimeMillis() - startAt < 200) { - try { - Thread.sleep(5); - } catch (InterruptedException ignore) {} - } - } - - @After - public void cleanUser() { - String userId = adminClient.realm(providerRealmName()).users().search(getUserLogin()).get(0).getId(); - adminClient.realm(providerRealmName()).users().delete(userId); - } - - @Test - public void testConsents() { - oauth.realm(consumerRealmName()); - oauth.redirectUri(oauth.SERVER_ROOT + "/auth/realms/" + consumerRealmName() + "/app/auth"); - oauth.openLoginForm(); - - log.debug("Clicking social " + getIDPAlias()); - accountLoginPage.clickSocial(getIDPAlias()); - - if (!driver.getCurrentUrl().contains("/auth/realms/" + providerRealmName() + "/")) { - log.debug("Not on provider realm page, url: " + driver.getCurrentUrl()); - } - - Assert.assertTrue("Driver should be on the provider realm page right now", - driver.getCurrentUrl().contains("/auth/realms/" + providerRealmName() + "/")); - - log.debug("Logging in"); - accountLoginPage.login(getUserLogin(), getUserPassword()); - - waitForPage("grant access"); - - Assert.assertTrue(consentPage.isCurrent()); - consentPage.confirm(); - - Assert.assertTrue("We must be on correct realm right now", - driver.getCurrentUrl().contains("/auth/realms/" + consumerRealmName() + "/")); - - UsersResource consumerUsers = adminClient.realm(consumerRealmName()).users(); - Assert.assertTrue("There must be at least one user", consumerUsers.count() > 0); - - List users = consumerUsers.search("", 0, 5); - - UserRepresentation foundUser = null; - for (UserRepresentation user : users) { - if (user.getUsername().equals(getUserLogin()) && user.getEmail().equals(getUserEmail())) { - foundUser = user; - break; - } - } - - Assert.assertNotNull("There must be user " + getUserLogin() + " in realm " + consumerRealmName(), - foundUser); - - // get user with the same username from provider realm - RealmResource providerRealm = adminClient.realm(providerRealmName()); - users = providerRealm.users().search(null, foundUser.getFirstName(), foundUser.getLastName(), null, 0, 1); - Assert.assertEquals("Same user should be in provider realm", 1, users.size()); - - String userId = users.get(0).getId(); - UserResource userResource = providerRealm.users().get(userId); - - // list consents - List> consents = userResource.getConsents(); - Assert.assertEquals("There should be one consent", 1, consents.size()); - - Map consent = consents.get(0); - Assert.assertEquals("Consent should be given to " + CLIENT_ID, CLIENT_ID, consent.get("clientId")); - - // list sessions. Single client should be in user session - List sessions = userResource.getUserSessions(); - Assert.assertEquals("There should be one active session", 1, sessions.size()); - Assert.assertEquals("There should be one client in user session", 1, sessions.get(0).getClients().size()); - - // revoke consent - userResource.revokeConsent(CLIENT_ID); - - // list consents - consents = userResource.getConsents(); - Assert.assertEquals("There should be no consents", 0, consents.size()); - - // list sessions - sessions = userResource.getUserSessions(); - Assert.assertEquals("There should be one active session", 1, sessions.size()); - Assert.assertEquals("There should be no client in user session", 0, sessions.get(0).getClients().size()); - - // oauth clean up - oauth.realm("test"); - oauth.redirectUri(oauth.SERVER_ROOT + "/auth/realms/master/app/auth"); - } - - /** - * KEYCLOAK-18954 - */ - @Test - public void testRetrieveConsentsForUserWithClientsWithGrantedOfflineAccess() throws Exception { - RealmResource providerRealm = adminClient.realm(providerRealmName()); - - RealmRepresentation providerRealmRep = providerRealm.toRepresentation(); - providerRealmRep.setAccountTheme("keycloak"); - providerRealm.update(providerRealmRep); - providerRealm.clients().create(ClientBuilder.create().clientId("test-app").redirectUris("*").addWebOrigin("*").publicClient().build()); - - ClientRepresentation providerAccountRep = providerRealm.clients().findByClientId("test-app").get(0); - - // add offline_scope to default account-console client scope - ClientScopeRepresentation offlineAccessScope = providerRealm.getDefaultOptionalClientScopes().stream() - .filter(csr -> csr.getName().equals(OAuth2Constants.OFFLINE_ACCESS)).findFirst().get(); - providerRealm.clients().get(providerAccountRep.getId()).removeOptionalClientScope(offlineAccessScope.getId()); - providerRealm.clients().get(providerAccountRep.getId()).addDefaultClientScope(offlineAccessScope.getId()); - - // enable consent required to explicitly grant offline access - providerAccountRep.setConsentRequired(true); - providerAccountRep.setDirectAccessGrantsEnabled(true); // for offline token retrieval - providerRealm.clients().get(providerAccountRep.getId()).update(providerAccountRep); - - List searchResult = providerRealm.users().search(getUserLogin()); - UserRepresentation user = searchResult.get(0); - - accountLoginPage.open(providerRealmName()); - - waitForPage("Sign in to provider"); - log.debug("Logging in"); - accountLoginPage.login(getUserLogin(), getUserPassword()); - - waitForPage("grant access"); - log.debug("Grant consent for offline_access"); - Assert.assertTrue(consentPage.isCurrent()); - consentPage.confirm(); - - // disable consent required again to enable direct grant token retrieval. - providerAccountRep.setConsentRequired(false); - providerRealm.clients().get(providerAccountRep.getId()).update(providerAccountRep); - - log.debug("Obtain offline_token"); - AccessTokenResponse response = oauth.realm(providerRealmRep.getRealm()) - .client(providerAccountRep.getClientId()) - .scope(OAuth2Constants.SCOPE_OPENID +" " + OAuth2Constants.SCOPE_PROFILE + " " + OAuth2Constants.OFFLINE_ACCESS) - .doPasswordGrantRequest(getUserLogin(), getUserPassword()); - assertNotNull(response.getRefreshToken()); - - log.debug("Check for Offline Token in consents"); - List> consents = providerRealm.users().get(user.getId()).getConsents(); - assertFalse("Consents should not be empty", consents.isEmpty()); - - assertTrue(consents.toString().contains("Offline Token")); - } - - @Test - public void testConsentCancel() { - // setup account client to require consent - createAppClientInRealm(providerRealmName()); - RealmResource providerRealm = adminClient.realm(providerRealmName()); - ClientResource accountClient = findClientByClientId(providerRealm, "test-app"); - - ClientRepresentation clientRepresentation = accountClient.toRepresentation(); - clientRepresentation.setConsentRequired(true); - accountClient.update(clientRepresentation); - - // setup correct realm - oauth.realm(providerRealmName()); - - // navigate to account console and login - oauth.openLoginForm(); - loginPage.form().login(getUserLogin(), getUserPassword()); - - consentPage.assertCurrent(); - consentPage.cancel(); - - // check an error page after cancelling the consent - assertTrue(driver.getTitle().contains("AUTH_RESPONSE")); - assertTrue(driver.getCurrentUrl().contains("error=access_denied")); - - oauth.openLoginForm(); - loginPage.form().login(getUserLogin(), getUserPassword()); - consentPage.confirm(); - - // successful login - assertFalse(driver.getCurrentUrl().contains("error")); - assertTrue("Test user should be successfully logged in.", driver.getTitle().contains("AUTH_RESPONSE")); - } - - @Test - public void clientConsentRequiredAfterLogin() { - oauth.realm(TEST_REALM_NAME).client("test-app", "password"); - AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password"); - AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(response.getCode()); - - Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); - Assert.assertNotNull(oauth.parseLoginResponse().getCode()); - - EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent(); - String sessionId = loginEvent.getSessionId(); - - ClientRepresentation clientRepresentation = adminClient.realm(TEST_REALM_NAME).clients().findByClientId("test-app").get(0); - try { - clientRepresentation.setConsentRequired(true); - adminClient.realm(TEST_REALM_NAME).clients().get(clientRepresentation.getId()).update(clientRepresentation); - - events.clear(); - - // try to refresh the token - // this fails as client no longer has requested consent from user - AccessTokenResponse refreshTokenResponse = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken()); - Assert.assertEquals(OAuthErrorException.INVALID_SCOPE, refreshTokenResponse.getError()); - Assert.assertEquals("Client no longer has requested consent from user", refreshTokenResponse.getErrorDescription()); - - events.expectRefresh(accessTokenResponse.getRefreshToken(), sessionId).user((String) null).clearDetails().error(Errors.INVALID_TOKEN).assertEvent(); - } finally { - clientRepresentation.setConsentRequired(false); - adminClient.realm(TEST_REALM_NAME).clients().get(clientRepresentation.getId()).update(clientRepresentation); - } - } - - @Test - public void testConsentWithAdditionalClientAttributes() { - // setup account client to require consent - RealmResource providerRealm = adminClient.realm(providerRealmName()); - ClientResource accountClient = findClientByClientId(providerRealm, "test-app"); - - ClientRepresentation clientRepresentation = accountClient.toRepresentation(); - clientRepresentation.setConsentRequired(true); - clientRepresentation.getAttributes().put(ClientModel.LOGO_URI,"https://www.keycloak.org/resources/images/keycloak_logo_480x108.png"); - clientRepresentation.getAttributes().put(ClientModel.POLICY_URI,"https://www.keycloak.org/policy"); - clientRepresentation.getAttributes().put(ClientModel.TOS_URI,"https://www.keycloak.org/tos"); - accountClient.update(clientRepresentation); - - // setup correct realm - oauth.realm(providerRealmName()); - - // navigate to account console and login - oauth.openLoginForm(); - loginPage.form().login(getUserLogin(), getUserPassword()); - - consentPage.assertCurrent(); - - assertTrue("logoUri must be presented", driver.findElement(By.xpath("//img[@src='https://www.keycloak.org/resources/images/keycloak_logo_480x108.png']")).isDisplayed()); - assertTrue("policyUri must be presented", driver.findElement(By.xpath("//a[@href='https://www.keycloak.org/policy']")).isDisplayed()); - assertTrue("tosUri must be presented", driver.findElement(By.xpath("//a[@href='https://www.keycloak.org/tos']")).isDisplayed()); - - consentPage.confirm(); - - // successful login - assertTrue("Test user should be successfully logged in.", driver.getTitle().contains("AUTH_RESPONSE")); - } - - private String getAccountUrl(String realmName) { - return getAuthRoot() + "/auth/realms/" + realmName + "/account"; - } -}