diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java b/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java index 668207580f..90cba85cc9 100644 --- a/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java @@ -151,7 +151,7 @@ public class AuthorizationRequest { ResourcePermission permission = null; for (ResourcePermission resourcePermission : permissions.getResources()) { - if (resourcePermission.getResourceId().equals(resourceId)) { + if (resourcePermission.getResourceId() != null && resourcePermission.getResourceId().equals(resourceId)) { permission = resourcePermission; break; } diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java index 751197b428..97e6727b7e 100644 --- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java +++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java @@ -29,6 +29,7 @@ import java.util.Objects; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -354,17 +355,21 @@ public class AuthorizationTokenService { } } - if (existingResources.isEmpty() && (requestedScopes == null || requestedScopes.isEmpty())) { - throw new CorsErrorResponseException(cors, "invalid_resource", "Resource with id [" + requestedResource.getResourceId() + "] does not exist.", Status.FORBIDDEN); - } - String clientAdditionalScopes = request.getScope(); if (clientAdditionalScopes != null) { requestedScopes.addAll(Arrays.asList(clientAdditionalScopes.split(" "))); } - List requestedScopesModel = requestedScopes.stream().map(s -> scopeStore.findByName(s, resourceServer.getId())).collect(Collectors.toList()); + List requestedScopesModel = requestedScopes.stream().map(s -> scopeStore.findByName(s, resourceServer.getId())).filter(Objects::nonNull).collect(Collectors.toList()); + + if (requestedResource.getResourceId() != null && !"".equals(requestedResource.getResourceId().trim()) && existingResources.isEmpty()) { + throw new CorsErrorResponseException(cors, "invalid_resource", "Resource with id [" + requestedResource.getResourceId() + "] does not exist.", Status.BAD_REQUEST); + } + + if ((requestedResource.getScopes() != null && !requestedResource.getScopes().isEmpty()) && requestedScopesModel.isEmpty()) { + throw new CorsErrorResponseException(cors, "invalid_scope", "One of the given scopes " + requestedResource.getScopes() + " are invalid", Status.BAD_REQUEST); + } if (!existingResources.isEmpty()) { for (Resource resource : existingResources) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java index 9325550d1a..562555aea8 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java @@ -21,12 +21,15 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; 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.Set; import java.util.function.Supplier; import org.junit.Before; @@ -439,7 +442,7 @@ public class EntitlementAPITest extends AbstractAuthzTest { authorization.resources().resource(resource.getId()).update(resource); - // the addition of a new scope invalidates the permission previously grante to the resource + // the addition of a new scope invalidates the permission previously granted to the resource assertFalse(hasPermission("kolo", "password", resource.getId())); accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken(); @@ -471,6 +474,7 @@ public class EntitlementAPITest extends AbstractAuthzTest { authorization.resources().resource(resource.getId()).update(resource); assertTrue(hasPermission("kolo", "password", resource.getId())); + assertTrue(hasPermission("kolo", "password", resource.getId(), "Scope A")); assertFalse(hasPermission("kolo", "password", resource.getId(), "Scope B")); resource.setScopes(new HashSet<>()); @@ -479,6 +483,7 @@ public class EntitlementAPITest extends AbstractAuthzTest { assertTrue(hasPermission("kolo", "password", resource.getId())); assertFalse(hasPermission("kolo", "password", resource.getId(), "Scope A")); + assertFalse(hasPermission("kolo", "password", resource.getId(), "Scope B")); } @Test @@ -514,9 +519,146 @@ public class EntitlementAPITest extends AbstractAuthzTest { request.addPermission("Sensortest", "sensors:view"); - AuthorizationResponse response = authzClient.authorization(accessToken).authorize(request); + try { + authzClient.authorization(accessToken).authorize(request); + fail("resource is invalid"); + } catch (RuntimeException expected) { + assertEquals(400, HttpResponseException.class.cast(expected.getCause()).getStatusCode()); + assertTrue(HttpResponseException.class.cast(expected.getCause()).toString().contains("invalid_resource")); + } + } - assertNotNull(response); + @Test + public void testObtainAllEntitlementsInvalidScope() throws Exception { + ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST); + AuthorizationResource authorization = client.authorization(); + + JSPolicyRepresentation policy = new JSPolicyRepresentation(); + + policy.setName(KeycloakModelUtils.generateId()); + policy.setCode("$evaluation.grant();"); + + authorization.policies().js().create(policy).close(); + + ResourceRepresentation resource = new ResourceRepresentation(); + + resource.setName(KeycloakModelUtils.generateId()); + resource.addScope("sensors:view", "sensors:update", "sensors:delete"); + + resource = authorization.resources().create(resource).readEntity(ResourceRepresentation.class); + + ScopePermissionRepresentation permission = new ScopePermissionRepresentation(); + + permission.setName(KeycloakModelUtils.generateId()); + permission.addScope("sensors:view"); + permission.addPolicy(policy.getName()); + + authorization.permissions().scope().create(permission); + + String accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken(); + AuthzClient authzClient = getAuthzClient(AUTHZ_CLIENT_CONFIG); + AuthorizationRequest request = new AuthorizationRequest(); + + request.addPermission(resource.getId(), "sensors:view_invalid"); + + try { + authzClient.authorization(accessToken).authorize(request); + fail("scope is invalid"); + } catch (RuntimeException expected) { + assertEquals(400, HttpResponseException.class.cast(expected.getCause()).getStatusCode()); + assertTrue(HttpResponseException.class.cast(expected.getCause()).toString().contains("invalid_scope")); + } + + request = new AuthorizationRequest(); + + request.addPermission(null, "sensors:view_invalid"); + + try { + authzClient.authorization(accessToken).authorize(request); + fail("scope is invalid"); + } catch (RuntimeException expected) { + assertEquals(400, HttpResponseException.class.cast(expected.getCause()).getStatusCode()); + assertTrue(HttpResponseException.class.cast(expected.getCause()).toString().contains("invalid_scope")); + } + } + + @Test + public void testObtainAllEntitlementsForScope() throws Exception { + ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST); + AuthorizationResource authorization = client.authorization(); + + JSPolicyRepresentation policy = new JSPolicyRepresentation(); + + policy.setName(KeycloakModelUtils.generateId()); + policy.setCode("$evaluation.grant();"); + + authorization.policies().js().create(policy).close(); + + Set resourceIds = new HashSet<>(); + ResourceRepresentation resource = new ResourceRepresentation(); + + resource.setName(KeycloakModelUtils.generateId()); + resource.addScope("sensors:view", "sensors:update", "sensors:delete"); + + resourceIds.add(authorization.resources().create(resource).readEntity(ResourceRepresentation.class).getId()); + + resource = new ResourceRepresentation(); + + resource.setName(KeycloakModelUtils.generateId()); + resource.addScope("sensors:view", "sensors:update", "sensors:delete"); + + resourceIds.add(authorization.resources().create(resource).readEntity(ResourceRepresentation.class).getId()); + + ScopePermissionRepresentation permission = new ScopePermissionRepresentation(); + + permission.setName(KeycloakModelUtils.generateId()); + permission.addScope("sensors:view", "sensors:update"); + permission.addPolicy(policy.getName()); + + authorization.permissions().scope().create(permission); + + String accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken(); + AuthzClient authzClient = getAuthzClient(AUTHZ_CLIENT_CONFIG); + AuthorizationRequest request = new AuthorizationRequest(); + + request.addPermission(null, "sensors:view"); + + AuthorizationResponse response = authzClient.authorization(accessToken).authorize(request); + assertNotNull(response.getToken()); + List permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions(); + assertEquals(2, permissions.size()); + + for (Permission grantedPermission : permissions) { + assertTrue(resourceIds.containsAll(Arrays.asList(grantedPermission.getResourceId()))); + assertEquals(1, grantedPermission.getScopes().size()); + assertTrue(grantedPermission.getScopes().containsAll(Arrays.asList("sensors:view"))); + } + + request.addPermission(null, "sensors:view", "sensors:update"); + + response = authzClient.authorization(accessToken).authorize(request); + assertNotNull(response.getToken()); + permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions(); + assertEquals(2, permissions.size()); + + for (Permission grantedPermission : permissions) { + assertTrue(resourceIds.containsAll(Arrays.asList(grantedPermission.getResourceId()))); + assertEquals(2, grantedPermission.getScopes().size()); + assertTrue(grantedPermission.getScopes().containsAll(Arrays.asList("sensors:view", "sensors:update"))); + } + + request.addPermission(null, "sensors:view", "sensors:update", "sensors:delete"); + + response = authzClient.authorization(accessToken).authorize(request); + assertNotNull(response.getToken()); + permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions(); + assertEquals(2, permissions.size()); + + for (Permission grantedPermission : permissions) { + assertTrue(resourceIds.containsAll(Arrays.asList(grantedPermission.getResourceId()))); + assertEquals(2, grantedPermission.getScopes().size()); + assertTrue(grantedPermission.getScopes().containsAll(Arrays.asList("sensors:view", "sensors:update"))); + } } private void testRptRequestWithResourceName(String configFile) {