diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java index a89830f44f..7db61baa15 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java @@ -133,16 +133,16 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory { } static String[] getUsers(Policy policy) { - String roles = policy.getConfig().get("users"); + String users = policy.getConfig().get("users"); - if (roles != null) { + if (users != null) { try { - return JsonSerialization.readValue(roles.getBytes(), String[].class); + return JsonSerialization.readValue(users.getBytes(), String[].class); } catch (IOException e) { - throw new RuntimeException("Could not parse roles [" + roles + "] from policy config [" + policy.getName() + ".", e); + throw new RuntimeException("Could not parse users [" + users + "] from policy config [" + policy.getName() + ".", e); } } - return new String[]{}; + return new String[0]; } } diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 3693b9ecc0..1d27f3d963 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -2275,7 +2275,7 @@ public class RepresentationToModel { type = "rules"; } if (authorization.getProvider(type) == null) { - throw new RuntimeException("Unknown polucy type [" + type + "]. Could not find a provider for this type."); + throw new RuntimeException("Unknown policy type [" + type + "]. Could not find a provider for this type."); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java new file mode 100644 index 0000000000..da835d558b --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java @@ -0,0 +1,249 @@ +/* + * 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.authz; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.junit.Before; +import org.junit.Test; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.ClientsResource; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.authorization.client.AuthzClient; +import org.keycloak.authorization.client.Configuration; +import org.keycloak.authorization.client.representation.EntitlementResponse; +import org.keycloak.authorization.client.representation.ResourceRepresentation; +import org.keycloak.authorization.client.representation.ScopeRepresentation; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.JWSInputException; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.authorization.Permission; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.util.AdminClientUtil; +import org.keycloak.testsuite.util.ClientBuilder; +import org.keycloak.testsuite.util.RealmBuilder; +import org.keycloak.testsuite.util.UserBuilder; +import org.keycloak.util.JsonSerialization; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ConflictingScopePermissionTest extends AbstractKeycloakTest { + + @Override + public void addTestRealms(List testRealms) { + testRealms.add(RealmBuilder.create().name("authz-test") + .user(UserBuilder.create().username("marta").password("password")) + .user(UserBuilder.create().username("kolo").password("password")) + .client(ClientBuilder.create().clientId("resource-server-test") + .secret("secret") + .authorizationServicesEnabled(true) + .redirectUris("http://localhost/resource-server-test") + .defaultRoles("uma_protection") + .directAccessGrants()) + .build()); + } + + @Before + public void configureAuthorization() throws Exception { + createResourcesAndScopes(); + + RealmResource realm = getRealm(); + ClientResource client = getClient(realm); + + createPolicies(realm, client); + createPermissions(client); + } + + /** + *

Scope Read on Resource A has two conflicting permissions. One is granting access for Marta and the other for Kolo. + * + *

Scope Read should not be granted for Marta. + */ + @Test + public void testMartaCanAccessResourceAWithExecuteAndWrite() { + List permissions = getEntitlements("marta", "password"); + + for (Permission permission : new ArrayList<>(permissions)) { + String resourceSetName = permission.getResourceSetName(); + + switch (resourceSetName) { + case "Resource A": + assertEquals(2, permission.getScopes().size()); + assertTrue(permission.getScopes().contains("execute")); + assertTrue(permission.getScopes().contains("write")); + permissions.remove(permission); + break; + case "Resource C": + assertEquals(3, permission.getScopes().size()); + assertTrue(permission.getScopes().contains("execute")); + assertTrue(permission.getScopes().contains("write")); + assertTrue(permission.getScopes().contains("read")); + permissions.remove(permission); + break; + default: + fail("Unexpected permission for resource [" + resourceSetName + "]"); + } + } + + assertTrue(permissions.isEmpty()); + } + + private List getEntitlements(String username, String password) { + AuthzClient authzClient = getAuthzClient(); + EntitlementResponse response = authzClient.entitlement(authzClient.obtainAccessToken(username, password).getToken()).getAll("resource-server-test"); + AccessToken accessToken; + + try { + accessToken = new JWSInput(response.getRpt()).readJsonContent(AccessToken.class); + } catch (JWSInputException cause) { + throw new RuntimeException("Failed to deserialize RPT", cause); + } + + AccessToken.Authorization authorization = accessToken.getAuthorization(); + + assertNotNull("RPT does not contain any authorization data", authorization); + + return authorization.getPermissions(); + } + + private RealmResource getRealm() throws Exception { + return AdminClientUtil.createAdminClient().realm("authz-test"); + } + + private ClientResource getClient(RealmResource realm) { + ClientsResource clients = realm.clients(); + return clients.findByClientId("resource-server-test").stream().map(representation -> clients.get(representation.getId())).findFirst().orElseThrow(() -> new RuntimeException("Expected client [resource-server-test]")); + } + + private void createPermissions(ClientResource client) throws IOException { + createResourcePermission("Resource C Only For Marta Permission", "Resource C", Arrays.asList("Only Marta Policy"), client); + createScopePermission("Resource A Scope Read Only For Marta Permission", "Resource A", Arrays.asList("read"), Arrays.asList("Only Marta Policy"), client); + createScopePermission("Resource A Scope Read Only For Kolo Permission", "Resource A", Arrays.asList("read"), Arrays.asList("Only Kolo Policy"), client); + } + + private void createPolicies(RealmResource realm, ClientResource client) throws IOException { + createUserPolicy("Only Marta Policy", realm, client, "marta"); + createUserPolicy("Only Kolo Policy", realm, client, "kolo"); + } + + private void createResourcesAndScopes() throws IOException { + AuthzClient authzClient = getAuthzClient(); + Set scopes = new HashSet<>(); + + scopes.add(new ScopeRepresentation("read")); + scopes.add(new ScopeRepresentation("write")); + scopes.add(new ScopeRepresentation("execute")); + + List resources = new ArrayList<>(); + + resources.add(new ResourceRepresentation("Resource A", scopes)); + resources.add(new ResourceRepresentation("Resource B", scopes)); + resources.add(new ResourceRepresentation("Resource C", scopes)); + + resources.forEach(resource -> authzClient.protection().resource().create(resource)); + } + + private void createUserPolicy(String name, RealmResource realm, ClientResource client, String username) throws IOException { + String userId = realm.users().search(username).stream().map(representation -> representation.getId()).findFirst().orElseThrow(() -> new RuntimeException("Expected user [userId]")); + + PolicyRepresentation representation = new PolicyRepresentation(); + + representation.setName(name); + representation.setType("user"); + + Map config = new HashMap<>(); + + config.put("users", JsonSerialization.writeValueAsString(new String[] {userId})); + + representation.setConfig(config); + + client.authorization().policies().create(representation); + } + + private void createResourcePermission(String name, String resourceName, List policies, ClientResource client) throws IOException { + String resourceId = client.authorization().resources().find(resourceName, null, null, null, null, -1, -1).stream().map(representation -> representation.getId()).findFirst().orElseThrow(() -> new RuntimeException("Expected user [userId]")); + List policyIds = client.authorization().policies().policies().stream().filter(representation -> policies.contains(representation.getName())).map(representation -> representation.getId()).collect(Collectors.toList()); + + PolicyRepresentation representation = new PolicyRepresentation(); + + representation.setName(name); + representation.setType("resource"); + + Map config = new HashMap<>(); + + config.put("resources", JsonSerialization.writeValueAsString(new String[] {resourceId})); + config.put("applyPolicies", JsonSerialization.writeValueAsString(policyIds)); + + representation.setConfig(config); + + client.authorization().policies().create(representation); + } + + private void createScopePermission(String name, String resourceName, List scopes, List policies, ClientResource client) throws IOException { + String resourceId = null; + + if (resourceName != null) { + resourceId = client.authorization().resources().find(resourceName, null, null, null, null, -1, -1).stream().map(representation -> representation.getId()).findFirst().orElseThrow(() -> new RuntimeException("Expected user [userId]")); + } + + List scopeIds = client.authorization().scopes().scopes().stream().filter(representation -> policies.contains(representation.getName())).map(representation -> representation.getId()).collect(Collectors.toList()); + List policyIds = client.authorization().policies().policies().stream().filter(representation -> policies.contains(representation.getName())).map(representation -> representation.getId()).collect(Collectors.toList()); + + PolicyRepresentation representation = new PolicyRepresentation(); + + representation.setName(name); + representation.setType("resource"); + + Map config = new HashMap<>(); + + if (resourceId != null) { + config.put("resources", JsonSerialization.writeValueAsString(new String[]{resourceId})); + } + + config.put("scopes", JsonSerialization.writeValueAsString(scopeIds)); + config.put("applyPolicies", JsonSerialization.writeValueAsString(policyIds)); + + representation.setConfig(config); + + client.authorization().policies().create(representation); + } + + private AuthzClient getAuthzClient() { + try { + return AuthzClient.create(JsonSerialization.readValue(getClass().getResourceAsStream("/authorization-test/default-keycloak.json"), Configuration.class)); + } catch (IOException cause) { + throw new RuntimeException("Failed to create authz client", cause); + } + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java index 753027592a..df49edb308 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java @@ -159,4 +159,9 @@ public class ClientBuilder { rep.setRootUrl(rootUrl); return this; } + + public ClientBuilder authorizationServicesEnabled(boolean enable) { + rep.setAuthorizationServicesEnabled(true); + return this; + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/default-keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/default-keycloak.json new file mode 100644 index 0000000000..7308c7191b --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/default-keycloak.json @@ -0,0 +1,8 @@ +{ + "realm": "authz-test", + "auth-server-url" : "http://localhost:8180/auth", + "resource" : "resource-server-test", + "credentials": { + "secret": "secret" + } +} \ No newline at end of file