Skip to content

Commit

Permalink
Merge pull request #9275 from uyuni-project/api-datetime-input
Browse files Browse the repository at this point in the history
Fix date input on HTTP API endpoints
  • Loading branch information
deneb-alpha authored Sep 24, 2024
2 parents 3c139ae + 59b8bab commit 73b24c0
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 37 deletions.
34 changes: 34 additions & 0 deletions java/code/src/com/redhat/rhn/frontend/xmlrpc/BaseHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
import com.suse.manager.api.ReadOnly;
import com.suse.salt.netapi.utils.Xor;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.HibernateException;
Expand Down Expand Up @@ -409,6 +412,37 @@ public static void ensureUserRole(User user, Role role)
}
}

/**
* Parse an input element uniformly for both XMLRPC and JSON APIs.
* <p>
* Useful when parsing input parameter values inside complex structs where there's no type information available.
* <p>
* XMLRPC and JSON APIs automatically parse the values for top-level parameters according to the type information
* available. However, the values nested inside a complex struct must be parsed inside the specific handler method.
*
* @param argIn the input value
* @return the parsed {@link T} object
* @throws InvalidParameterException when the input cannot be parsed
*/
protected static <T> T parseInputValue(Object argIn, Class<T> typeIn) throws InvalidParameterException {
T value;
try {
if (typeIn.isAssignableFrom(argIn.getClass())) {
// Assume exact type (XMLRPC)
value = typeIn.cast(argIn);
}
else {
// Interpret as string (JSON over HTTP)
value = new Gson().fromJson("\"" + argIn + "\"", typeIn);
}
}
catch (ClassCastException | JsonSyntaxException e) {
throw new InvalidParameterException("Wrong input format", e);
}

return value;
}

/**
* Ensure the org exists
* @param orgId the org id to check
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1516,7 +1516,7 @@ public List<ErrataOverview> listErrata(User loggedInUser,
@ReadOnly
public List<ErrataOverview> listErrata(User loggedInUser, String channelLabel)
throws NoSuchChannelException {
return listErrata(loggedInUser, channelLabel, (Date) null);
return listErrata(loggedInUser, channelLabel, null);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,37 +279,19 @@ public Integer setDetails(User loggedInUser, String advisoryName, Map<String, Ob

if (details.containsKey("issue_date")) {
try {
errata.setIssueDate((Date)details.get("issue_date"));
errata.setIssueDate(parseInputValue(details.get("issue_date"), Date.class));
}
catch (ClassCastException e) {
throw new InvalidParameterException("Wrong 'issue_date' format.");
}
}

if (details.containsKey("update_date")) {
try {
errata.setUpdateDate((Date)details.get("update_date"));
}
catch (ClassCastException e) {
throw new InvalidParameterException("Wrong 'update_date' format.");
}
}

if (details.containsKey("issue_date")) {
try {
errata.setIssueDate((Date)details.get("issue_date"));
}
catch (ClassCastException e) {
throw new InvalidParameterException("Wrong 'issue_date' format.");
catch (InvalidParameterException e) {
throw new InvalidParameterException("Wrong 'issue_date' format.", e);
}
}

if (details.containsKey("update_date")) {
try {
errata.setUpdateDate((Date)details.get("update_date"));
errata.setUpdateDate(parseInputValue(details.get("update_date"), Date.class));
}
catch (ClassCastException e) {
throw new InvalidParameterException("Wrong 'update_date' format.");
catch (InvalidParameterException e) {
throw new InvalidParameterException("Wrong 'update_date' format.", e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,12 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -291,6 +294,26 @@ public void testSetDetails() throws Exception {
assertTrue(foundKeyword2);
}

@Test
public void testSetDetailsDates() throws Exception {
Errata errata = ErrataFactoryTest.createTestErrata(user.getOrg().getId());

Map<String, Object> details = new HashMap<>();
Date expectedDate = Date.from(LocalDate.of(1989, 4, 1).atStartOfDay().toInstant(ZoneOffset.UTC));
// Set using Date instance (XMLRPC)
details.put("issue_date", expectedDate);
// Set using ISO-8601 String (JSON over HTTP)
details.put("update_date", "1989-04-01T00:00:00Z");

int result = handler.setDetails(user, errata.getAdvisoryName(), details);

assertEquals(1, result);

Errata updatedErrata = ErrataManager.lookupErrata(errata.getId(), user);
assertEquals(expectedDate, updatedErrata.getIssueDate());
assertEquals(expectedDate, updatedErrata.getUpdateDate());
}

@Test
public void testListAffectedSystems() throws Exception {
//no affected systems
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,10 @@ public int deleteSnapshots(User loggedInUser, Map dateDetails) {
Date endDate = null;

if (dateDetails.containsKey("startDate")) {
startDate = (Date)dateDetails.get("startDate");
startDate = parseInputValue(dateDetails.get("startDate"), Date.class);
}
if (dateDetails.containsKey("endDate")) {
endDate = (Date)dateDetails.get("endDate");
endDate = parseInputValue(dateDetails.get("endDate"), Date.class);
}
return deleteSnapshots(loggedInUser, startDate, endDate);
}
Expand Down
7 changes: 3 additions & 4 deletions java/code/src/com/suse/manager/api/ApiRequestParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
*/
public class ApiRequestParser {
private final Gson gson;
private final JsonParser parser = new JsonParser();

/**
* Constructs a parser with a {@link Gson} instance
Expand All @@ -49,7 +48,7 @@ public ApiRequestParser(Gson gsonIn) {
* @return the JSON object properties as key-value pairs
*/
public Map<String, JsonElement> parseBody(String body) throws ParseException {
JsonElement bodyElement = parser.parse(body);
JsonElement bodyElement = JsonParser.parseString(body);

if (bodyElement.isJsonNull()) {
return Collections.emptyMap();
Expand Down Expand Up @@ -115,11 +114,11 @@ private JsonElement parseQueryParam(String value) throws ParseException {
JsonElement element;
try {
// Try to parse the literal value
element = parser.parse(value);
element = JsonParser.parseString(value);
}
catch (JsonSyntaxException e) {
// Invalid syntax, try as string
element = parser.parse('"' + value + '"');
element = JsonParser.parseString('"' + value + '"');
}

if (element.isJsonPrimitive() || element.isJsonNull()) {
Expand Down
2 changes: 0 additions & 2 deletions java/code/src/com/suse/manager/api/ListDeserializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@

/**
* Custom {@link List} deserializer that handles arbitrary numbers properly
* @deprecated the same behavior can be achieved using ToNumberPolicy.LONG_OR_DOUBLE with gson-2.8.9
*/
@Deprecated
public class ListDeserializer implements JsonDeserializer<List<Object>> {
@Override
public List<Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
Expand Down
2 changes: 0 additions & 2 deletions java/code/src/com/suse/manager/api/MapDeserializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@

/**
* Custom {@link Map} deserializer that handles arbitrary numbers properly
* @deprecated the same behavior can be achieved using ToNumberPolicy.LONG_OR_DOUBLE with gson-2.8.9
*/
@Deprecated
public class MapDeserializer implements JsonDeserializer<Map<String, Object>> {
@Override
public Map<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ public class RouteFactoryTest extends BaseControllerTestCase {
.registerTypeAdapter(Map.class, new MapDeserializer())
.registerTypeAdapter(List.class, new ListDeserializer())
.create();
private final JsonParser parser = new JsonParser();
private RouteFactory routeFactory;
private TestHandler handler;

Expand Down Expand Up @@ -718,7 +717,7 @@ public void testQueryStringArray() throws Exception {
* @return the result object
*/
private <T> T getResult(String response, Type resultType) {
JsonObject obj = parser.parse(response).getAsJsonObject();
JsonObject obj = JsonParser.parseString(response).getAsJsonObject();

boolean isSuccess = obj.getAsJsonPrimitive("success").getAsBoolean();
assertTrue(isSuccess);
Expand Down
1 change: 1 addition & 0 deletions java/spacewalk-java.changes.cbbayburt.api-datetime-input
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fix date input in 'errata.setDetails' endpoint in the HTTP API

0 comments on commit 73b24c0

Please sign in to comment.