-
Notifications
You must be signed in to change notification settings - Fork 75
fix: add validation for cron in page source during import to prevent malformed cron expressions #12324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 4.5.x
Are you sure you want to change the base?
Conversation
APIM UI Tests
|
Project |
APIM UI Tests
|
Branch Review |
APIM-9446-fix-cron-check-api-import
|
Run status |
|
Run duration | 06m 51s |
Commit |
|
Committer | vikrant |
View all properties for this run ↗︎ |
Test results | |
---|---|
|
0
|
|
0
|
|
8
|
|
0
|
|
82
|
View all changes introduced in this branch ↗︎ |
fedd86f
to
2b061d0
Compare
for (JsonNode pageNode : apiJsonNode.getPagesArray()) { | ||
JsonNode sourceNode = pageNode.path("source"); | ||
JsonNode configNode = sourceNode.path("configuration"); | ||
|
||
if (!configNode.isMissingNode() && configNode.has("fetchCron")) { | ||
String cron = configNode.path("fetchCron").asText(null); | ||
|
||
if (cron != null && !cron.isEmpty()) { | ||
try { | ||
CronExpression.parse(cron); // Validate cron | ||
} catch (IllegalArgumentException e) { | ||
String pageName = pageNode.path("name").asText("Unnamed Page"); | ||
throw new ApiImportException("Invalid fetchCron expression in page '" + pageName + "': " + e.getMessage()); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be run whenever a page is created or modified?
I wonder if there is a more central page validation service to add this to? 🤔
public static void validateFetchConfig(String jsonConfig) { | ||
ObjectMapper mapper = new ObjectMapper(); | ||
try { | ||
JsonNode root = mapper.readTree(jsonConfig); | ||
boolean autoFetch = root.path("autoFetch").asBoolean(false); | ||
if (autoFetch) { | ||
String fetchCron = root.path("fetchCron").asText(); | ||
if (fetchCron == null || fetchCron.isEmpty()) { | ||
throw new IllegalArgumentException("fetchCron is required when autoFetch is true."); | ||
} | ||
// Throws IllegalArgumentException if cron expression is invalid | ||
CronExpression.parse(fetchCron); | ||
} | ||
} catch (IllegalArgumentException e) { | ||
throw new IllegalArgumentException("Invalid cron expression: " + e.getMessage(), e); | ||
} catch (Exception e) { | ||
throw new RuntimeException("Failed to parse configuration JSON: " + e.getMessage(), e); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this could throw a gravitee exception and add the exception to the signature?
I believe we try to avoid throwing RuntimeExceptions 🤔
d7e8cd9
to
187b1c3
Compare
187b1c3
to
50e25f1
Compare
…malformed cron expressions
50e25f1
to
b205cee
Compare
public static void validateFetchConfig(String jsonConfig) throws TechnicalException { | ||
ObjectMapper mapper = new ObjectMapper(); | ||
try { | ||
JsonNode root = mapper.readTree(jsonConfig); | ||
boolean autoFetch = root.path("autoFetch").asBoolean(false); | ||
String fetchCron = root.path("fetchCron").asText(); | ||
if (autoFetch) { | ||
if (fetchCron == null || fetchCron.isEmpty()) { | ||
throw new TechnicalException("fetchCron is required when autoFetch is true."); | ||
} | ||
} | ||
if (fetchCron != null && !fetchCron.isEmpty() && !"null".equals(fetchCron)) { | ||
CronExpression.parse(fetchCron); | ||
} | ||
} catch (IllegalArgumentException e) { | ||
throw new TechnicalException("Invalid cron expression: " + e.getMessage(), e); | ||
} catch (Exception e) { | ||
throw new TechnicalException("Failed to parse configuration JSON: " + e.getMessage(), e); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two things that I suggest improving in this method:
- Not to throw an exception, catch it, then rethrow it
- Using exceptions in
io.gravitee.rest.api.service.exceptions;
instead ofio.gravitee.repository.exceptions;
to have the desired impact when the user calls the resource
By throwing a TechnicalException that is specific to repository errors, the user will receive a 500 error if their CRON is invalid. This should probably be a 400. You can create a new exception PageCronExpressionInvalidException
(or however you want to name it) that extends AbstractManagementException
and returns HttpStatusCode.BAD_REQUEST_400
for getHttpCode.
A suggested refactoring:
public static void validateFetchConfig(String jsonConfig) throws TechnicalException { | |
ObjectMapper mapper = new ObjectMapper(); | |
try { | |
JsonNode root = mapper.readTree(jsonConfig); | |
boolean autoFetch = root.path("autoFetch").asBoolean(false); | |
String fetchCron = root.path("fetchCron").asText(); | |
if (autoFetch) { | |
if (fetchCron == null || fetchCron.isEmpty()) { | |
throw new TechnicalException("fetchCron is required when autoFetch is true."); | |
} | |
} | |
if (fetchCron != null && !fetchCron.isEmpty() && !"null".equals(fetchCron)) { | |
CronExpression.parse(fetchCron); | |
} | |
} catch (IllegalArgumentException e) { | |
throw new TechnicalException("Invalid cron expression: " + e.getMessage(), e); | |
} catch (Exception e) { | |
throw new TechnicalException("Failed to parse configuration JSON: " + e.getMessage(), e); | |
} | |
} | |
public static void validateFetchConfig(String jsonConfig) throws PageCronExpressionInvalidException { | |
ObjectMapper mapper = new ObjectMapper(); | |
JsonNode root; | |
try { | |
root = mapper.readTree(jsonConfig); | |
} catch (IOException e) { | |
throw new PageCronExpressionInvalidException("Invalid JSON format: " + e.getMessage(), e); | |
} | |
boolean autoFetch = root.path("autoFetch").asBoolean(false); | |
String fetchCron = root.path("fetchCron").asText(null); // default to null | |
if (autoFetch && (fetchCron == null || fetchCron.trim().isEmpty() || "null".equalsIgnoreCase(fetchCron))) { | |
throw new PageCronExpressionInvalidException("fetchCron is required when autoFetch is true."); | |
} | |
if (fetchCron != null && !fetchCron.trim().isEmpty() && !"null".equalsIgnoreCase(fetchCron)) { | |
try { | |
CronExpression.parse(fetchCron); | |
} catch (IllegalArgumentException e) { | |
throw new PageCronExpressionInvalidException("Invalid cron expression: " + e.getMessage(), e); | |
} | |
} | |
} | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can probably make a cleaner solution that what I proposed...
Issue
https://gravitee.atlassian.net/browse/APIM-9446
Description
This commit adds validation logic for the cron expression field in page sources (e.g., GitHub fetcher). Previously, malformed cron expressions (e.g., with only 5 fields) were silently accepted during import or upgrade, leading to inconsistencies in behavior.
Issue:
APIM-9446.mov
Fix:
Fix_APIM-9446.mov
Additional context
📚 View the storybook of this branch here