diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/regex/RegexPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/regex/RegexPolicyProvider.java index bdf2c6b02c..ea9ec644aa 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/regex/RegexPolicyProvider.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/regex/RegexPolicyProvider.java @@ -17,6 +17,13 @@ */ package org.keycloak.authorization.policy.provider.regex; +import static org.keycloak.utils.JsonUtils.getJsonValue; +import static org.keycloak.utils.JsonUtils.hasPath; +import static org.keycloak.utils.JsonUtils.splitClaimPath; + +import java.io.IOException; +import java.util.List; +import java.util.Optional; import java.util.function.BiFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -27,6 +34,8 @@ import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.policy.evaluation.Evaluation; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.representations.idm.authorization.RegexPolicyRepresentation; +import org.keycloak.util.JsonSerialization; +import com.fasterxml.jackson.databind.JsonNode; /** * @author Yoshiyuki Tabata @@ -47,17 +56,60 @@ public class RegexPolicyProvider implements PolicyProvider { public void evaluate(Evaluation evaluation) { AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider(); RegexPolicyRepresentation policy = representationFunction.apply(evaluation.getPolicy(), authorizationProvider); - Attributes.Entry targetClaim = evaluation.getContext().getIdentity().getAttributes().getValue(policy.getTargetClaim()); + String value = getClaimValue(evaluation, policy); - if (targetClaim == null) { + if (value == null) { return; } Pattern pattern = Pattern.compile(policy.getPattern()); - Matcher matcher = pattern.matcher(targetClaim.asString(0)); + Matcher matcher = pattern.matcher(value); if (matcher.matches()) { evaluation.grant(); } } + private String getClaimValue(Evaluation evaluation, RegexPolicyRepresentation policy) { + Attributes attributes = evaluation.getContext().getIdentity().getAttributes(); + String targetClaim = policy.getTargetClaim(); + + try { + if (hasPath(targetClaim)) { + return resolveJsonValue(attributes, targetClaim); + } + + return resolveSimpleValue(attributes, targetClaim); + } catch (IOException cause) { + throw new RuntimeException("Failed to resolve value from claim: " + targetClaim, cause); + } + } + + private String resolveSimpleValue(Attributes attributes, String targetClaim) { + Attributes.Entry value = attributes.getValue(targetClaim); + + if (value == null || value.isEmpty()) { + return null; + } + + return value.asString(0); + } + + private String resolveJsonValue(Attributes attributes, String targetClaim) throws IOException { + List paths = splitClaimPath(targetClaim); + + if (paths.isEmpty()) { + return null; + } + + Attributes.Entry attribute = attributes.getValue(paths.get(0)); + + if (attribute == null || attribute.isEmpty()) { + return null; + } + + JsonNode node = JsonSerialization.readValue(attribute.asString(0), JsonNode.class); + String path = String.join(".", paths.subList(1, paths.size())); + + return Optional.ofNullable(getJsonValue(node, path)).map(Object::toString).orElse(null); + } } diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-common/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-common/main/module.xml index a8ab099035..96b1b6b6fc 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-common/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-common/main/module.xml @@ -31,6 +31,8 @@ + + diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-common/main/module.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-common/main/module.xml index a8ab099035..96b1b6b6fc 100644 --- a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-common/main/module.xml +++ b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-common/main/module.xml @@ -31,6 +31,8 @@ + + diff --git a/server-spi-private/src/main/java/org/keycloak/utils/JsonUtils.java b/server-spi-private/src/main/java/org/keycloak/utils/JsonUtils.java new file mode 100644 index 0000000000..e0352e434e --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/utils/JsonUtils.java @@ -0,0 +1,139 @@ +/* + * Copyright 2022 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.utils; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Utility methods for manipulating JSON objects. + */ +public class JsonUtils { + + // A character in a claim component is either a literal character escaped by a backslash (\., \\, \_, \q, etc.) + // or any character other than backslash (escaping) and dot (claim component separator) + private static final Pattern CLAIM_COMPONENT = Pattern.compile("^((\\\\.|[^\\\\.])+?)\\."); + private static final Pattern BACKSLASHED_CHARACTER = Pattern.compile("\\\\(.)"); + + /** + * Splits the given {@code claim} into separate paths if the value contains separators as per {@link #CLAIM_COMPONENT}. + * + * @param claim the claim + * @return a list with the paths + */ + public static List splitClaimPath(String claim) { + final LinkedList claimComponents = new LinkedList<>(); + Matcher m = CLAIM_COMPONENT.matcher(claim); + int start = 0; + while (m.find()) { + claimComponents.add(BACKSLASHED_CHARACTER.matcher(m.group(1)).replaceAll("$1")); + start = m.end(); + // This is necessary to match the start of region as the start of string as determined by ^ + m.region(start, claim.length()); + } + if (claim.length() > start) { + claimComponents.add(BACKSLASHED_CHARACTER.matcher(claim.substring(start)).replaceAll("$1")); + } + return claimComponents; + } + + /** + * Determines if the given {@code claim} contains paths. + * + * @param claim the claim + * @return {@code true} if the {@code claim} contains paths. Otherwise, false. + */ + public static boolean hasPath(String claim) { + return CLAIM_COMPONENT.matcher(claim).find(); + } + + /** + *

Returns the value corresponding to the given {@code claim}. + * + * @param node the JSON node + * @param claim the claim + * @return the value + */ + public static Object getJsonValue(JsonNode node, String claim) { + if (node != null) { + List fields = splitClaimPath(claim); + if (fields.isEmpty() || claim.endsWith(".")) { + return null; + } + + JsonNode currentNode = node; + for (String currentFieldName : fields) { + + // if array path, retrieve field name and index + String currentNodeName = currentFieldName; + int arrayIndex = -1; + if (currentFieldName.endsWith("]")) { + int bi = currentFieldName.indexOf("["); + if (bi == -1) { + return null; + } + try { + String is = currentFieldName.substring(bi + 1, currentFieldName.length() - 1).trim(); + arrayIndex = Integer.parseInt(is); + if( arrayIndex < 0) throw new ArrayIndexOutOfBoundsException(); + } catch (Exception e) { + return null; + } + currentNodeName = currentFieldName.substring(0, bi).trim(); + } + + currentNode = currentNode.get(currentNodeName); + if (arrayIndex > -1 && currentNode.isArray()) { + currentNode = currentNode.get(arrayIndex); + } + + if (currentNode == null) { + return null; + } + + if (currentNode.isArray()) { + List values = new ArrayList<>(); + for (JsonNode childNode : currentNode) { + if (childNode.isTextual()) { + values.add(childNode.textValue()); + } + } + if (values.isEmpty()) { + return null; + } + return values ; + } else if (currentNode.isNull()) { + return null; + } else if (currentNode.isValueNode()) { + String ret = currentNode.asText(); + if (ret != null && !ret.trim().isEmpty()) + return ret.trim(); + else + return null; + } + + } + return currentNode; + } + return null; + } +} diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java index 5db71a2da0..73e277f353 100644 --- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java +++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java @@ -195,6 +195,8 @@ public class KeycloakIdentity implements Identity { while (valueIterator.hasNext()) { values.add(valueIterator.next().asText()); } + } else if (fieldValue.isObject()) { + values.add(fieldValue.toString()); } else { String value = fieldValue.asText(); diff --git a/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java b/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java index 05167f3eb4..ef2572aa7d 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java +++ b/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java @@ -17,13 +17,14 @@ package org.keycloak.broker.oidc.mappers; +import static org.keycloak.utils.JsonUtils.splitClaimPath; + import com.fasterxml.jackson.databind.JsonNode; import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider; import org.keycloak.broker.oidc.OIDCIdentityProvider; import org.keycloak.broker.provider.AbstractIdentityProviderMapper; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.models.IdentityProviderMapperModel; -import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.representations.JsonWebToken; import org.keycloak.util.JsonSerialization; @@ -47,7 +48,7 @@ public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper // found no match, try other claims } - List split = OIDCAttributeMapperHelper.splitClaimPath(claim); + List split = splitClaimPath(claim); Map jsonObject = token.getOtherClaims(); final int length = split.size(); int i = 0; diff --git a/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractJsonUserAttributeMapper.java b/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractJsonUserAttributeMapper.java index ca0ddff039..615cb0d322 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractJsonUserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractJsonUserAttributeMapper.java @@ -17,6 +17,8 @@ package org.keycloak.broker.oidc.mappers; +import static org.keycloak.utils.JsonUtils.splitClaimPath; + import com.fasterxml.jackson.databind.JsonNode; import org.jboss.logging.Logger; import org.keycloak.broker.oidc.OIDCIdentityProvider; @@ -27,7 +29,6 @@ import org.keycloak.models.IdentityProviderSyncMode; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; -import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.provider.ProviderConfigProperty; import java.util.ArrayList; @@ -206,7 +207,7 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr logger.debug("Going to process JsonNode path " + fieldPath + " on data " + baseNode); if (baseNode != null) { - List fields = OIDCAttributeMapperHelper.splitClaimPath(fieldPath); + List fields = splitClaimPath(fieldPath); if (fields.isEmpty() || fieldPath.endsWith(".")) { logger.debug("JSON path is invalid " + fieldPath); return null; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java index 2fc96ef3a1..b59dbb631f 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java @@ -17,6 +17,8 @@ package org.keycloak.protocol.oidc.mappers; +import static org.keycloak.utils.JsonUtils.splitClaimPath; + import org.keycloak.models.ProtocolMapperModel; import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.representations.AccessToken; @@ -100,7 +102,7 @@ abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper protocolClaim = CLIENT_ID_PATTERN.matcher(protocolClaim).replaceAll(clientId); } - List split = OIDCAttributeMapperHelper.splitClaimPath(protocolClaim); + List split = splitClaimPath(protocolClaim); // Special case if (checkAccessToken(token, split, attributeValue)) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java index 666b3ec8a8..2375e5e6ed 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java @@ -17,6 +17,8 @@ package org.keycloak.protocol.oidc.mappers; +import static org.keycloak.utils.JsonUtils.splitClaimPath; + import com.fasterxml.jackson.databind.JsonNode; import org.jboss.logging.Logger; import org.keycloak.models.ProtocolMapperModel; @@ -31,8 +33,6 @@ import org.keycloak.util.JsonSerialization; import java.util.*; import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -250,28 +250,6 @@ public class OIDCAttributeMapperHelper { return null; } - // A character in a claim component is either a literal character escaped by a backslash (\., \\, \_, \q, etc.) - // or any character other than backslash (escaping) and dot (claim component separator) - private static final Pattern CLAIM_COMPONENT = Pattern.compile("^((\\\\.|[^\\\\.])+?)\\."); - - private static final Pattern BACKSLASHED_CHARACTER = Pattern.compile("\\\\(.)"); - - public static List splitClaimPath(String claimPath) { - final LinkedList claimComponents = new LinkedList<>(); - Matcher m = CLAIM_COMPONENT.matcher(claimPath); - int start = 0; - while (m.find()) { - claimComponents.add(BACKSLASHED_CHARACTER.matcher(m.group(1)).replaceAll("$1")); - start = m.end(); - // This is necessary to match the start of region as the start of string as determined by ^ - m.region(start, claimPath.length()); - } - if (claimPath.length() > start) { - claimComponents.add(BACKSLASHED_CHARACTER.matcher(claimPath.substring(start)).replaceAll("$1")); - } - return claimComponents; - } - public static void mapClaim(IDToken token, ProtocolMapperModel mappingModel, Object attributeValue) { mapClaim(token, mappingModel, attributeValue, tokenPropertySetters, token.getOtherClaims()); } diff --git a/services/src/test/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelperTest.java b/services/src/test/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelperTest.java index 438544c16a..da1f80753b 100644 --- a/services/src/test/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelperTest.java +++ b/services/src/test/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelperTest.java @@ -18,6 +18,8 @@ package org.keycloak.protocol.oidc.mappers; import org.hamcrest.Matchers; import org.junit.Test; +import org.keycloak.utils.JsonUtils; + import static org.junit.Assert.assertThat; /** @@ -28,17 +30,17 @@ public class OIDCAttributeMapperHelperTest { @Test public void testSplitClaimPath() { - assertThat(OIDCAttributeMapperHelper.splitClaimPath(""), Matchers.empty()); - assertThat(OIDCAttributeMapperHelper.splitClaimPath("a"), Matchers.contains("a")); + assertThat(JsonUtils.splitClaimPath(""), Matchers.empty()); + assertThat(JsonUtils.splitClaimPath("a"), Matchers.contains("a")); - assertThat(OIDCAttributeMapperHelper.splitClaimPath("a.b"), Matchers.contains("a", "b")); - assertThat(OIDCAttributeMapperHelper.splitClaimPath("a\\.b"), Matchers.contains("a.b")); - assertThat(OIDCAttributeMapperHelper.splitClaimPath("a\\\\.b"), Matchers.contains("a\\", "b")); - assertThat(OIDCAttributeMapperHelper.splitClaimPath("a\\\\\\.b"), Matchers.contains("a\\.b")); + assertThat(JsonUtils.splitClaimPath("a.b"), Matchers.contains("a", "b")); + assertThat(JsonUtils.splitClaimPath("a\\.b"), Matchers.contains("a.b")); + assertThat(JsonUtils.splitClaimPath("a\\\\.b"), Matchers.contains("a\\", "b")); + assertThat(JsonUtils.splitClaimPath("a\\\\\\.b"), Matchers.contains("a\\.b")); - assertThat(OIDCAttributeMapperHelper.splitClaimPath("c.a\\\\.b"), Matchers.contains("c", "a\\", "b")); - assertThat(OIDCAttributeMapperHelper.splitClaimPath("c.a\\\\\\.b"), Matchers.contains("c", "a\\.b")); - assertThat(OIDCAttributeMapperHelper.splitClaimPath("c\\\\\\.b.a\\\\\\.b"), Matchers.contains("c\\.b", "a\\.b")); - assertThat(OIDCAttributeMapperHelper.splitClaimPath("c\\h\\.b.a\\\\\\.b"), Matchers.contains("ch.b", "a\\.b")); + assertThat(JsonUtils.splitClaimPath("c.a\\\\.b"), Matchers.contains("c", "a\\", "b")); + assertThat(JsonUtils.splitClaimPath("c.a\\\\\\.b"), Matchers.contains("c", "a\\.b")); + assertThat(JsonUtils.splitClaimPath("c\\\\\\.b.a\\\\\\.b"), Matchers.contains("c\\.b", "a\\.b")); + assertThat(JsonUtils.splitClaimPath("c\\h\\.b.a\\\\\\.b"), Matchers.contains("ch.b", "a\\.b")); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RegexPolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RegexPolicyTest.java index ab89c975f4..702da1160b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RegexPolicyTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RegexPolicyTest.java @@ -56,38 +56,35 @@ public class RegexPolicyTest extends AbstractAuthzTest { @Override public void addTestRealms(List testRealms) { - ProtocolMapperRepresentation userAttrFooProtocolMapper = new ProtocolMapperRepresentation(); - userAttrFooProtocolMapper.setName("userAttrFoo"); - userAttrFooProtocolMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); - userAttrFooProtocolMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - Map configFoo = new HashMap<>(); - configFoo.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); - configFoo.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); - configFoo.put(OIDCAttributeMapperHelper.JSON_TYPE, "String"); - configFoo.put("user.attribute", "foo"); - configFoo.put("claim.name", "foo"); - userAttrFooProtocolMapper.setConfig(configFoo); + Map claims = new HashMap<>(); - ProtocolMapperRepresentation userAttrBarProtocolMapper = new ProtocolMapperRepresentation(); - userAttrBarProtocolMapper.setName("userAttrBar"); - userAttrBarProtocolMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); - userAttrBarProtocolMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - Map configBar = new HashMap<>(); - configBar.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); - configBar.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); - configBar.put(OIDCAttributeMapperHelper.JSON_TYPE, "String"); - configBar.put("user.attribute", "bar"); - configBar.put("claim.name", "bar"); - userAttrBarProtocolMapper.setConfig(configBar); + claims.put("user.attribute", "foo"); + claims.put("claim.name", "foo"); + ProtocolMapperRepresentation userAttrFooProtocolMapper = addClaimMapper("userAttrFoo", claims); + + claims.put("user.attribute", "bar"); + claims.put("claim.name", "bar"); + ProtocolMapperRepresentation userAttrBarProtocolMapper = addClaimMapper("userAttrBar", claims); + + claims.put("user.attribute", "json-simple"); + claims.put("claim.name", "userinfo"); + ProtocolMapperRepresentation userAttrJsonProtocolMapper = addClaimMapper("userAttrJsonSimple", claims); + + claims.put("user.attribute", "json-complex"); + claims.put("claim.name", "json-complex"); + ProtocolMapperRepresentation userAttrJsonComplexProtocolMapper = addClaimMapper("userAttrJsonComplex", claims); + + // For JSON-based claims, you can use dot notation for nesting and square brackets to access array fields by index. For example, contact.address[0].country. testRealms.add(RealmBuilder.create().name("authz-test") .user(UserBuilder.create().username("marta").password("password").addAttribute("foo", "foo").addAttribute("bar", - "barbar")) + "barbar").addAttribute("json-simple", "{\"tenant\": \"abc\"}") + .addAttribute("json-complex", "{\"userinfo\": {\"tenant\": \"abc\"}, \"some-array\": [\"foo\",\"bar\"]}")) .user(UserBuilder.create().username("taro").password("password").addAttribute("foo", "faa").addAttribute("bar", "bbarbar")) .client(ClientBuilder.create().clientId("resource-server-test").secret("secret").authorizationServicesEnabled(true) .redirectUris("http://localhost/resource-server-test").directAccessGrants() - .protocolMapper(userAttrFooProtocolMapper, userAttrBarProtocolMapper)) + .protocolMapper(userAttrFooProtocolMapper, userAttrBarProtocolMapper, userAttrJsonProtocolMapper, userAttrJsonComplexProtocolMapper)) .build()); } @@ -95,12 +92,21 @@ public class RegexPolicyTest extends AbstractAuthzTest { public void configureAuthorization() throws Exception { createResource("Resource A"); createResource("Resource B"); + createResource("Resource C"); + createResource("Resource D"); + createResource("Resource E"); createRegexPolicy("Regex foo Policy", "foo", "foo"); createRegexPolicy("Regex bar Policy", "bar", "^bar.+$"); + createRegexPolicy("Regex json-simple Policy", "userinfo.tenant", "^abc$"); + createRegexPolicy("Regex json-complex Policy", "json-complex.userinfo.tenant", "^abc$"); + createRegexPolicy("Regex json-array Policy", "json-complex.some-array[1]", "bar"); createResourcePermission("Resource A Permission", "Resource A", "Regex foo Policy"); createResourcePermission("Resource B Permission", "Resource B", "Regex bar Policy"); + createResourcePermission("Resource C Permission", "Resource C", "Regex json-simple Policy"); + createResourcePermission("Resource D Permission", "Resource D", "Regex json-complex Policy"); + createResourcePermission("Resource E Permission", "Resource E", "Regex json-array Policy"); } private void createResource(String name) { @@ -164,6 +170,21 @@ public class RegexPolicyTest extends AbstractAuthzTest { ticket = authzClient.protection().permission().create(request).getTicket(); response = authzClient.authorization("marta", "password").authorize(new AuthorizationRequest(ticket)); assertNotNull(response.getToken()); + + request = new PermissionRequest("Resource C"); + ticket = authzClient.protection().permission().create(request).getTicket(); + response = authzClient.authorization("marta", "password").authorize(new AuthorizationRequest(ticket)); + assertNotNull(response.getToken()); + + request = new PermissionRequest("Resource D"); + ticket = authzClient.protection().permission().create(request).getTicket(); + response = authzClient.authorization("marta", "password").authorize(new AuthorizationRequest(ticket)); + assertNotNull(response.getToken()); + + request = new PermissionRequest("Resource E"); + ticket = authzClient.protection().permission().create(request).getTicket(); + response = authzClient.authorization("marta", "password").authorize(new AuthorizationRequest(ticket)); + assertNotNull(response.getToken()); } @Test @@ -188,9 +209,42 @@ public class RegexPolicyTest extends AbstractAuthzTest { } catch (AuthorizationDeniedException ignore) { } + + // Access Resource C with taro. + request = new PermissionRequest("Resource C"); + ticket = authzClient.protection().permission().create(request).getTicket(); + try { + authzClient.authorization("taro", "password").authorize(new AuthorizationRequest(ticket)); + fail("Should fail."); + } catch (AuthorizationDeniedException ignore) { + + } + + // Access Resource D with taro. + request = new PermissionRequest("Resource D"); + ticket = authzClient.protection().permission().create(request).getTicket(); + try { + authzClient.authorization("taro", "password").authorize(new AuthorizationRequest(ticket)); + fail("Should fail."); + } catch (AuthorizationDeniedException ignore) { + + } } private AuthzClient getAuthzClient() { return AuthzClient.create(getClass().getResourceAsStream("/authorization-test/default-keycloak.json")); } + + private ProtocolMapperRepresentation addClaimMapper(String name, Map claims) { + ProtocolMapperRepresentation userAttrBarProtocolMapper = new ProtocolMapperRepresentation(); + userAttrBarProtocolMapper.setName(name); + userAttrBarProtocolMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); + userAttrBarProtocolMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + Map configBar = new HashMap<>(claims); + configBar.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + configBar.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + configBar.put(OIDCAttributeMapperHelper.JSON_TYPE, "String"); + userAttrBarProtocolMapper.setConfig(configBar); + return userAttrBarProtocolMapper; + } }