diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/clientscopes/ClientScopesEvaluate.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/clientscopes/ClientScopesEvaluate.java new file mode 100644 index 0000000000..a72e410d94 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/clientscopes/ClientScopesEvaluate.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 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.console.page.clients.clientscopes; + +import org.jboss.arquillian.graphene.page.Page; +import org.keycloak.testsuite.console.page.clients.Client; + +/** + * @author Marek Posolda + */ +public class ClientScopesEvaluate extends Client { + + @Page + private ClientScopesEvaluateForm form; + + @Override + public String getUriFragment() { + return super.getUriFragment() + "/client-scopes/evaluate-scopes"; + } + + public ClientScopesEvaluateForm form() { + return form; + } +} diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/clientscopes/ClientScopesEvaluateForm.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/clientscopes/ClientScopesEvaluateForm.java new file mode 100644 index 0000000000..2a426520eb --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/clientscopes/ClientScopesEvaluateForm.java @@ -0,0 +1,157 @@ +/* + * Copyright 2017 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.console.page.clients.clientscopes; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.keycloak.testsuite.console.page.fragment.DataTable; +import org.keycloak.testsuite.page.Form; +import org.keycloak.testsuite.util.WaitUtils; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.ui.Select; + +/** + * @author Marek Posolda + */ +public class ClientScopesEvaluateForm extends Form { + + @FindBy(id = "scopeParam") + private WebElement scopeParamInput; + + @FindBy(id = "available") + protected Select availableClientScopesSelect; + + @FindBy(id = "assigned") + protected Select assignedClientScopesSelect; + + @FindBy(id = "effective") + protected Select effectiveClientScopesSelect; + + @FindBy(css = "button[ng-click*='addAppliedClientScope']") + protected WebElement addAppliedClientScopesButton; + + @FindBy(css = "button[ng-click*='deleteAppliedClientScope']") + protected WebElement deleteAppliedClientScopesButton; + + @FindBy(css = "button[data-ng-click*='sendEvaluationRequest']") + protected WebElement evaluateButton; + + // Bottom part of the page (stuff shown after "Evaluate" button clicked) + @FindBy(css = "li[data-ng-click*='showTab(1)']") + protected WebElement showProtocolMappersLink; + + @FindBy(css = "li[data-ng-click*='showTab(2)']") + protected WebElement showRolesLink; + + @FindBy(css = "li[data-ng-click*='showTab(3)']") + protected WebElement showTokenLink; + + @FindBy(css = "table[data-ng-show*='protocolMappersShown']") + protected DataTable protocolMappersTable; + + @FindBy(id = "available-realm-roles") + protected Select notGrantedRealmRolesSelect; + + @FindBy(id = "realm-composite") + protected Select grantedRealmRolesSelect; + + @FindBy(tagName = "textarea") + private WebElement accessTokenTextArea; + + + public Set getAvailableClientScopes() { + return ClientScopesSetupForm.getSelectValues(availableClientScopesSelect); + } + + public Set getAssignedClientScopes() { + return ClientScopesSetupForm.getSelectValues(assignedClientScopesSelect); + } + + public Set getEffectiveClientScopes() { + return ClientScopesSetupForm.getSelectValues(effectiveClientScopesSelect); + } + + public void setAssignedClientScopes(Collection scopes) { + ClientScopesSetupForm.removeRedundantScopes(assignedClientScopesSelect, deleteAppliedClientScopesButton, scopes); + ClientScopesSetupForm.addMissingScopes(availableClientScopesSelect, addAppliedClientScopesButton, scopes); + } + + + public void selectUser(String username) { + // TODO: Should be probably better way how to work with the "ui-select2" component + driver.findElement(By.id("select2-chosen-1")).click(); + driver.findElement(By.className("select2-input")).sendKeys(username); + driver.findElement(By.className("select2-result-label")).click(); + } + + + public void evaluate() { + evaluateButton.click(); + WaitUtils.waitForPageToLoad(); + } + + + public void showProtocolMappers() { + showProtocolMappersLink.click(); + WaitUtils.waitForPageToLoad(); + } + + public void showRoles() { + showRolesLink.click(); + WaitUtils.waitForPageToLoad(); + } + + public void showToken() { + showTokenLink.click(); + WaitUtils.waitForPageToLoad(); + } + + + // Bottom part of the page (stuff shown after "Evaluate" button clicked) + public Set getEffectiveProtocolMapperNames() { + List rows = protocolMappersTable.rows(); + + Set names = rows.stream().map((WebElement row) -> { + + return row.findElement(By.xpath("td[1]")).getText(); + + }).collect(Collectors.toSet()); + + return names; + } + + + public Set getGrantedRealmRoles() { + return ClientScopesSetupForm.getSelectValues(grantedRealmRolesSelect); + } + + public Set getNotGrantedRealmRoles() { + return ClientScopesSetupForm.getSelectValues(notGrantedRealmRolesSelect); + } + + public String getAccessToken() { + return accessTokenTextArea.getText(); + } + + +} diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/clientscopes/ClientScopesSetup.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/clientscopes/ClientScopesSetup.java new file mode 100644 index 0000000000..6f4ecfea5f --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/clientscopes/ClientScopesSetup.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 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.console.page.clients.clientscopes; + +import org.jboss.arquillian.graphene.page.Page; +import org.keycloak.testsuite.console.page.clients.Client; + +/** + * @author Marek Posolda + */ +public class ClientScopesSetup extends Client { + + @Page + private ClientScopesSetupForm form; + + @Override + public String getUriFragment() { + return super.getUriFragment() + "/client-scopes/setup-scopes"; + } + + public ClientScopesSetupForm form() { + return form; + } +} diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/clientscopes/ClientScopesSetupForm.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/clientscopes/ClientScopesSetupForm.java new file mode 100644 index 0000000000..6ce0a46daf --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/clientscopes/ClientScopesSetupForm.java @@ -0,0 +1,139 @@ +/* + * Copyright 2017 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.console.page.clients.clientscopes; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.keycloak.testsuite.page.Form; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.ui.Select; + +import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement; + +/** + * @author Marek Posolda + */ +public class ClientScopesSetupForm extends Form { + + @FindBy(id = "available") + protected Select availableDefaultClientScopesSelect; + + @FindBy(id = "assigned") + protected Select defaultClientScopesSelect; + + @FindBy(id = "available-opt") + protected Select availableOptionalClientScopesSelect; + + @FindBy(id = "assigned-opt") + protected Select optionalClientScopesSelect; + + + @FindBy(css = "button[ng-click*='addDefaultClientScope']") + protected WebElement addSelectedDefaultClientScopesButton; + + @FindBy(css = "button[ng-click*='addOptionalClientScope']") + protected WebElement addSelectedOptionalClientScopesButton; + + @FindBy(css = "button[ng-click*='deleteDefaultClientScope']") + protected WebElement removeSelectedDefaultClientScopesButton; + + @FindBy(css = "button[ng-click*='deleteOptionalClientScope']") + protected WebElement removeSelectedOptionalClientScopesButton; + + + public Set getAvailableDefaultClientScopes() { + return getSelectValues(availableDefaultClientScopesSelect); + } + + public Set getDefaultClientScopes() { + return getSelectValues(defaultClientScopesSelect); + } + + public Set getAvailableOptionalClientScopes() { + return getSelectValues(availableOptionalClientScopesSelect); + } + + public Set getOptionalClientScopes() { + return getSelectValues(optionalClientScopesSelect); + } + + + public void setDefaultClientScopes(Collection scopes) { + removeRedundantScopes(defaultClientScopesSelect, removeSelectedDefaultClientScopesButton, scopes); + addMissingScopes(availableDefaultClientScopesSelect, addSelectedDefaultClientScopesButton, scopes); + } + + public void setOptionalClientScopes(Collection scopes) { + removeRedundantScopes(optionalClientScopesSelect, removeSelectedOptionalClientScopesButton, scopes); + addMissingScopes(availableOptionalClientScopesSelect, addSelectedOptionalClientScopesButton, scopes); + } + + + // Static helper methods + + static Set getSelectValues(Select select) { + Set roles = new HashSet<>(); + for (WebElement option : select.getOptions()) { + roles.add(option.getText()); + } + return roles; + } + + + static void removeRedundantScopes(Select select, WebElement button, Collection scopes) { + boolean someRemoved = false; + + select.deselectAll(); + for (String scope : getSelectValues(select)) { + if (scopes == null // if scopes not provided, remove all + || !scopes.contains(scope)) { // if scopes provided, remove only the redundant + select.selectByVisibleText(scope); + someRemoved = true; + } + } + + if (someRemoved) { + waitUntilElement(button).is().enabled(); + button.click(); + } + } + + + static void addMissingScopes(Select select, WebElement button, Collection scopes) { + select.deselectAll(); + if (scopes != null) { // if scopes not provided, don't add any + boolean someAdded = false; + + for (String scope : getSelectValues(select)) { + if (scopes.contains(scope)) { // if scopes provided, add only the missing + select.selectByVisibleText(scope); + someAdded = true; + } + } + + if (someAdded) { + waitUntilElement(button).is().enabled(); + button.click(); + } + } + } + +} diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientClientScopesTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientClientScopesTest.java new file mode 100644 index 0000000000..e7cb74fe62 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientClientScopesTest.java @@ -0,0 +1,172 @@ +/* + * Copyright 2017 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.console.clients; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.console.page.clients.clientscopes.ClientScopesEvaluate; +import org.keycloak.testsuite.console.page.clients.clientscopes.ClientScopesEvaluateForm; +import org.keycloak.testsuite.console.page.clients.clientscopes.ClientScopesSetup; +import org.keycloak.testsuite.console.page.clients.clientscopes.ClientScopesSetupForm; +import org.keycloak.util.JsonSerialization; +import org.keycloak.util.TokenUtil; + +import static org.junit.Assert.assertNotNull; +import static org.keycloak.testsuite.auth.page.login.Login.OIDC; + +/** + * Test for the "Client Scopes" tab of client (Binding between "Client" and "Client Scopes") + * + * @author Marek Posolda + */ +public class ClientClientScopesTest extends AbstractClientTest { + + private ClientRepresentation newClient; + private ClientRepresentation found; + + + @Page + private ClientScopesSetup clientScopesSetupPage; + + @Page + private ClientScopesEvaluate clientScopesEvaluatePage; + + + @Before + public void before() { + newClient = createClientRep(TEST_CLIENT_ID, OIDC); + newClient.setFullScopeAllowed(false); + + testRealmResource().clients().create(newClient).close(); + + found = findClientByClientId(TEST_CLIENT_ID); + assertNotNull("Client " + TEST_CLIENT_ID + " was not found.", found); + clientScopesSetupPage.setId(found.getId()); + clientScopesSetupPage.navigateTo(); + } + + + @Test + public void testSetupClientScopes() { + ClientScopesSetupForm setupForm = clientScopesSetupPage.form(); + + // Test the initial state + Assert.assertNames(setupForm.getAvailableDefaultClientScopes()); + Assert.assertNames(setupForm.getDefaultClientScopes(), "email", "profile"); + Assert.assertNames(setupForm.getAvailableOptionalClientScopes()); + Assert.assertNames(setupForm.getOptionalClientScopes(), "address", "phone", "offline_access"); + + // Remove 'profile' as default client scope and assert + setupForm.setDefaultClientScopes(Collections.singletonList("email")); + Assert.assertNames(setupForm.getAvailableDefaultClientScopes(), "profile"); + Assert.assertNames(setupForm.getDefaultClientScopes(), "email"); + Assert.assertNames(setupForm.getAvailableOptionalClientScopes(), "profile"); + Assert.assertNames(setupForm.getOptionalClientScopes(), "address", "phone", "offline_access"); + + // Add 'profile' as optional client scope and assert + setupForm.setOptionalClientScopes(Arrays.asList("profile", "address", "phone", "offline_access")); + Assert.assertNames(setupForm.getAvailableDefaultClientScopes()); + Assert.assertNames(setupForm.getDefaultClientScopes(), "email"); + Assert.assertNames(setupForm.getAvailableOptionalClientScopes()); + Assert.assertNames(setupForm.getOptionalClientScopes(), "profile", "address", "phone", "offline_access"); + + // Retrieve client through adminClient + found = findClientByClientId(TEST_CLIENT_ID); + Assert.assertNames(found.getDefaultClientScopes(), "email", "role_list"); // SAML client scope 'role_list' is included too in the rep + Assert.assertNames(found.getOptionalClientScopes(), "profile", "address", "phone", "offline_access"); + + + // Revert and check things successfully reverted + setupForm.setOptionalClientScopes(Arrays.asList("address", "phone", "offline_access")); + Assert.assertNames(setupForm.getAvailableDefaultClientScopes(), "profile"); + setupForm.setDefaultClientScopes(Arrays.asList("profile", "email")); + + Assert.assertNames(setupForm.getAvailableDefaultClientScopes()); + Assert.assertNames(setupForm.getDefaultClientScopes(), "email", "profile"); + Assert.assertNames(setupForm.getAvailableOptionalClientScopes()); + Assert.assertNames(setupForm.getOptionalClientScopes(), "address", "phone", "offline_access"); + } + + + @Test + public void testEvaluateClientScopes() throws IOException { + clientScopesEvaluatePage.setId(found.getId()); + clientScopesEvaluatePage.navigateTo(); + + ClientScopesEvaluateForm evaluateForm = clientScopesEvaluatePage.form(); + + // Check the defaults + Assert.assertNames(evaluateForm.getAvailableClientScopes(), "address", "phone", "offline_access"); + Assert.assertNames(evaluateForm.getAssignedClientScopes()); + Assert.assertNames(evaluateForm.getEffectiveClientScopes(), "profile", "email"); + + // Add some optional scopes to the evaluation + evaluateForm.setAssignedClientScopes(Arrays.asList("address", "phone")); + Assert.assertNames(evaluateForm.getAvailableClientScopes(), "offline_access"); + Assert.assertNames(evaluateForm.getAssignedClientScopes(), "address", "phone"); + Assert.assertNames(evaluateForm.getEffectiveClientScopes(), "address", "phone", "profile", "email"); + + // Remove optional 'phone' scope from the evaluation + evaluateForm.setAssignedClientScopes(Arrays.asList("address", "offline_access")); + Assert.assertNames(evaluateForm.getAvailableClientScopes(), "phone"); + Assert.assertNames(evaluateForm.getAssignedClientScopes(), "address", "offline_access"); + Assert.assertNames(evaluateForm.getEffectiveClientScopes(), "address", "offline_access", "profile", "email"); + + // Select some user + evaluateForm.selectUser("test"); + + // Submit + evaluateForm.evaluate(); + + // Test protocolMappers of 'address' , 'profile' and 'email' scopes are included + Set protocolMappers = evaluateForm.getEffectiveProtocolMapperNames(); + Assert.assertTrue(protocolMappers.contains("address")); + Assert.assertTrue(protocolMappers.contains("email")); + Assert.assertTrue(protocolMappers.contains("email verified")); + Assert.assertTrue(protocolMappers.contains("username")); + Assert.assertTrue(protocolMappers.contains("full name")); + Assert.assertFalse(protocolMappers.contains("phone")); + + // Test roles + evaluateForm.showRoles(); + Assert.assertNames(evaluateForm.getGrantedRealmRoles(), "offline_access"); + Assert.assertNames(evaluateForm.getNotGrantedRealmRoles(), "uma_authorization"); + + // Test access token + evaluateForm.showToken(); + String accessTokenStr = evaluateForm.getAccessToken(); + + AccessToken token = JsonSerialization.readValue(accessTokenStr, AccessToken.class); + String scopeParam = token.getScope(); + Assert.assertTrue(TokenUtil.isOIDCRequest(scopeParam)); + Assert.assertTrue(TokenUtil.hasScope(scopeParam, "address")); + Assert.assertTrue(TokenUtil.hasScope(scopeParam, "profile")); + Assert.assertTrue(TokenUtil.hasScope(scopeParam, "email")); + Assert.assertFalse(TokenUtil.hasScope(scopeParam, "phone")); + } +} diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientScopeTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientScopeTest.java index 1c9d03eddf..51e0ccf097 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientScopeTest.java +++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientScopeTest.java @@ -38,6 +38,7 @@ import static org.junit.Assert.*; import static org.keycloak.testsuite.auth.page.login.Login.OIDC; /** + * Test for the "Scope" tab of client (Client role mappings) * * @author Vlastislav Ramik */