Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@
import org.wso2.carbon.identity.oauth.rar.exception.AuthorizationDetailsProcessingException;
import org.wso2.carbon.identity.oauth.rar.model.AuthorizationDetail;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.wso2.carbon.identity.oauth.rar.util.AuthorizationDetailsConstants.SCHEMA_VALIDATION_FAILED_ERR_MSG_FORMAT;
import static org.wso2.carbon.identity.oauth.rar.util.AuthorizationDetailsConstants.TYPE_VALIDATION_FAILED_ERR_MSG_FORMAT;
Expand Down Expand Up @@ -68,6 +75,8 @@ public class AuthorizationDetailsSchemaValidatorImpl implements AuthorizationDet

private static final String ADDITIONAL_PROPERTIES = "additionalProperties";
private static final String BASE_URI = "https://wso2.com/identity-server/schemas";
private static final Set<String> NON_NEGATIVE_INTEGER_SCHEMA_KEYWORDS = Set.of("minItems", "maxItems",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the list misses minContains and maxContains. A schema using contains with either one may still crash. shall we check that too?

@sanjulaonline sanjulaonline Sep 28, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vert.x 4.5.25 reads both minContains and maxContains through JsonObject#getInteger, and the new regression case reproduced the same Double cannot be cast to Integer failure. I added both keywords to the normalization set and covered successful normalization, caller-map immutability, and maxContains enforcement in eb54bc8. The focused validator suite now passes all 13 tests, and the full RAR suite passes all 45 tests.

"minLength", "maxLength", "minProperties", "maxProperties", "minContains", "maxContains");

private static volatile AuthorizationDetailsSchemaValidator instance;
private final JsonSchemaOptions jsonSchemaOptions;
Expand Down Expand Up @@ -218,9 +227,94 @@ public boolean isSchemaCompliant(final Map<String, Object> schema, final Authori
return false;
}

final JsonObject jsonSchema = new JsonObject(schema);
final JsonObject jsonSchema = new JsonObject(this.normalizeSchemaMap(schema));
jsonSchema.put(ADDITIONAL_PROPERTIES, false); // Ensure no unknown fields are allowed

return this.isSchemaCompliant(jsonSchema, authorizationDetail);
}

private Map<String, Object> normalizeSchemaMap(final Map<String, Object> schema) {

final Map<String, Object> normalizedSchema = new HashMap<>();
for (Map.Entry<String, Object> entry : schema.entrySet()) {
normalizedSchema.put(entry.getKey(), this.normalizeSchemaValue(entry.getKey(), entry.getValue()));
}
return normalizedSchema;
}

@SuppressWarnings("unchecked")
private Object normalizeSchemaValue(final String key, final Object value) {

if (value instanceof Number && NON_NEGATIVE_INTEGER_SCHEMA_KEYWORDS.contains(key)) {
return this.normalizeNonNegativeIntegerSchemaKeyword(value);
}
if (value instanceof Map) {
return this.normalizeSchemaMap((Map<String, Object>) value);
}
if (value instanceof Collection) {
return this.normalizeSchemaCollection((Collection<?>) value);
}
return value;
}

@SuppressWarnings("unchecked")
private List<Object> normalizeSchemaCollection(final Collection<?> values) {

final List<Object> normalizedValues = new ArrayList<>(values.size());
for (Object value : values) {
if (value instanceof Map) {
normalizedValues.add(this.normalizeSchemaMap((Map<String, Object>) value));
} else if (value instanceof Collection) {
normalizedValues.add(this.normalizeSchemaCollection((Collection<?>) value));
} else {
normalizedValues.add(value);
}
}
return normalizedValues;
}

private Object normalizeNonNegativeIntegerSchemaKeyword(final Object value) {

if (!(value instanceof Number)) {
return value;
}

final Number number = (Number) value;
if (number instanceof Integer) {
return number;
}
if (number instanceof Byte || number instanceof Short) {
final int intValue = number.intValue();
return intValue >= 0 ? intValue : value;
}
if (number instanceof Long) {
final long longValue = number.longValue();
return longValue >= 0 && longValue <= Integer.MAX_VALUE ? (int) longValue : value;
}
if (number instanceof BigInteger) {
return this.normalizeBigInteger((BigInteger) number, value);
}
if (number instanceof BigDecimal) {
try {
return this.normalizeBigInteger(((BigDecimal) number).toBigIntegerExact(), value);
} catch (ArithmeticException e) {
return value;
}
}

final double doubleValue = number.doubleValue();
if (Double.isFinite(doubleValue) && doubleValue >= 0 && doubleValue <= Integer.MAX_VALUE
&& doubleValue == Math.rint(doubleValue)) {
return (int) doubleValue;
}
return value;
}

private Object normalizeBigInteger(final BigInteger integerValue, final Object originalValue) {

if (integerValue.signum() >= 0 && integerValue.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) <= 0) {
return integerValue.intValue();
}
return originalValue;
}
}
Loading