I have a Play application with a POST route which will act as a RESTful API.
Whats the best way to get POST data within a controller? As you can see from my controller I have attempted this, however it doesn't appear to work correctly.
Routes:
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# Home page
GET / controllers.Application.index()
GET /api/getMessages controllers.Application.getMessages()
POST /api/createMessage controllers.Application.createMessages()
Controller:
package controllers;
import play.*;
import play.mvc.*;
import static play.libs.Json.toJson;
import java.util.Map;
import models.*;
import views.html.*;
public class Application extends Controller {
public static Result index() {
return ok(index.render("Your new application is ready."));
}
public static Result createMessages(){
final Map<String, String[]> values = request().body().asFormUrlEncoded();
String from = values.get("from")[0];
String subject = values.get("subject")[0];
String message = values.get("message")[0];
Message.create(from, subject, message);
return ok(toJson("ok"));
}
public static Result getMessages(){
return ok(toJson(Message.all()));
}
}
Request:
Request Url: http://localhost:9000/api/createMessage
Request Method: POST
Status Code: 400
Params: {
"from": "hello#test.com",
"subject": "Hello",
"message": "World"
}
Try with DynamicForm:
public static Result createMessages(){
DynamicForm df = play.data.Form.form().bindFromRequest();
String from = df.get("from");
String subject = df.get("subject");
String message = df.get("message");
if(from != null && subject != null && message != null){
Message.create(from, subject, message);
return ok(toJson("ok"));
} else {
return ok(toJson("error"));
}
}
I'm pretty sure that author already found solution for those 2 years :), but today I was on same trouble and probably my nuance will help someone:
I used same methods to get POST parameters:
request().body().asFormUrlEncoded().get("from")[0];
And I got error too. But error was because of different POST type. So in my case I just was need to expect Multipart Form Data like in next variant:
request().body().asMultipartFormData().asFormUrlEncoded().get("from")[0];
So - just be a bit more careful with data that you are sending and data that you are expecting :)
Related
Hi there I am writing an OpenSource Springboot microservice to get issues from SonarQube
I use SonarQube is installed in a Docker container
From the browser I call successfully after logging in :
http://localhost:9001/api/issues/search?componentKeys=za.co.nico:RabbitMqPoc
I am unit testing a Java class that I can call the same URL and getting a 401
failing Authentication where I need it to work
package za.co.nico.poc.services;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.HttpConnector;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.WsClientFactories;
import org.sonarqube.ws.client.WsResponse;
import org.springframework.stereotype.Service;
/**
*
* https://www.baeldung.com/java-http-request
*
*/
#Service
public class SonarServiceImpl implements SonarService {
private static final Logger log = LoggerFactory.getLogger(SonarServiceImpl.class);
private final String TOKEN="2ce06b3585c34141beeeb4005235337ba2bd135d";
/**
* https://programtalk.com/java-api-usage-examples/org.sonarqube.ws.client.WsClient/
*/
#Override
public String getData(String restUrl,String sonarEndPoint) {
log.debug(" restUrl : "+restUrl+" sonarEndPoint : "+sonarEndPoint); // restUrl : http://localhost:9001/ sonarEndPoint : api/components/search_projects
String login="admin";
String password="admin";
WsClient wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder().url(restUrl).credentials(login, password).build());
WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate"));
String content = response.content();
log.debug(""+response.isSuccessful());
log.debug(""+response.code()); //200
response = wsClient.wsConnector().call(new GetRequest("api/components/search_projects"));
log.debug(""+response.isSuccessful());
content = response.content();
log.debug(""+response.code()); //401
wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder().url(restUrl).credentials("admin", "admin").build());
response = wsClient.wsConnector().call(new GetRequest("local_ws_call/require_permission"));
log.debug(""+response.isSuccessful());
log.debug(""+response.code()); // 200
wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder().url(restUrl).credentials(login, password).build());
response = wsClient.wsConnector().call(new GetRequest("api/rules/search"));
log.debug(""+response.isSuccessful());
log.debug(""+response.code()); // 401
return "";
}
}
Please advise how to fix this
A 401 error simply means that the authentication credentials you are providing did not match what was expected. It appears you are providing a username and password of "admin" and "admin", but I also see that you defined a "TOKEN" constant, which is unreferenced. We would have no idea how you configured your sonarqube instance, or what credentials are actually required. In my experience, it's best for automation scripts to use the "token" authentication system, which you appear to have been intending to use, but you're simply not passing it correctly.
I have seen this issue being addressed in many other posts but none of them has solved my problem. I have a front-end Vue.js application and a spring boot Java application.
I am using the vue-google-oauth to prompt the Google sign in from my front end application to get the auth code, then I wanted to use my backend server to get user details and handle logic there.
On Google Cloud Platform I defined an Authorized redirect URI:
and I am using this very same uri when I am sending my auth code in the front end
import api from "#/assets/js/api";
import AdminNavigation from "./AdminNavigation";
import { mapGetters } from "vuex";
import Axios from "axios";
export default {
name: "Dashboard",
computed: {
...mapGetters(["IsSignedIn"]),
},
data() {
return {
title: "Christopher s' portfolio admin",
appDescription:
"Here you can add contents for the front end portfolio website.",
isInit: false,
};
},
components: {
AdminNavigation,
},
methods: {
signIn: async function () {
try {
const authCode = await this.$gAuth.getAuthCode();
Axios.post("http://localhost:8080/authenticate", {
code: authCode,
redirect_uri: "http://localhost:3000/admin/dashboard",
});
} catch (err) {
console.log(err);
}
},
},
mounted() {
let that = this;
let checkGauthLoad = setInterval(function () {
that.isInit = that.$gAuth.isInit;
if (!this.IsSignedIn) {
that.signIn();
}
if (that.isInit) clearInterval(checkGauthLoad);
}, 1000);
},
};
My backend server receives the auth code and the redirect_uri which is identical to what was defined on Google Cloud Platform.
package com.salay.christophersalayportfolio.controllers;
import com.google.api.client.auth.oauth2.TokenResponseException;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.salay.christophersalayportfolio.general.ConstantVariables;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE;
#Controller
public class AdminController {
#CrossOrigin(origins = "http://localhost:3000")
#RequestMapping(value = "/authenticate", method = RequestMethod.POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
#ResponseBody
public String authentication(HttpEntity<String> data) throws IOException, ParseException {
JSONParser parser = new JSONParser();
JSONObject json = (JSONObject) parser.parse(data.getBody());
String authCode = json.get("code").toString();
String redirect_uri = json.get("redirect_uri").toString();
try {
GoogleTokenResponse response =
new GoogleAuthorizationCodeTokenRequest(new NetHttpTransport(), new JacksonFactory(),
ConstantVariables.GOOGLE_CLIENT_ID,
ConstantVariables.GOOGLE_CLIENT_SECRET,
authCode, redirect_uri).execute();
System.out.println("Access token: " + response.getAccessToken());
} catch (TokenResponseException e) {
if (e.getDetails() != null) {
System.err.println("Error: " + e.getDetails().getError());
if (e.getDetails().getErrorDescription() != null) {
System.err.println(e.getDetails().getErrorDescription());
}
if (e.getDetails().getErrorUri() != null) {
System.err.println(e.getDetails().getErrorUri());
}
} else {
System.err.println(e.getMessage());
}
}
return "";
}
}
But I get the following error:
400 Bad Request redirect_uri_mismatch
I kept looking at a lot of stack overflow questions and no solution worked for me so far... any ideas?
Sounds like you are not sending the OAuth details you think you are. Have you captured HTTPS messages to the Authorization Server from your Spring Boot back end - and can you post details here?
If it helps, this blog post of mine includes some notes on configuring an HTTP proxy in Java.
Say I have this resource:
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresRoles;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
#Path("/authhello")
#Api(value = "hello", description = "Simple endpoints for testing api authentification",
hidden = true)
#Produces(MediaType.APPLICATION_JSON)
#RequiresAuthentication
public class AuthenticatedHelloWorldResource {
private static final String READ = "READ";
private static final String WRITE = "WRITE";
#GET
#ApiOperation(value = "helloworld",
notes = "Simple hello world.",
response = String.class)
#RequiresRoles(READ)
public Response helloWorld() {
String hello = "Hello world!";
return Response.status(Response.Status.OK).entity(hello).build();
}
#GET
#Path("/{param}")
#ApiOperation(value = "helloReply",
notes = "Returns Hello you! and {param}",
response = String.class)
#RequiresRoles(WRITE)
public Response getMsg(#PathParam("param") String msg) {
String output = "Hello you! " + msg;
return Response.status(Response.Status.OK).entity(output).build();
}
}
Should I write tests that confirm that certain (test) users get a response from the endpoints, and certain users don't? And if so: How can I write those tests? I've tried something like this:
import javax.ws.rs.core.Application;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.Test;
import com.cognite.api.shiro.AbstractShiroTest;
import static org.junit.Assert.assertEquals;
public class AuthenticatedHelloWorldTest extends AbstractShiroTest {
#Override
protected Application configure() {
return new ResourceConfig(AuthenticatedHelloWorldResource.class);
}
#Test
public void testAuthenticatedReadHelloWorld() {
final String hello = target("/authhello").request().get(String.class);
assertEquals("Hello world!", hello);
}
#Test
public void testAuthenticatedWriteHelloWorld() {
final String hello = target("/authhello/test").request().get(String.class);
assertEquals("Hello you! test", hello);
}
}
but I'm not sure how to actually test the function of the #RequiresRoles-annotation. I've read Shiro's page on testing, but I haven't been able to write a failing test (e.g. a test for a subject that does not have the WRITE role trying to access /authhello/test). Any tips would be appreciated.
Should I even test this?
Yes. Provided you want to make sure that certain roles will have or have not access to your resource. This will be a security integration test.
How should I go about setting up the whole application + actually call it with an http request in a test if I am to test it? Or is there a simpler way?
Part of the issue is that #RequiresAuthentication and #RequiresRoles themselves are just class and method meta information. Annotations themselves do not provide the security check functionality.
It is not clear from your question what type of container you are using but I can guess that it is plain Jersey JAX-RS service (am I right?). For Shiro to perform security checks you should have added some JAX-RS filter (maybe some other way?) around your endpoints. To test security you should replicate this setup in your tests. Otherwise there is no engine processing your annotations and no security checks as the result.
New to java programming and still learning. I've built a RESTful service and I'm trying to pass in a parameter for a GET routine and I'm getting back a state 400 saying that the "Request entity cannot be empty". When I call the non-parameterized GET, the data comes back just fine. I've stripped down all the functionality of the parameterized GET to just return a simple string and I'm still getting the same message. Searched all over and can't find anything that's very helpful.
Below is the code that I'm running for the service. The method "GetChildAllInfo" makes a call to a local mySQL instance and returns a list of objects; that one works just fine. The parameterized one returns nothing, not even an exception.
Any help would be tremendously appreciated. Even if it's a ridiculously simple solution like a syntax error that I may have missed. AND I'm willing to accept any other advice on what you see in the code as well. Thanks!
package allowanceManagerChild;
import java.util.Set;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.Produces;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PUT;
import javax.ws.rs.core.MediaType;
import com.google.gson.Gson;
#Path("allowanceManagerChild")
public class AllowanceManagerChild {
#Context
private UriInfo context;
/**
* Creates a new instance of AllowanceManagerChild
*/
public AllowanceManagerChild() {
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public String getJson() {
String response = "";
Set<Child> children = Child.GetAllChildInfo();
for (Child child : children){
Gson gson = new Gson();
String json = gson.toJson(child);
response = response + json;
}
return response;
}
#GET
#Path("/{childID}")
#Produces(MediaType.APPLICATION_JSON)
public String getJson(int childID) {
String response = "";
try{
// Set<Child> children = Child.GetChildInfo(id);
// for (Child child : children){
// Gson gson = new Gson();
// String json = gson.toJson(child);
// response = response + json;
// }
response = "Made it here"; //Integer.toString(childID);
}
catch(Exception e){
response = e.toString();
}
return response;
}
/**
* PUT method for updating or creating an instance of AllowanceManagerChild
* #param content representation for the resource
*/
#PUT
#Consumes(MediaType.APPLICATION_JSON)
public void putJson(String content) {
}
}
Adding the #PathParam annotation to the method parameter might help:
#GET
#Path("/{childID}")
#Produces(MediaType.APPLICATION_JSON)
public String getJson(#PathParam("childID") int childID) {
See the RESTful Web Services Developer's Guide for more details.
I have been Googling and trying to get this to work for hours...The problem is the server is not receiving data as JSON but as text. This is the POJO
package my.package;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class TestConfig {
private String firmID;
private String traderID;
private String userID;
public TestConfig() {};
...
}
A Javascript client which contains:
function callbackForTest(response) {
console.log("Call to callbackForTest");
if (response.state == "opening" && response.status == 200) {
//push request data
if (connectedEndpoint[0] == null) {
console.log("[DEBUG] Connected endpoint for " + value + "is null!");
//disable button
$(value).attr('disabled','');
$.atmosphere.unsubscribe();
return false;
}
// push ( POST )
connectedEndpoint[0].push(JSON.stringify(
{
operation : "RUN",
firmID : $('#firmID').val(),
userID : $('#userID').val(),
traderID : $('#traderID').val(),
protocol : $('#protocol').val(),
group1 :
}
));
}
}
function subscribeUrl(jobName, call, transport) {
var location = subscribePath + jobName.id;
return subscribeAtmosphere(location, call, transport);
}
function globalCallback(response) {
if (response.state != "messageReceived") {
return;
}
}
function subscribeAtmosphere(location, call, transport) {
var rq = $.atmosphere.subscribe(location, globalCallback, $.atmosphere.request = {
logLevel : 'debug',
transport : transport,
enableProtocol: true,
callback : call,
contentType : 'application/json'
});
return rq;
}
function sendMessage(connectedEndpoint, jobName) {
var phrase = $('#msg-' + jobName).val();
connectedEndpoint.push({data: "message=" + phrase});
}
// Run Test handlers
$("input[name='runButtons']").each(function(index, value){
$(value).click(function(){
//disable button
$(value).attr('disabled','disabled');
// connect (GET)
connectedEndpoint[index] = subscribeUrl(value, callbackForTest, transport);
});
});
I have included the libs shown in this screenshot:
LIBS
And this is my web.xml (part of it)
com.sun.jersey.api.json.POJOMappingFeature
true
The Jersey resource
#Path("/subscribe/{topic}")
#Produces({MediaType.APPLICATION_JSON, "text/html;charset=ISO-8859-1", MediaType.TEXT_PLAIN})
public class Subscriber {
private static final Logger LOG = Logger.getLogger(Subscriber.class);
#PathParam("topic")
private Broadcaster topic;
#GET
public SuspendResponse<String> subscribe() {
LOG.debug("GET - OnSubscribe to topic");
SuspendResponse<String> sr = new SuspendResponse.SuspendResponseBuilder<String>().broadcaster(topic).outputComments(true)
.addListener(new EventsLogger()).build();
return sr;
}
#POST
#Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN, MediaType.TEXT_HTML})
#Broadcast
public Broadcastable publish( TestConfig t) {
LOG.debug("POST");
String s = t.getFirmID();
return new Broadcastable(s, "", topic);
}
I can subscribe OK. When I try to push to the server, I get this exception:
A message body reader for Java class com.mx.sailcertifier.TestConfig, and Java type class com.mx.sailcertifier.TestConfig, and MIME media type text/plain was not found.
Why is it sending plain text if I set the content type to application/json? What is the correct way to get the Jersey resource to read the JSON?
I finally got this working with two changes:
After looking at the sample here, I added this init-param to the AtmosphereServlet in the web.xml to resolve the text/plain problem in Tomcat:
<init-param>
<param-name>org.atmosphere.websocket.messageContentType</param-name>
<param-value>application/json</param-value>
</init-param>
I didn't see this anywhere documented in the Atmosphere docs. It would have saved a lot of time had it been, but documentation-wise the API is unfortunately disorganized and lacking.
Also, I needed to use the jersey-bundle jar make sure that everything Jersey related is included, including as the jersey-json.jar. After that, it worked! Hope this helps someone else who may have been stuck with the same or similar problem.