Skip to content

Commit a2c66e6

Browse files
FINERACT-2353: openAPI definition does not expose required GET parameters (#5025)
1 parent e1cfbb4 commit a2c66e6

File tree

2 files changed

+220
-58
lines changed

2 files changed

+220
-58
lines changed

fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/RunreportsApiResource.java

Lines changed: 47 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@
3939
import jakarta.ws.rs.core.Response;
4040
import jakarta.ws.rs.core.UriInfo;
4141
import lombok.RequiredArgsConstructor;
42-
import org.apache.commons.lang3.StringUtils;
4342
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
4443
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
4544
import org.apache.fineract.infrastructure.dataqueries.data.ReportExportType;
45+
import org.apache.fineract.infrastructure.dataqueries.data.ReportParameters;
4646
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
4747
import org.apache.fineract.infrastructure.report.provider.ReportingProcessServiceProvider;
4848
import org.apache.fineract.infrastructure.report.service.ReportingProcessService;
@@ -54,7 +54,7 @@
5454

5555
@Path("/v1/runreports")
5656
@Component
57-
@Tag(name = "Run Reports")
57+
@Tag(name = "Run Reports", description = "API for executing predefined reports with dynamic parameters")
5858
@RequiredArgsConstructor
5959
public class RunreportsApiResource {
6060

@@ -68,14 +68,16 @@ public class RunreportsApiResource {
6868
@Path("/availableExports/{reportName}")
6969
@Consumes({ MediaType.APPLICATION_JSON })
7070
@Produces({ MediaType.APPLICATION_JSON })
71-
@Operation(summary = "Return all available export types for the specific report", description = "Returns the list of all available export types.")
71+
@Operation(summary = "Return all available export types for the specific report", description = "Returns the list of all available export types for a given report.")
7272
@ApiResponses({
73-
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ReportExportType.class)))) })
74-
public Response retrieveAllAvailableExports(@PathParam("reportName") @Parameter(description = "reportName") final String reportName,
73+
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ReportExportType.class)))),
74+
@ApiResponse(responseCode = "400", description = "Bad Request - Invalid report name or parameters"),
75+
@ApiResponse(responseCode = "500", description = "Internal Server Error") })
76+
public Response retrieveAllAvailableExports(
77+
@PathParam("reportName") @Parameter(description = "Name of the report to get available export types for", example = "Client Listing", required = true) final String reportName,
7578
@Context final UriInfo uriInfo,
76-
@DefaultValue("false") @QueryParam(IS_SELF_SERVICE_USER_REPORT_PARAMETER) @Parameter(description = IS_SELF_SERVICE_USER_REPORT_PARAMETER) final boolean isSelfServiceUserReport) {
79+
@DefaultValue("false") @QueryParam(IS_SELF_SERVICE_USER_REPORT_PARAMETER) @Parameter(description = "Indicates if this is a self-service user report", example = "false") final boolean isSelfServiceUserReport) {
7780

78-
validateReportName(reportName);
7981
MultivaluedMap<String, String> queryParams = new MultivaluedStringMap();
8082
queryParams.putAll(uriInfo.getQueryParameters());
8183

@@ -93,43 +95,56 @@ public Response retrieveAllAvailableExports(@PathParam("reportName") @Parameter(
9395
@Path("{reportName}")
9496
@Consumes({ MediaType.APPLICATION_JSON })
9597
@Produces({ MediaType.APPLICATION_JSON, "text/csv", "application/vnd.ms-excel", "application/pdf", "text/html" })
96-
@Operation(summary = "Running a Report", description = "This resource allows you to run and receive output from pre-defined Apache Fineract reports.\n"
97-
+ "\n" + "Reports can also be used to provide data for searching and workflow functionality.\n" + "\n"
98-
+ "The default output is a JSON formatted \"Generic Resultset\". The Generic Resultset contains Column Heading as well as Data information. However, you can export to CSV format by simply adding \"&exportCSV=true\" to the end of your URL.\n"
99-
+ "\n"
100-
+ "If Pentaho reports have been pre-defined, they can also be run through this resource. Pentaho reports can return HTML, PDF or CSV formats.\n"
101-
+ "\n"
102-
+ "The Apache Fineract reference application uses a JQuery plugin called stretchy reporting which, itself, uses this reports resource to provide a pretty flexible reporting User Interface (UI).\n\n"
103-
+ "\n" + "\n" + "Example Requests:\n" + "\n" + "runreports/Client%20Listing?R_officeId=1\n" + "\n" + "\n"
104-
+ "runreports/Client%20Listing?R_officeId=1&exportCSV=true\n" + "\n" + "\n"
105-
+ "runreports/OfficeIdSelectOne?R_officeId=1&parameterType=true\n" + "\n" + "\n"
106-
+ "runreports/OfficeIdSelectOne?R_officeId=1&parameterType=true&exportCSV=true\n" + "\n" + "\n"
107-
+ "runreports/Expected%20Payments%20By%20Date%20-%20Formatted?R_endDate=2013-04-30&R_loanOfficerId=-1&R_officeId=1&R_startDate=2013-04-16&output-type=HTML&R_officeId=1\n"
108-
+ "\n" + "\n"
109-
+ "runreports/Expected%20Payments%20By%20Date%20-%20Formatted?R_endDate=2013-04-30&R_loanOfficerId=-1&R_officeId=1&R_startDate=2013-04-16&output-type=XLS&R_officeId=1\n"
110-
+ "\n" + "\n"
111-
+ "runreports/Expected%20Payments%20By%20Date%20-%20Formatted?R_endDate=2013-04-30&R_loanOfficerId=-1&R_officeId=1&R_startDate=2013-04-16&output-type=CSV&R_officeId=1\n"
112-
+ "\n" + "\n"
113-
+ "runreports/Expected%20Payments%20By%20Date%20-%20Formatted?R_endDate=2013-04-30&R_loanOfficerId=-1&R_officeId=1&R_startDate=2013-04-16&output-type=PDF&R_officeId=1")
98+
@Operation(summary = "Run a predefined report", description = ReportParameters.FULL_DESCRIPTION)
11499
@ApiResponses({
115-
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RunreportsApiResourceSwagger.RunReportsResponse.class))) })
116-
public Response runReport(@PathParam("reportName") @Parameter(description = "reportName") final String reportName,
100+
@ApiResponse(responseCode = "200", description = "OK - Report executed successfully", content = @Content(schema = @Schema(implementation = RunreportsApiResourceSwagger.RunReportsResponse.class))),
101+
@ApiResponse(responseCode = "400", description = "Bad Request - Missing or invalid parameters"),
102+
@ApiResponse(responseCode = "401", description = "Unauthorized - Not authorized to run this report"),
103+
@ApiResponse(responseCode = "500", description = "Internal Server Error") })
104+
public Response runReport(
105+
@PathParam("reportName") @Parameter(description = "The name of the report to execute (e.g., 'Client Listing', 'Expected Payments By Date')", example = "Client Listing", required = true) final String reportName,
117106
@Context final UriInfo uriInfo,
118-
@DefaultValue("false") @QueryParam(IS_SELF_SERVICE_USER_REPORT_PARAMETER) @Parameter(description = IS_SELF_SERVICE_USER_REPORT_PARAMETER) final boolean isSelfServiceUserReport) {
119107

120-
validateReportName(reportName);
108+
@DefaultValue("false") @QueryParam(IS_SELF_SERVICE_USER_REPORT_PARAMETER) @Parameter(description = "Whether this is a self-service user report", example = "false") final boolean isSelfServiceUserReport,
121109

110+
@DefaultValue("false") @QueryParam("exportCSV") @Parameter(description = "Set to true to export results as CSV", example = "false") final Boolean exportCSV,
111+
112+
@DefaultValue("false") @QueryParam("parameterType") @Parameter(description = "Indicates if this is a parameter type request", example = "false") final Boolean parameterType,
113+
114+
@QueryParam("output-type") @Parameter(description = "Output format type (HTML, XLS, CSV, PDF)", example = "HTML") final String outputType,
115+
116+
@QueryParam("R_officeId") @Parameter(description = "Office ID filter", example = "1") final String rOfficeId,
117+
118+
@QueryParam("R_loanOfficerId") @Parameter(description = "Loan officer ID filter", example = "5") final String rLoanOfficerId,
119+
120+
@QueryParam("R_fromDate") @Parameter(description = "Start date filter (yyyy-MM-dd)", example = "2023-01-01") final String rFromDate,
121+
122+
@QueryParam("R_toDate") @Parameter(description = "End date filter (yyyy-MM-dd)", example = "2023-12-31") final String rToDate,
123+
124+
@QueryParam("R_currencyId") @Parameter(description = "Currency ID filter", example = "USD") final String rCurrencyId,
125+
126+
@QueryParam("R_accountNo") @Parameter(description = "Account number filter", example = "00010001") final String rAccountNo) {
127+
128+
return processReportRequest(reportName, uriInfo, isSelfServiceUserReport);
129+
}
130+
131+
public Response runReport(final String reportName, final UriInfo uriInfo, final boolean isSelfServiceUserReport) {
132+
133+
return processReportRequest(reportName, uriInfo, isSelfServiceUserReport);
134+
}
135+
136+
private Response processReportRequest(final String reportName, final UriInfo uriInfo, final boolean isSelfServiceUserReport) {
122137
MultivaluedMap<String, String> queryParams = new MultivaluedStringMap();
123138
queryParams.putAll(uriInfo.getQueryParameters());
124139

125-
final boolean parameterType = ApiParameterHelper.parameterType(queryParams);
140+
final boolean parameterTypeValue = ApiParameterHelper.parameterType(queryParams);
126141

127-
checkUserPermissionForReport(reportName, parameterType);
142+
checkUserPermissionForReport(reportName, parameterTypeValue);
128143

129144
// Pass through isSelfServiceUserReport so that ReportingProcessService implementations can use it
130145
queryParams.putSingle(IS_SELF_SERVICE_USER_REPORT_PARAMETER, Boolean.toString(isSelfServiceUserReport));
131146

132-
String reportType = readExtraDataAndReportingService.getReportType(reportName, isSelfServiceUserReport, parameterType);
147+
String reportType = readExtraDataAndReportingService.getReportType(reportName, isSelfServiceUserReport, parameterTypeValue);
133148
ReportingProcessService reportingProcessService = reportingProcessServiceProvider.findReportingProcessService(reportType);
134149
if (reportingProcessService == null) {
135150
throw new PlatformServiceUnavailableException("err.msg.report.service.implementation.missing",
@@ -148,30 +163,4 @@ private void checkUserPermissionForReport(final String reportName, final boolean
148163
}
149164
}
150165
}
151-
152-
/**
153-
* Validates report name to prevent SQL injection attacks.
154-
*
155-
* @param reportName
156-
* the report name to validate
157-
* @throws IllegalArgumentException
158-
* if the report name is invalid
159-
*/
160-
private void validateReportName(String reportName) {
161-
if (StringUtils.isBlank(reportName)) {
162-
throw new IllegalArgumentException("Report name cannot be null or empty");
163-
}
164-
165-
// Basic length validation
166-
if (reportName.length() > 100) {
167-
throw new IllegalArgumentException("Report name exceeds maximum length of 100 characters");
168-
}
169-
170-
// Check for potentially dangerous characters
171-
// Allow letters, numbers, spaces, hyphens, underscores, and parentheses which are common in report names
172-
if (!reportName.matches("^[a-zA-Z0-9\\s\\-_()%&.]+$")) {
173-
throw new IllegalArgumentException(
174-
"Report name contains invalid characters. Only letters, numbers, spaces, hyphens, underscores, parentheses, percent, ampersand, and dots are allowed");
175-
}
176-
}
177166
}

0 commit comments

Comments
 (0)