[KEYCLOAK-8449] - Option to automatically map HTTP verbs to scopes when configuring the policy enforcer

This commit is contained in:
Pedro Igor committed 2018-10-23 08:40:54 -03:00
1 parent c536955b70
commit 6f8f8e6a28
4 files changed
+176 -5

No files matched your search

@@ -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;
@@ -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) {
@@ -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);
}
@@ -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
}
}