Support JSON objects when evaluating claims in regex policy
Closes #11514
This commit is contained in:
parent
c95ecd9e88
commit
3d2c3fbc6a
11 changed files with 301 additions and 66 deletions
|
@ -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 <a href="mailto:yoshiyuki.tabata.jy@hitachi.com">Yoshiyuki Tabata</a>
|
||||
|
@ -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<String> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
<module name="org.keycloak.keycloak-server-spi"/>
|
||||
<module name="org.keycloak.keycloak-server-spi-private"/>
|
||||
<module name="org.keycloak.keycloak-services"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-core"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-databind"/>
|
||||
</dependencies>
|
||||
|
||||
</module>
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
<module name="org.keycloak.keycloak-server-spi"/>
|
||||
<module name="org.keycloak.keycloak-server-spi-private"/>
|
||||
<module name="org.keycloak.keycloak-services"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-core"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-databind"/>
|
||||
</dependencies>
|
||||
|
||||
</module>
|
||||
|
|
|
@ -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<String> splitClaimPath(String claim) {
|
||||
final LinkedList<String> 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>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<String> 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<String> 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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<String> split = OIDCAttributeMapperHelper.splitClaimPath(claim);
|
||||
List<String> split = splitClaimPath(claim);
|
||||
Map<String, Object> jsonObject = token.getOtherClaims();
|
||||
final int length = split.size();
|
||||
int i = 0;
|
||||
|
|
|
@ -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<String> fields = OIDCAttributeMapperHelper.splitClaimPath(fieldPath);
|
||||
List<String> fields = splitClaimPath(fieldPath);
|
||||
if (fields.isEmpty() || fieldPath.endsWith(".")) {
|
||||
logger.debug("JSON path is invalid " + fieldPath);
|
||||
return null;
|
||||
|
|
|
@ -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<String> split = OIDCAttributeMapperHelper.splitClaimPath(protocolClaim);
|
||||
List<String> split = splitClaimPath(protocolClaim);
|
||||
|
||||
// Special case
|
||||
if (checkAccessToken(token, split, attributeValue)) {
|
||||
|
|
|
@ -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<String> splitClaimPath(String claimPath) {
|
||||
final LinkedList<String> 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());
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,38 +56,35 @@ public class RegexPolicyTest extends AbstractAuthzTest {
|
|||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
ProtocolMapperRepresentation userAttrFooProtocolMapper = new ProtocolMapperRepresentation();
|
||||
userAttrFooProtocolMapper.setName("userAttrFoo");
|
||||
userAttrFooProtocolMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID);
|
||||
userAttrFooProtocolMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
Map<String, String> 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<String, String> claims = new HashMap<>();
|
||||
|
||||
ProtocolMapperRepresentation userAttrBarProtocolMapper = new ProtocolMapperRepresentation();
|
||||
userAttrBarProtocolMapper.setName("userAttrBar");
|
||||
userAttrBarProtocolMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID);
|
||||
userAttrBarProtocolMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
Map<String, String> 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<String, String> claims) {
|
||||
ProtocolMapperRepresentation userAttrBarProtocolMapper = new ProtocolMapperRepresentation();
|
||||
userAttrBarProtocolMapper.setName(name);
|
||||
userAttrBarProtocolMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID);
|
||||
userAttrBarProtocolMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
Map<String, String> 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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue