MultivaluedMap in RESTEasy and square brackets - java

I'm building something of a relay server, using JAX-RS. I need to be able to extract any query parameters from a GET request and then re-wrap them into another request, to pass along to another server. I'm unfamiliar with MultivaluedMap, but I just figured out what is happening. The UriInfo class's getQueryParameters method returns a MultivaluedMap<String, String>. What bit me, unexpectedly, is that the values of each parameter are List values, even though they purport to be String values (by the way I read the JavaDoc).
In other words, if I have a key-value pair of foo=bar, in the URL query string, when I extract the parameter, it comes out as foo=[bar]. This totally throws me for a loop (a 500 Server Error, actually) when I try to re-wrap the parameter and send it along to the other server.
Is there another way to handle unpacking the query string from a Request, and then re-packing it for another Request? I'm including some code to illustrate my issue:
#GET
#Path("parameters")
public Response showParameters(#Context UriInfo uriInfo) {
MultivaluedMap<String, String> parameters = uriInfo.getQueryParameters();
StringBuffer sb = new StringBuffer();
sb.append("<h4>Parameters:</h4>");
if (parameters != null) {
sb.append("<ul>");
Iterator it = parameters.keySet().iterator();
while (it.hasNext()) {
String key = (String)it.next();
sb.append("<li>")
.append(key)
.append(": ")
.append(parameters.get(key))
.append("</li>");
}
sb.append("</ul>");
}
else {
sb.append("<p>None</p>");
}
return Response.ok(sb.toString()).build();
}
So, in summary, what gets printed out from the code above, if the request has query parameters, is something like this:
Parameters:
key1: [value1]
key2: [value2]
key3: [value3]
Is there another way to unpack/re-pack, and maybe avoid this whole issue altogether? Thanks.
Complete answer
#Jack deserves credit for pointing me in the right direction, and I am marking his answer as correct, but here is what I got working.
Client client = ClientBuilder.newClient();
// Assume instance variable providing URI (without query string).
WebTarget target = client.target(theRequestUri);
// Instance variable uriInfo.
MultivaluedMap<String, String> parameters = uriInfo.getQueryParameters();
if (parameters != null && parameters.size() > 0) {
Iterator it = parameters.keySet().iterator();
String key = null;
StringTokenizer st = null;
while (it.hasNext()) {
key = (String)it.next();
// RESTEasy API is quirky, here. It wraps the values in square
// brackets; moreover, each value is separated by a comma and
// padded by a space as well. ([one, two, three, etc])
st = new StringTokenizer(parameters.get(key).toString(), "[,]");
while (st.hasMoreTokens()) {
target = target.queryParam(key, st.nextToken().trim());
}
}
}
// Instance method: getContentType.
Response response = target.request().accept(getContentType()).get();

Its because of MultivaluedMap interface.
public interface MultivaluedMap<K, V> extends Map<K, List<V>> {
It returns the values as List.
Instead of parameters.get(key) try parameters.getFirst(key)
Note that this will drop the other values for the same parameter. It is possible to send multiple values for same parameter while making a rest call such as blahblah:8080?foo=bar1&foo=bar2. With getFirst() call you will get bar1 value only. If you are sure you will not get multiple calls, you can go with getFirst(key) approach
--- UPDATED ---
Based on your comments, it seems you need to iterate over the multivalued map and call queryParam on the WebTarget instance. I understand you are looking for a library/straight forward way to do this. I did not try RESTEasy. But code I believe should be simple enough.
multiValuesMap?.each { key, values->
webTargetInstance = webTargetInstance.queryParam(key, values as Object[])
}

Related

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.");
}

jOOQ - multiple bindings

I have a map of binding values:
final Map<String, Object> values = ...;
Before executing the query, I loop through the binding parameters and bind its value as following:
final ResultQuery<Record> q = ...;
for (final Param p : q.getParams().values()) {
if (p.getParamName() != null) {
q.bind(p.getParamName(), values.get(p.getParamName()));
}
}
When the same binding is used multiple times, this seems to fail:
final ResultQuery<Record> q = create.select().from(DSL.table("my_table"))
.where((DSL.field("identifier").eq(DSL.param("binding"))
.and(DSL.field("identifier").eq(DSL.param("binding")))));
... code above ...
create.fetch(q);
In the generated query, only one of the bindings is filled in. The other is null.
Is there an alternative approach? The same binding parameter can only be used once, or is this a bug?
(I know this query doesn't make much sense, it is only for demonstrating the problem.)
The issue was probably solved if we could do the following instead, but it is not possible since getParams() returns Param<?> and not Param<Object>:
for (final Param p : q.getParams().values()) {
if (p.getParamName() != null) {
p.bind(values.get(p.getParamName()));
}
}
update - The statement above is incorrect, since getParams() returns a Map<String, Param> and not Map<String, Collection<Param>>. It still woud be useful to bind a Param directly though.
This is a bug in jOOQ 3.5.2 (#4056).
Another workaround apart from the one you've found would be to make sure that both instances of "binding" are in fact the same, you should probably externalise the bind value:
Param<Object> binding = DSL.param("binding");
final ResultQuery<Record> q = create.select().from(DSL.table("my_table"))
.where((DSL.field("identifier").eq(binding)
.and(DSL.field("identifier").eq(binding))));
Now, you'll explicitly create a single bind value that is used two times in the AST.

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.

Deadbolt - Play Framework - How to check a #RestrictedResource with parameters in a controller?

With Deadbolt's module we can check the restrictedResource with a ressource name and parameters in the view.
For example in my view, I have it, and it works well:
#{deadbolt.restrictedResource resourceKeys:['Domain'] , resourceParameters:['domainid':domain.id]}
<li>${domain.title}</li>
#{/deadbolt.restrictedResource}
But in my controller, I just can check the ressource name but I don't find a way to check it in my RestrictedResourcesHandler passing the domainid with.
I am looking for a solution to do something like that:
#RestrictedResource(name = {"Domain"}, params = {domainid})
public static void showDomain(String domainid)
{
}
Thanks in advance
It's not possible to have dynamic information in an annotation, but you can use params to define the name of an incoming value in the request. However, this information isn't passed into the handler at the moment because it expects a map. While you can pass in a map of parameters from the restrictedResource tag, you can't do this from an annotation so an empty map is passed into the handler.
Your best approach here is to pull a well-known parameter name from the request object. I need to have a rethink about the best way to do this without breaking backwards compatibility.
Steve (author of Deadbolt)
I've found a way the solved the problem, not the best I think, but it is the Steve Chaloner's solution (Deadbolt's creator), and it works.
For example, if your Controller's method argument is named "id", and you want to check this id inside your checkAccess method :
// Controller's method :
#RestrictedResource(name = {"Domain"})
public static void showDomain(String id){}
Just check at the beginning of your checkAccess method the Map "resourceParameters" is empty, and use the request object to get the parameters:
public AccessResult checkAccess(List<String> resourceNames,
Map<String, String> resourceParameters)
{
Map<String, String> hashm = new HashMap<String,String>();
if(resourceParameters != null && !resourceParameters.isEmpty()){
hashm = resourceParameters;
}else if(Http.Request.current().routeArgs!= null && !Http.Request.current().routeArgs.isEmpty()){
hashm = Http.Request.current().routeArgs;
}
}
Then just have to foreach your hashmap inside your checkAccess method to get your Controller's method argument and check the Access as you wish.
for (Map.Entry<String,String> mp : hashm.entrySet())
{
// Get the id argument
if(mp.getKey().equals("id"))
{
// Do something with the value..
mp.getValue()
}
}

Better pattern/design for this java code?

LinkedIn rest API for people search has following format http://api.linkedin.com/people-search?search-param1=val1&search-param2=val2...
Since I intend to use this many time in my application, I am trying to create a Search object like this where we can set values for different search parameters and then a method called generateQueryUrl to generate the url in above format.
public class Search {
private String searchParam1;
private String searchParam2;
public void setSearchParam1(String val) { this.searchParam1 = val; }
public void setSearchParam2(String val) { this.searchParam2 = val; }
//Form the query url
public String generateQueryUrl(){
String url = "";
if(searchParam1 != null) {
url += "search-param1=" + searchParam1 + "&";
}
if(searchParam2 != null) {
url += "search-param2=" + searchParam2 + "&";
}
return url;
}
My question is, is there a better pattern/design to do this? If there are many parameters, checking for NULL and then appending corresponding parameter name, value seems to add redundant code to me.
Also please let me know if this approach is fine.
I think what you are designing is in fact a "Builder", in that you are building the URL that will be returned at the end. Think StringBuilder, or Apache's EqualsBuilder, HashCodeBuilder, etc.
For a more theoretical explanation, check http://sourcemaking.com/design_patterns/builder.
Now, as for your code, to properly build the URL, I would use "set" methods like you do, but inside them I would use Apache's HttpComponents (ex HttpClient) to properly append the parameters of the URL.
Think about it, if you insert a "&" in the value of one of the parameter, your query parameters will be messed up since you normally have to escape this character (and you don't seem to be doing it here). I prefer letting tested and known APIs (UriUtils) do that for me.
So, my class would look like so using Apache's HttpComponents:
public class SearchBuilder {
private URI baseUri;
private List<NameValuePair> parameters;
public SearchBuilder (URI baseUri) {
this.baseUri= baseUri;
this.parameters = new ArrayList<NameValuePair>();
}
public void addSearchParam1(String val) {
if(!StringUtils.isBlank(val)) {
parameters.add(new BasicNameValuePair("SearchParam1", val));
}
}
//Form the query url
public URI toURI(){
URI uri = URIUtils.createURI(baseUri.getScheme(), baseUri.getHost(), baseUri.getPort(), baseUri.getPath(), URLEncodedUtils.format(parameters, "UTF-8"), null);
return uri;
}
Edit: I added the "isBlank" check to ensure that parameters with null, empty of whitespace-only Strings will not be added to the query. Now, you can change that to check only for null, but I'm sure you got the idea.
You can maintain a Map with keys as the param names and values as the values. Your setter methods can put keys and values in the Map. Or, you can have one generic setter which accepts the name & the value.
In the generateQueryUrl method, you can just iterate over the Map.
You may also want to URLEncode the values if not being done at the later stage.

Categories