diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyConfigDeserializer.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyConfigDeserializer.java new file mode 100644 index 00000000000..dd17fc7b39f --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyConfigDeserializer.java @@ -0,0 +1,62 @@ +package org.keycloak.representations.idm.authorization; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.node.ArrayNode; + +public class PolicyConfigDeserializer extends StdDeserializer> { + + public PolicyConfigDeserializer() { + this(null); + } + + public PolicyConfigDeserializer(Class> t) { + super(t); + } + + @Override + public Map deserialize(JsonParser parser, DeserializationContext context) throws IOException { + + // ensure we are at the start of the JSON object for the map + if (parser.currentToken() != JsonToken.START_OBJECT) { + context.reportWrongTokenException(Map.class, JsonToken.START_OBJECT, + "Expected START_OBJECT for config map"); + } + + Map map = new HashMap<>(); + ObjectMapper mapper = (ObjectMapper) parser.getCodec(); + + // loop through key-value pairs in the JSON object + while (parser.nextToken() != JsonToken.END_OBJECT) { + // get the key and value + String key = parser.currentName(); + parser.nextToken(); + + // check the type of the value token + if (parser.currentToken() == JsonToken.START_ARRAY) { + // case 1: value is a JSON array + ArrayNode arrayNode = mapper.readTree(parser); + + // convert the ArrayNode back into a single string (the original format) + String originalStringFormat = arrayNode.toString(); + map.put(key, originalStringFormat); + } else if (parser.currentToken() == JsonToken.VALUE_STRING) { + // case 2: value is a JSON String (the regular case) + String value = parser.getText(); + map.put(key, value); + } else { + // handle other unexpected types, if necessary + context.reportWrongTokenException(Map.class, parser.currentToken(), + "Expected String or Array for config value"); + } + } + return map; + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyConfigSerializer.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyConfigSerializer.java new file mode 100644 index 00000000000..177d4b164ab --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyConfigSerializer.java @@ -0,0 +1,52 @@ +package org.keycloak.representations.idm.authorization; + +import java.io.IOException; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +public class PolicyConfigSerializer extends StdSerializer> { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + public PolicyConfigSerializer() { + this(null); + } + + public PolicyConfigSerializer(Class> t) { + super(t); + } + + @Override + public void serialize(Map map, JsonGenerator generator, SerializerProvider provider) throws IOException { + generator.writeStartObject(); + + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + generator.writeFieldName(key); + + // check if the value string looks like a JSON array + if (value != null && value.startsWith("[\"") && value.endsWith("\"]")) { + try { + // attempt to read the string as a JSON node (which should be an ArrayNode) + ArrayNode arrayNode = (ArrayNode) MAPPER.readTree(value); + // write the ArrayNode directly to the generator + generator.writeTree(arrayNode); + } catch (Exception e) { + // if parsing fails, write the value as a plain string + generator.writeString(value); + } + } else { + // not an array string, so write as a plain string + generator.writeString(value); + } + } + generator.writeEndObject(); + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java index e36d7af445e..50b928f5539 100644 --- a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java @@ -19,12 +19,17 @@ package org.keycloak.representations.idm.authorization; import java.util.HashMap; import java.util.Map; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + /** * @author Pedro Igor */ public class PolicyRepresentation extends AbstractPolicyRepresentation { - private Map config = new HashMap(); + @JsonSerialize(using = PolicyConfigSerializer.class) + @JsonDeserialize(using = PolicyConfigDeserializer.class) + private Map config = new HashMap<>(); public Map getConfig() { return this.config; @@ -33,4 +38,4 @@ public class PolicyRepresentation extends AbstractPolicyRepresentation { public void setConfig(Map config) { this.config = config; } -} \ No newline at end of file +}