Validating Query Params in REST API - java

I have a REST API which accepts query parameters. The query parameters are valid if and only if at a time only one query parameter is passed and it is among the list of valid query parameters.
Currently my logic for this is:
I am collecting the query params in a map. And then checking it's size. If size > 1 function throwing an error. If that is not the case then iterating through the map and if find a parameter other than valid ones the function throwing an error.
For example:
if(queryParam.size()>1) {
throw new FailureResponse();
}
queryParam.forEach(e->{
String key = e.getKey();
if(!key.equalsIgnoreCase("p1") && !key.equalsIgnoreCase("p2")) {
throw new FailureResponse();
}
});
But I think in this way I am violating the SOLID design principle which says a class should be open for extension but closed for modification.
I also thought of creating a file and then reading the acceptable params from it but that would add to the response time of the API as it involves reading a file.
Is there some way I can keep and read the valid query-params and it does not violate the design principles?

You could maintain an Enum of valid params and extend the enums as and when applicable like
public enum QueryParams{
PARAM_1("param1"),
PARAM_2("param2"),
private String paramValue;
QueryParams(String paramName){
this.paramValue = paramValue();
}
public void getParamValue(){
return this.value;
}
}
and then you could iterate over the set of values of this enum to filter out invalid values
List<String> validParams = Arrays.asList(QueryParams.values()).stream().map(QueryParams::getParamValue).collect(Collectors.toList());
queryParams.removeAll(validParams);
if(queryParams.size()!=0) {
throw new FailureResponse();
}
}
This helps you maintain the API class without any changes, whenever a new parameter is added, just extend the enum and all the rest is automatically extended as it all depends upon the value in the enum.

Related

Why Architecture rule Violation exception is coming if I change the passing parameter of the method?

I am having very weird situation here , In my project we have Architecture Rules Test. but this below method did not throw any error msg , but when I just change a method passing meter from String to Query and change the first line of the method then it is throwing
Architecture Violation [Priority: MEDIUM] - Rule exception, (the rule is set to check if we are importing any third party library class in our specific class which for biddable and we have written IT for the same so no one should use third part library lets say classes from com.google.* here since I am using here ArrayListMultimap from com.google.common.. so this is failing , but the real question here is why it is happening when I am changing the parameter and why it was working fine earlier , this rule is breaking after I made changes in just parameter I did not add the class in ArrayListMultimap in this map and error is coming like I am using ArrayListMultimap .create() method ehich is breaking the rule )
Because the package this method is present(let's package P) is not supposed to depend on the classes present in packages ['com.google.common..', 'com.google.thirdparty..']'.
And ArrayListMultimap<String, Integer> xAndYMap = ArrayListMultimap.create(); this from google package. Here my doubt is why it is working if I dont change the method passing parameter type (first it was String then I changed from String -> Query).
I am not able to resolve it. What am I supposed to do here , is this happening because I changed function def by changing passing parameter? or if it is so why it was working and building the project successfully.
Could anyone please help here to understand the situation ?
Thanks in advance:)
the Method is given below:
private Map<CONSTANT_X, List<Integer>> getAllOfCaseCountGroupMap(final Query query) {
List<Object[]> results = this.getQueryResultsAsList(query);
Map<CONSTANT_X, List<Integer>> result = new HashMap<>();
ArrayListMultimap<String, Integer> xAndYMap = ArrayListMultimap.create();
for (Object[] objs : results) {
CONSTANT_X x = CONSTANT_X.valueOf(objs[0].toString());
Integer caseId = (Integer) objs[1];
xAndYMap.put(x.toString(), caseId);
}
for (String key : xAndYMap.keys()) {
CONSTANT_X x = CONSTANT_X.valueOf(key);
List<Integer> y = xAndYMap.get(key);
result.put(x, y);
}
return result;
}`enter code here`

How do I accept multiple optional parameters in a Spring request mapping, but only one at a time?

Edit: I ended up solving the problem myself through more experimentation. The code seems quite verbose though so there is probably a better solution that doesn't involve typecasting strings to other things.
Answer posted below.
For my school work, I am supposed to create a GET mapping to receive a list of all entities of a specific type. This GET mapping should return simply all the entities if no parameter is provided, or otherwise it will apply something in the entity repository to use a JPQL query and the provided parameter which is used as an ordinal query parameter to filter the returned results.
"If no request parameter is provided, the existing functionality of returning all events
should be retained.
If more than one request parameter is specified, an error response should be
returned, indicating that at most one parameter can be provided.
If the ‘?status=XXX’ parameter is provided, with a status string value that does not
match the AEventStatus enumeration, an appropriate error response should be
returned."
I have tried to alter my GET mapping to have 3 optional #RequestParameter variables, but I found out that it is tedious logic-wise to check for the existence of multiple or no parameters, and then do something again based on the existence of which parameter is there.
Instead I tried this (I was in the middle of this and it is not complete):
#RequestMapping(
value="/aevents",
method = RequestMethod.GET)
public ResponseEntity<Object> getAllAEvents(HttpServletRequest request) {
if (request.getParameterMap().size() == 0) {
return ResponseEntity.status(HttpStatus.OK).body(repository.findAll());
}
if (request.getParameterMap().size() > 1) {
return new ResponseEntity<>("Can only handle one request paramter: title=, status= or minRegistrastions=", HttpStatus.BAD_REQUEST);
}
//incomplete from here
}
And I am now not sure if this is the correct approach or if I am simply overlooking something. I suppose I might be able to check for the names of the parameters that were provided in the request and then return a bad request response again if I find something that isn't a valid parameter. But I am not sure how to check the parameter map for the names of the parameters or if the parameter map even does what I think it does.
The parameters provided are supposed to be either an int, a value from an enum or a string.
Am I overlooking a simpler way to do this? i.e. a way to check the amount of parameters and the existence of parameters in a signature like:
#GetMapping("/aevents")
public List<AEvent> getAllAEvents(#RequestParam(required = false) String title,
#RequestParam(required = false) AEventStatus status,
#RequestParam(required = false) int minRegistrations) {
//Do something here
}
Or is my current approach feasible, and if it is, how do I continue on it?
Yes, you would likely do it your way, though:
You can inject the map of parameters directly in Spring.
Throw a ResponseStatusException (available since Spring 5) instead of fumbling around with the ResponseEntity.
#GetMapping("/aevents")
public List<AEvent> getAllAEvents( #RequestParam Map<String, String> allRequestParams){
if(allRequestParams.size() >1){
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST,"too many params");
}
// do something
return list;
}
Answer to my own question after solving it:
What is the parameter map?
The parameterMap is actually a map of string keys and string arrays. To get the value for a parameter name (the key) you can get values of this key and then access it like an array.
However, using the parameterMap was not necessary. Instead it was better to just use the built-in Spring way of doing it which is by using the #RequestParam annotation with simply a #RequestParam Map<String, String> params in the method body.
Credits to https://stackoverflow.com/users/2255293/marco-behler for giving me an idea as well as providing a better way to throw exceptions.
#GetMapping("/aevents")
public List<AEvent> getAllAEvents(#RequestParam Map<String, String> params) {
if (params.size() == 0) { //Default case, no params
return repository.findAll();
}
if (params.size() > 1) { //Refuse to handle more than one provided param
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Can only handle one request parameter: title=, status= or minRegistrations=");
}
if (params.containsKey("title")) {
String value = params.get("title");
return repository.findByQuery("AEvent_find_by_title", ("%" + value + "%"));
}
if (params.containsKey("status")) {
String stringValue = params.get("status").toUpperCase();
for (AEventStatus e : AEventStatus.values()) {
if (e.name().equals(stringValue)) {
AEventStatus value = AEventStatus.valueOf(stringValue);
return repository.findByQuery("AEvent_find_by_status", value);
}
}
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "status=" + stringValue + " is not a valid AEvent status value.");
}
if (params.containsKey("minRegistrations")) {
int value;
try {
value = Integer.parseInt(params.get("minRegistrations"));
} catch (NumberFormatException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Provided request parameter was not a valid number.");
}
return repository.findByQuery("AEvent_find_by_minRegistrations", value);
}
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid query parameters.");
}

Get Field value from entity using DTO

I have an app with several #MappedSuperClasses. Out of one of them I need to write a csv with columns in a very particular order stablished by the client.
Doing a Entity.class.getDeclaredFields() used to be enough to retrieve and write the columns in the right order before we had superclasses, but now, even if I use a custom solution to iterate through the superclasses's fields the order is incorrect, so I resorted to using a DTO Entity which returns the right order when calling getDeclaredFields().
The problems come when I try to retrieve the values present in the entities related, we used to do something like:
Object value = getLineFromField(field, line);
Where getLineFromField() method would be like:
private Object getLineFromField(Field field, Entity line) {
Object value = null;
try {
value = field.get(line);
} catch (Exception e) {
LOG.info("There is no value. Adding a WhiteSpace to the Column Value");
}
return value;
}
The problem appears in the field.get(line), this method from the Field library will always return a null value
Any experience out there doing a similar mapping?
Just trying to avoid writing a super-ugly 100-liner switch case in the codebase...
EDIT to add internal exception I get from the Field library: UnsafeObjectFieldAccessorImpl

Java Jersey REST Request Parameter Sanitation

I'm trying to make sure my Jersey request parameters are sanitized.
When processing a Jersey GET request, do I need to filter non String types?
For example, if the parameter submitted is an integer are both option 1 (getIntData) and option 2 (getStringData) hacker safe? What about a JSON PUT request, is my ESAPI implementation enough, or do I need to validate each data parameter after it is mapped? Could it be validated before it is mapped?
Jersey Rest Example Class:
public class RestExample {
//Option 1 Submit data as an Integer
//Jersey throws an internal server error if the type is not Integer
//Is that a valid way to validate the data?
//Integer Data, not filtered
#Path("/data/int/{data}/")
#GET
#Produces(MediaType.TEXT_HTML)
public Response getIntData(#PathParam("data") Integer data){
return Response.ok("You entered:" + data).build();
}
//Option 2 Submit data as a String, then validate it and cast it to an Integer
//String Data, filtered
#Path("/data/string/{data}/")
#GET
#Produces(MediaType.TEXT_HTML)
public Response getStringData(#PathParam("data") String data) {
data = ESAPI.encoder().canonicalize(data);
if (ESAPI.validator().isValidInteger("data", data, 0, 999999, false))
{
int intData = Integer.parseInt(data);
return Response.ok("You entered:" + intData).build();
}
return Response.status(404).entity("404 Not Found").build();
}
//JSON data, HTML encoded
#Path("/post/{requestid}")
#POST
#Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON})
#Produces(MediaType.TEXT_HTML)
public Response postData(String json) {
json = ESAPI.encoder().canonicalize(json);
json = ESAPI.encoder().encodeForHTML(json);
//Is there a way to iterate through each JSON KeyValue and filter here?
ObjectMapper mapper = new ObjectMapper();
DataMap dm = new DataMap();
try {
dm = mapper.readValue(json, DataMap.class);
} catch (Exception e) {
e.printStackTrace();
}
//Do we need to validate each DataMap object value and is there a dynamic way to do it?
if (ESAPI.validator().isValidInput("strData", dm.strData, "HTTPParameterValue", 25, false, true))
{
//Is Integer validation needed or will the thrown exception be good enough?
return Response.ok("You entered:" + dm.strData + " and " + dm.intData).build();
}
return Response.status(404).entity("404 Not Found").build();
}
}
Data Map Class:
public class DataMap {
public DataMap(){}
String strData;
Integer intData;
}
The short answer is yes, though by "filter" I interpret it as "validate," because no amount of "filtering" will EVER provide you with SAFE data. You can still run into integer overflows in Java, and while those may not have immediate security concerns, they could still put parts of your application in an unplanned for state, and hacking is all about perturbing the system in ways you can control.
You packed waaaaay too many questions into one "question," but here we go:
First off, the lines
json = ESAPI.encoder().canonicalize(json);
json = ESAPI.encoder().encodeForHTML(json);
Aren't doing what you think they're doing. If your JSON is coming in as a raw String right here, these two calls are going to be applying mass rules across the entire string, when you really need to handle these with more surgical precision, which you seem to at least be subconsciously aware of in the next question.
//Is there a way to iterate through each JSON KeyValue and filter
here?
Partial duplicate of this question.
While you're in the loop discussed here, you can perform any data transformations you want, but what you should really be considering is using the JSONObject class referenced in that first link. Then you'll have JSON parsed into an object where you'll have better access to JSON key/value pairs.
//Do we need to validate each DataMap object value and is there a
dynamic way to do it?
Yes, we validate everything that comes from a user. All users are assumed to be trained hackers, and smarter than you. However if you handled filtering before you do your data mapping transformation, you don't need to do it a second time. Doing it dynamically?
Something like:
JSONObject json = new JSONObject(s);
Iterator iterator = json.keys();
while( iterator.hasNext() ){
String data = iterator.next();
//filter and or business logic
}
^^That syntax is skipping typechecks but it should get you where you need to go.
/Is Integer validation needed or will the thrown exception be good
enough?
I don't see where you're throwing an exception with these lines of code:
if (ESAPI.validator().isValidInput("strData", dm.strData, "HTTPParameterValue", 25, false, true))
{
//Is Integer validation needed or will the thrown exception be good enough?
return Response.ok("You entered:" + dm.strData + " and " + dm.intData).build();
}
Firstly, in java we have autoboxing which means this:
int foo = 555555;
String bar = "";
//the code
foo + bar;
Will be cast to a string in any instance. The compiler will promote the int to an Integer and then silently call the Integer.toString() method. Also, in your Response.ok( String ); call, THIS is where you're going to want to encodeForHTML or whatever the output context may be. Encoding methods are ALWAYS For outputting data to user, whereas canonicalize you want to call when receiving data. Finally, in this segment of code we also have an error where you're assuming that you're dealing with an HTTPParameter. NOT at this point in the code. You'll validate http Parameters in instances where you're calling request.getParameter("id"): where id isn't a large blob of data like an entire JSON response or an entire XML response. At this point you should be validating for things like "SafeString"
Usually there are parsing libraries in Java that can at least get you to the level of Java objects, but on the validation side you're always going to be running through every item and punting whatever might be malicious.
As a final note, while coding, keep these principles in mind your code will be cleaner and your thought process much more focused:
user input is NEVER safe. (Yes, even if you've run it through an XSS filter.)
Use validate and canonicalize methods whenever RECEIVING data, and encode methods whenever transferring data to a different context, where context is defined as "Html field. Http attribute. Javascript input, etc...)
Instead of using the method isValidInput() I'd suggest using getValidInput() because it will call canonicalize for you, making you have to provide one less call.
Encode ANY time your data is going to be passed to another dynamic language, like SQL, groovy, Perl, or javascript.

How do I control input to my method?

I have a search method to retrieve records from database and I want to restrict the user to only search by name. I have a SearchBy enum with list of search parameters and for particular SearchBy methods the user can only search by certain values.
public List<Book> getBooks(SearchBy identifierName,List<String> identifierList) throws UnsupportedOperationException{
List<Book> resultList = new ArrayList<Book>();
if (identifierName.equals(SearchBy.TITLE)) {
//returns list of BookObjects
} else if (identifierName.equals(SearchBy.AUTHOR)) {
//returns list of BookObjects
} else {
throw new UnsupportedOperationException("Books can be retrieved only using book titles or author names");
}
}
Instead of validating and throwing an exception, how can we make it clear that only the values TITLE and AUTHOR are allowed as input for identifier names?
I have not used it but this framework makes some sense for your requirement:
Java Argument Validation
An easy way for checking the preconditions of (public) methods and
constructors. All arguments can be checked, before an
IllegalArgumentException is thrown. Creating consistent messages for
inconveniences in argument values.
It's hard to tell what you mean, but if you are trying to validate input to a method it's common to throw an IllegalArgumentException for bad inputs and the client code can then handle this as they desire.
You would typically do something like this to validate input to a method:
public void method(String name) throws InvalidArgumentException {
if (isInvalid(name)) {
throw new IllegalArgumentException("The name is invalid");
}
else {
// rest of method ...
}
}
It's up to you to decide how to validate the actual name depending on the rules you want to enforce. You can then give a suitable message in the exception to explain why it might not have been valid. Perhaps a regex could be used for the validation code, but without knowing the validation requirements it's impossible to suggest one here.

Categories