keycloak-memoizeit
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java 11(+10 -1)
core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java 11(+11 -0)
Details
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
index 290582f..943f517 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
@@ -17,6 +17,7 @@
*/
package org.keycloak.adapters.authorization;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -309,7 +310,15 @@ public abstract class AbstractPolicyEnforcer {
MethodConfig methodConfig = new MethodConfig();
methodConfig.setMethod(request.getMethod());
- methodConfig.setScopes(pathConfig.getScopes());
+ List scopes = new ArrayList<>();
+
+ if (Boolean.TRUE.equals(getEnforcerConfig().getHttpMethodAsScope())) {
+ scopes.add(request.getMethod());
+ } else {
+ scopes.addAll(pathConfig.getScopes());
+ }
+
+ methodConfig.setScopes(scopes);
methodConfig.setScopesEnforcementMode(PolicyEnforcerConfig.ScopeEnforcementMode.ANY);
return methodConfig;
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
index b6872ec..3a880bd 100644
--- a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
@@ -61,6 +61,9 @@ public class PolicyEnforcerConfig {
@JsonInclude(JsonInclude.Include.NON_NULL)
private Map<String, Map<String, Object>> claimInformationPointConfig;
+ @JsonProperty("http-method-as-scope")
+ private Boolean httpMethodAsScope;
+
public List<PathConfig> getPaths() {
return this.paths;
}
@@ -117,6 +120,14 @@ public class PolicyEnforcerConfig {
this.claimInformationPointConfig = config;
}
+ public Boolean getHttpMethodAsScope() {
+ return httpMethodAsScope;
+ }
+
+ public void setHttpMethodAsScope(Boolean httpMethodAsScope) {
+ this.httpMethodAsScope = httpMethodAsScope;
+ }
+
public static class PathConfig {
public static Set<PathConfig> createPathConfigs(ResourceRepresentation resourceDescription) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java
index 7896be9..4f792f1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/PolicyEnforcerTest.java
@@ -19,6 +19,7 @@ package org.keycloak.testsuite.admin.client.authorization;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import javax.security.cert.X509Certificate;
import javax.ws.rs.HttpMethod;
@@ -58,6 +59,7 @@ import org.keycloak.adapters.spi.HttpFacade.Response;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.admin.client.resource.PermissionsResource;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.jose.jws.JWSInput;
@@ -65,9 +67,12 @@ import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.authorization.AuthorizationRequest;
+import org.keycloak.representations.idm.authorization.AuthorizationResponse;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.ProfileAssume;
@@ -84,7 +89,8 @@ import org.keycloak.util.JsonSerialization;
*/
public class PolicyEnforcerTest extends AbstractKeycloakTest {
- protected static final String REALM_NAME = "authz-test";
+ private static final String RESOURCE_SERVER_CLIENT_ID = "resource-server-test";
+ private static final String REALM_NAME = "authz-test";
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
@@ -118,7 +124,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
@Before
public void onBefore() {
- initAuthorizationSettings(getClientResource("resource-server-test"));
+ initAuthorizationSettings(getClientResource(RESOURCE_SERVER_CLIENT_ID));
}
@Test
@@ -259,11 +265,139 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
assertEquals(HttpHeaders.WWW_AUTHENTICATE, headers.get(CorsHeaders.ACCESS_CONTROL_EXPOSE_HEADERS).get(0));
}
+ @Test
+ public void testMatchHttpVerbsToScopes() {
+ ClientResource clientResource = getClientResource(RESOURCE_SERVER_CLIENT_ID);
+ ResourceRepresentation resource = createResource(clientResource, "Resource With HTTP Scopes", "/api/resource-with-scope");
+
+ ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
+
+ permission.setName(resource.getName() + " Permission");
+ permission.addResource(resource.getName());
+ permission.addPolicy("Always Grant Policy");
+
+ PermissionsResource permissions = clientResource.authorization().permissions();
+ permissions.resource().create(permission).close();
+
+ KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-match-http-verbs-scopes.json"));
+ PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer();
+ oauth.realm(REALM_NAME);
+ oauth.clientId("public-client-test");
+ oauth.doLogin("marta", "password");
+
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
+ String token = response.getAccessToken();
+
+ OIDCHttpFacade httpFacade = createHttpFacade("/api/resource-with-scope", token);
+
+ try {
+ policyEnforcer.enforce(httpFacade);
+ fail("Should fail because resource does not have any scope named GET");
+ } catch (Exception ignore) {
+ assertTrue(ignore.getCause().getMessage().contains("One of the given scopes [GET] is invalid"));
+ }
+
+ resource.addScope("GET", "POST");
+
+ clientResource.authorization().resources().resource(resource.getId()).update(resource);
+
+ deployment = KeycloakDeploymentBuilder.build(getAdapterConfiguration("enforcer-match-http-verbs-scopes.json"));
+ policyEnforcer = deployment.getPolicyEnforcer();
+
+ AuthorizationContext context = policyEnforcer.enforce(httpFacade);
+ assertTrue(context.isGranted());
+
+ httpFacade = createHttpFacade("/api/resource-with-scope", token, "POST");
+ context = policyEnforcer.enforce(httpFacade);
+ assertTrue(context.isGranted());
+
+ ScopePermissionRepresentation postPermission = new ScopePermissionRepresentation();
+
+ postPermission.setName("GET permission");
+ postPermission.addScope("GET");
+ postPermission.addPolicy("Always Deny Policy");
+
+ permissions.scope().create(postPermission).close();
+
+ httpFacade = createHttpFacade("/api/resource-with-scope", token);
+ context = policyEnforcer.enforce(httpFacade);
+ assertFalse(context.isGranted());
+
+ postPermission = permissions.scope().findByName(postPermission.getName());
+
+ postPermission.addScope("GET");
+ postPermission.addPolicy("Always Grant Policy");
+
+ permissions.scope().findById(postPermission.getId()).update(postPermission);
+
+ AuthzClient authzClient = getAuthzClient("default-keycloak.json");
+ AuthorizationResponse authorize = authzClient.authorization(token).authorize();
+ token = authorize.getToken();
+
+ httpFacade = createHttpFacade("/api/resource-with-scope", token);
+ context = policyEnforcer.enforce(httpFacade);
+ assertTrue(context.isGranted());
+
+ httpFacade = createHttpFacade("/api/resource-with-scope", token, "POST");
+ context = policyEnforcer.enforce(httpFacade);
+ assertTrue(context.isGranted());
+
+ postPermission = permissions.scope().findByName(postPermission.getName());
+ postPermission.addScope("GET");
+ postPermission.addPolicy("Always Deny Policy");
+ permissions.scope().findById(postPermission.getId()).update(postPermission);
+ authorize = authzClient.authorization(token).authorize();
+ token = authorize.getToken();
+
+ httpFacade = createHttpFacade("/api/resource-with-scope", token);
+ context = policyEnforcer.enforce(httpFacade);
+ assertFalse(context.isGranted());
+
+ httpFacade = createHttpFacade("/api/resource-with-scope", token, "POST");
+ context = policyEnforcer.enforce(httpFacade);
+ assertTrue(context.isGranted());
+
+ postPermission = permissions.scope().findByName(postPermission.getName());
+ postPermission.addScope("GET");
+ postPermission.addPolicy("Always Grant Policy");
+ permissions.scope().findById(postPermission.getId()).update(postPermission);
+ authorize = authzClient.authorization(token).authorize();
+ token = authorize.getToken();
+
+ httpFacade = createHttpFacade("/api/resource-with-scope", token);
+ context = policyEnforcer.enforce(httpFacade);
+ assertTrue(context.isGranted());
+
+ httpFacade = createHttpFacade("/api/resource-with-scope", token, "POST");
+ context = policyEnforcer.enforce(httpFacade);
+ assertTrue(context.isGranted());
+
+ postPermission = permissions.scope().findByName(postPermission.getName());
+ postPermission.addScope("POST");
+ postPermission.addPolicy("Always Deny Policy");
+ permissions.scope().findById(postPermission.getId()).update(postPermission);
+ AuthorizationRequest request = new AuthorizationRequest();
+
+ request.addPermission(null, "GET");
+
+ authorize = authzClient.authorization(token).authorize(request);
+ token = authorize.getToken();
+
+ httpFacade = createHttpFacade("/api/resource-with-scope", token);
+ context = policyEnforcer.enforce(httpFacade);
+ assertTrue(context.isGranted());
+
+ httpFacade = createHttpFacade("/api/resource-with-scope", token, "POST");
+ context = policyEnforcer.enforce(httpFacade);
+ assertFalse(context.isGranted());
+ }
+
private void initAuthorizationSettings(ClientResource clientResource) {
if (clientResource.authorization().resources().findByName("Resource A").isEmpty()) {
JSPolicyRepresentation policy = new JSPolicyRepresentation();
- policy.setName("Resource A Policy");
+ policy.setName("Always Grant Policy");
StringBuilder code = new StringBuilder();
@@ -287,7 +421,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
if (clientResource.authorization().resources().findByName("Resource B").isEmpty()) {
JSPolicyRepresentation policy = new JSPolicyRepresentation();
- policy.setName("Resource B Policy");
+ policy.setName("Always Deny Policy");
StringBuilder code = new StringBuilder();
@@ -383,6 +517,10 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
return createHttpFacade(path, null, token, new HashMap<>(), new HashMap<>(), null, null);
}
+ private OIDCHttpFacade createHttpFacade(String path, String token, String method) {
+ return createHttpFacade(path, method, token, new HashMap<>(), new HashMap<>(), null, null);
+ }
+
private OIDCHttpFacade createHttpFacade(String path) {
return createHttpFacade(path, null, null, new HashMap<>(), new HashMap<>(), null, null);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-match-http-verbs-scopes.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-match-http-verbs-scopes.json
new file mode 100644
index 0000000..1daafa0
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-match-http-verbs-scopes.json
@@ -0,0 +1,13 @@
+{
+ "realm": "authz-test",
+ "auth-server-url": "http://localhost:8180/auth",
+ "ssl-required": "external",
+ "resource": "resource-server-test",
+ "credentials": {
+ "secret": "secret"
+ },
+ "bearer-only": true,
+ "policy-enforcer": {
+ "http-method-as-scope": true
+ }
+}