I have a simple post method that accepts temperature and checks is the temperature is greater than or equals to 37.4 and returns a response as cleared or not cleared.
#PostMapping(value = "/temperature")
public ResponseEntity<?> Temperature(#Valid #RequestBody Activation activation) throws UnsupportedEncodingException {
Temperature temperature = new Temperature();
temperature.setStatus(activation.getStatus());
temperature.setTemperature(activation.getTemp());
if (db_activation.getCode().equals(code)) {
temperature.setSource("VENUE");
temperatureRepository.save(temperature);
return ResponseEntity.ok(activation.getTemp() < 37.4 ? "Cleared" : "Not cleared");
}
}
how can I insert the response to Status column (which can be cleared or not cleared) to the database and will it be a post request or a put? Please suggest how to do that
To set the status, you can use something like below,
temperature.setStatus(activation.getTemp() < 37.4 ? "Cleared" : "Not cleared")
If you want to create the resource, POST should be the method, for updates, PUT should be used.
Choosing the right request type is just a matter of convention. Technically you could perform any operation with any type of request. By convention: POST sends some fresh data tonthe server, PUT sends an update of the server's data. So if it's a persisted data, POST would be "add" and PUT would be "update".
You could do it in the same method, like so:
if (activation.getTemp()< 37.4) {
temperature.setStatus("Cleared");
} else {
temperature.setStatus("Not cleared");
}
instead of this line:
temperature.setStatus(activation.getStatus());
Related
I am trying to update a resource in DB via #PutMapping (org.springframework.web.bind.annotation.PutMapping) by calling a stored procedure via (java.sql.PreparedStatement.executeUpdate()). This step is working absolutely fine and I am getting below integer response from DB:
1 - 1 row updated
0 - No row updated
I want to send the response back to the user on the basis of integer returned from the DB, for that I am returning ResponseEntity<UpdateResponseVO> from the rest controller, where UpdateResponseVO is normal POJO class:
public class UpdateResponseVO {
private String responseCode;
private String message;
}
I am framing the ResponseEntity on the basis of below condition:
UpdateResponseVO apiResponse = new UpdateResponseVO();
if (row > 0) {
apiResponse.setResponseCode("200");
apiResponse.setMessage("Updated Successfully.");
return new ResponseEntity<UpdateResponseVO>(apiResponse, HttpStatus.OK);
}
else {
apiResponse.setResponseCode(???);
apiResponse.setMessage("No row updated.");
return new ResponseEntity<UpdateResponseVO>(apiResponse, ???);
}
Now my question is what should be my response code when 0 row gets updated (this condition arises when we are not aware of the current value to be updated in the DB and we give the same value. For e.g in DB already student name is "abc" and we want to update it to "abc" again. Hence row updated will be 0) and what HttpStatus I should send along with the ResponseEntity.
I am not sure where you are handling exception when receiving wrong data, Recommended way to use #Validated and validate your request DTO receiving in your controller body. Use #ControllerAdvice for globally handling exception.
Now, Specifically you should return HttpStatus.FORBIDDEN i.e 403 as this is data validation error. In your case you check if row==0 simple ,In general if there is any processing error then respond back with 500. I personally prefer generic response in Success/Error case with HttpStatus.OK with the use of ResponseEntity<?> where you can return any object in response.
I have an endpoint I created using spring.io. My GetMapping declaration can be seen below
#ApiOperation(
value = "Returns a pageable list of CustomerInvoiceProducts for an array of CustomerInvoices.",
notes = "Must be authenticated.")
#EmptyNotFound
#GetMapping({
"customers/{customerId}/getProductsForInvoices/{invoiceIds}"
})
public Page<CustomerInvoiceProduct> getProductsForInvoices(
#PathVariable(required = false) Long customerId,
#PathVariable String[] invoiceIds,
Pageable pageInfo) {
//Do something fun here
for (string i: invoiceIds){
//invoiceIds is always empty
}
}
Here is how I am calling the url from postman and passing the data.
http://localhost:8030/api/v1/customers/4499/getProductsForInvoices/invoiceIds/
{
"invoiceIds": [
"123456",
"234566",
"343939"
]
}
My string array for invoiceIds is always empty in the for loop Nothing gets passed to the array. What am I doing wrong?
The mapping you are using is this:
customers/{customerId}/getProductsForInvoices/{invoiceIds}
Both customerId and invoiceIds are Path variables here.
http://localhost:8030/api/v1/customers/4499/getProductsForInvoices/invoiceIds/
The call you are making contains customerId but no invoiceIds. Either you can pass the list in place of invoiceIds as String and read it as a String and then create a List by breaking up the List - which will be a bad practice.
Other way is to change your path variable - invoiceId to RequestBody.
Generally, Path Variables are used for single id or say navigating through some structured data. When you want to deal in a group of ids, the recommended practice would be to pass them as RequestBody in a Post method call rather than a Get method call.
Sample code snippet for REST API (post calls):
Here, say you are trying to pass Employee object to the POST call, the REST API will look like something below
#PostMapping("/employees")
Employee newEmployee(#RequestBody Employee newEmployee) {
//.. perform some operation on newEmployee
}
This link will give you a better understanding of using RequestBody and PathVariables -
https://javarevisited.blogspot.com/2017/10/differences-between-requestparam-and-pathvariable-annotations-spring-mvc.html
https://spring.io/guides/tutorials/rest/
I have a simple controller method that calls a spring data repository and returns a few objects.
#RequestMapping(value="/api/learnitemlists", method=RequestMethod.GET, produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Iterable<LearnItemList>> getLearnItemLists(#RequestParam(value="fromLanguages") Optional<List<String>> fromLanguages,
#RequestParam(value="toLanguage") Optional<String> toLanguage,
#RequestParam("pagenumber") Integer pageNumber,
#RequestParam("pagesize") Integer pageSize) {
LOGGER.debug("start of learnItemLists call");
Page<LearnItemList> lists;
lists = learnItemListRepositoryCustom.findBasedOnLanguage(fromLanguages,toLanguage,new PageRequest(pageNumber,pageSize));
HttpHeaders headers = new HttpHeaders();
headers.add("X-total-count", Long.toString(lists.getTotalElements()));
headers.add("Access-Control-Expose-Headers", "X-total-count");
ResponseEntity<Iterable<LearnItemList>> result = new ResponseEntity<Iterable<LearnItemList>>(lists,headers,HttpStatus.OK);
LOGGER.debug("end of learnItemLists call");
return result;
}
I logged the beginning and the end of the method call:
22:06:11.914 - 22:06:12.541
So the actual retrieval of objects from the database is well under 1 second. However, the full request took about 2.68 when trying in a browser (integration tests show similar performance).
I can't help but think that something is off. Can serialization into JSON (I'm using Jackson) take this long? The whole JSON response is about 1 kb...
So is this normal (I doubt it), and if not, what steps should I take to find out the cause?
I have the following Java REST method I implemented using Jersey:
#POST
#Path("copy")
public List<Integer> copyCompanionTextRule(#QueryParam("ruleid") List<Integer> ruleIdList,
#QueryParam("workgroupid") List<WorkgroupId> workgroupIds,
#Context HttpHeaders hh)
throws ETMSException
{
List<Integer> insertedItems = new ArrayList<Integer>();
if ( null != ruleIdList ){
for(Integer ruleId : ruleIdList) {
insertedItems.addAll(copyCompanionTextRule(ruleId, workgroupIds));
}
}
return insertedItems;
}
It receives a list of integer and a list of objects of type WorkgroupId as well as the context for some extra processing I'll do later.
I'm working the client with Sencha EXTJS 4.2 and my request is being performed this way:
Ext.Ajax.request({
url: '/sysadmin/companiontextrules/copy',
method: 'POST',
showException: true,
scope: this,
params: {
ruleid: Ext.encode(ruleIdsArray),
workgroupid: toWorkgroups
},
callback: function(options, success, response) {
me.setLoading(false);
if (!success) {
return;
}
this.destroy();
}
});
The ruleIdsArray is just an array of integers: [1274,1292,1745].
The toWorkgroups is an array of objects which has a model that is related to the WorkgroupId entity.
As you can see, both lists are being processed as query parameters and I'm using the "params" config in the Ajax request; however, this is not working.
Seems like the ruleId array is empty, when it tries to iterate the rulesIdList is empty so the method POST works but it is returning always an empty list.
I know I cannot use them in the form "url?ruleid=a&workgroupid=b". When I tried it just by curiosity, I got a QueryParamException and NumberFormatException saying that the rule array is being considered as string.
When I use the "Ext.encode" for both params I receive a message in browser console that the Maximum callstack size exceeded.
This is what I got from Chrome Console:
I've tried almost everything, but maybe some more eyes can help me in this, I'd really appreciate comments or any kind of help.
Thanks in advance.
Looks like your parameters are going in the POST body instead of as query parameters.
url?ruleid=a&workgroupid=b is getting a NumberFormatException because ruleId is supposed to be Integer.
url?ruleid=1&workgroupid=b or url?ruleid=1&ruleid=2&workgroupid=b should work
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.