Skip to content
Merged
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 @@ -19,6 +19,9 @@
package org.wso2.carbon.identity.api.resource.mgt.util;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.ToNumberPolicy;
import com.google.gson.ToNumberStrategy;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Expand All @@ -36,6 +39,23 @@ public class AuthorizationDetailsTypesUtil {

private static final Log LOG = LogFactory.getLog(AuthorizationDetailsTypesUtil.class);
private static final Gson GSON = new Gson();
/*
* By default, Gson parses every JSON number of a Map<String, Object> as a Double. JSON schema validators such as
* Vert.x cast integer keywords (e.g. minItems, maxItems) directly to Integer, hence integral values (including
* ones written as 1.0) are returned as Integer, or as Long when they exceed the Integer range.
*/
private static final ToNumberStrategy INTEGRAL_PRESERVING_NUMBER_STRATEGY = in -> {
final Number number = ToNumberPolicy.LONG_OR_DOUBLE.readNumber(in);
final double doubleValue = number.doubleValue();
if (doubleValue >= Integer.MIN_VALUE && doubleValue <= Integer.MAX_VALUE
&& doubleValue == Math.rint(doubleValue)) {
return (int) doubleValue;
}
return number;
};
private static final Gson INTEGRAL_PRESERVING_GSON = new GsonBuilder()
.setObjectToNumberStrategy(INTEGRAL_PRESERVING_NUMBER_STRATEGY)
.create();
private static final Type SCHEMA_TYPE = new TypeToken<Map<String, Object>>() { }.getType();

/**
Expand All @@ -49,6 +69,19 @@ public static Map<String, Object> parseSchema(final String schema) {
return GSON.fromJson(schema, SCHEMA_TYPE);
}

/**
* Parses a JSON schema into a map structure to be used for authorization details schema validation. Unlike
* {@link #parseSchema(String)}, integral numbers are returned as {@code Integer} (or {@code Long}) instead of
* {@code Double}, as JSON schema validators expect integer keywords such as {@code minItems} to be integers.
*
* @param schema the JSON schema string to be parsed. It must be a valid JSON string.
* @return a {@code Map<String, Object>} representing the parsed JSON schema.
*/
public static Map<String, Object> parseSchemaForValidation(final String schema) {

return INTEGRAL_PRESERVING_GSON.fromJson(schema, SCHEMA_TYPE);
}

/**
* Converts a map representing a schema into its JSON string representation.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you 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.wso2.carbon.identity.api.resource.mgt;

import org.testng.Assert;
import org.testng.annotations.Test;
import org.wso2.carbon.identity.api.resource.mgt.util.AuthorizationDetailsTypesUtil;

import java.util.List;
import java.util.Map;

/**
* Test class for {@link AuthorizationDetailsTypesUtil}.
*/
public class AuthorizationDetailsTypesUtilTest {

private static final String SCHEMA = "{\"type\":\"object\",\"required\":[\"type\"],\"minProperties\":1," +
"\"properties\":{\"type\":{\"type\":\"string\",\"const\":\"test_type\"}," +
"\"actions\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"minItems\":1,\"maxItems\":3.0}," +
"\"amount\":{\"type\":\"number\",\"minimum\":0.5,\"maximum\":9999999999}}," +
"\"allOf\":[{\"maxProperties\":5}]}";

@Test
@SuppressWarnings("unchecked")
public void testParseSchemaForValidationPreservesIntegralValues() {

final Map<String, Object> schema = AuthorizationDetailsTypesUtil.parseSchemaForValidation(SCHEMA);
final Map<String, Object> properties = (Map<String, Object>) schema.get("properties");
final Map<String, Object> actions = (Map<String, Object>) properties.get("actions");
final Map<String, Object> amount = (Map<String, Object>) properties.get("amount");
final Map<String, Object> allOf = ((List<Map<String, Object>>) schema.get("allOf")).get(0);

Assert.assertEquals(schema.get("minProperties"), 1);
Assert.assertEquals(actions.get("minItems"), 1);
Assert.assertEquals(actions.get("maxItems"), 3);
Assert.assertEquals(allOf.get("maxProperties"), 5);
Assert.assertEquals(amount.get("minimum"), 0.5d);
Assert.assertEquals(amount.get("maximum"), 9999999999L);
}

@Test
@SuppressWarnings("unchecked")
public void testParseSchemaKeepsExistingNumberFormat() {

final Map<String, Object> schema = AuthorizationDetailsTypesUtil.parseSchema(SCHEMA);
final Map<String, Object> actions = (Map<String, Object>) ((Map<String, Object>) schema.get("properties"))
.get("actions");

Assert.assertEquals(schema.get("minProperties"), 1.0d);
Assert.assertEquals(actions.get("minItems"), 1.0d);
Assert.assertEquals(actions.get("maxItems"), 3.0d);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<class name="org.wso2.carbon.identity.api.resource.mgt.APIResourceManagerTest"/>
<class name="org.wso2.carbon.identity.api.resource.mgt.APIResourceManagementUtilTest"></class>
<class name="org.wso2.carbon.identity.api.resource.mgt.AuthorizationDetailsTypeManagerTest"/>
<class name="org.wso2.carbon.identity.api.resource.mgt.AuthorizationDetailsTypesUtilTest"/>
<class name="org.wso2.carbon.identity.api.resource.mgt.dao.CacheBackedAuthorizationDetailsTypeMgtDAOImplTest"/>
</classes>
</test>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1170,7 +1170,7 @@ private AuthorizationDetailsType buildAuthorizationDetailsTypeWithSchema(ResultS
final AuthorizationDetailsType authorizationDetailsType = this.buildAuthorizationDetailsType(resultSet);

if (authorizationDetailsType != null) {
authorizationDetailsType.setSchema(AuthorizationDetailsTypesUtil.parseSchema(resultSet
authorizationDetailsType.setSchema(AuthorizationDetailsTypesUtil.parseSchemaForValidation(resultSet
.getString(ApplicationConstants.ApplicationTableColumns.AUTHORIZATION_DETAILS_SCHEMA)));
}

Expand Down
Loading