401 for PostRequest with HttpURLConnection - java

UPDATE
My problem turned out to be due to some super-class weirdness. However, the urlEncode in this example is wrong. In this case it was also unnecessary. If needed it should be done by each key and value without encoding & or =.
Other than that, this code is correct.
The (scala) code below fails because the server does not seem to receive the body.
The warning on the server log is:
WARNING *** OAuthTokenProcessor caught a OAuthRequestProblemException with message OAuthProblemException{error='invalid_request', description='Missing grant_type parameter value', uri='null', state='null', scope='null', redirectUri='null', responseStatus=0, parameters={}}
I am certain that the params are correct. The url is correct, The headers are correct
(This call already works in Postman and Python)
Any hints deeply appreciated!
private def doPostRequest(): Unit ={
try {
val connectionforPost = (new URL(url)).openConnection.asInstanceOf[HttpURLConnection]
val params = "grant_type=password&client_id=xxxx&client_secret=seccret&username=admin&password=xxxx"
val encodedString = URLEncoder.encode(params,"UTF-8")
import java.nio.charset.StandardCharsets
val postData = encodedString.getBytes(StandardCharsets.UTF_8)
val contentlength = Integer.toString(encodedString.length)
connectionforPost.setConnectTimeout(50000)
connectionforPost.setReadTimeout(50000)
connectionforPost.setRequestMethod("POST")
connectionforPost.setDoOutput(true)
connectionforPost.setDoInput(true)
connectionforPost.setInstanceFollowRedirects(false)
connectionforPost.setRequestProperty("User-agent", "test bot")
connectionforPost.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
connectionforPost.setRequestProperty("Accept", "*/*")
connectionforPost.setRequestProperty("Connection", "keep-alive")
connectionforPost.setRequestProperty("Content-Length", contentlength)
connectionforPost.setRequestProperty("Accept-Encoding", "gzip, deflate")
connectionforPost.setUseCaches(false)
import java.io.DataOutputStream
val wr = new DataOutputStream(connectionforPost.getOutputStream)
wr.write(postData)
if (wr != null) wr.close()
} catch{
case e:Exception => logger.error(e.getMessage,e)
throw e
}
}

It appears that the server complains that you are not logged in. You get an authentication problem exception. How it usually works is that you send a request to the server to login with the user name and password. If the server authenticates you (recognizes your user name and password as valid ones) it sends you a response with an authentication token in the headers. You may see those 2 headers in your response to successful login:
access-control-expose-headers=[Authorization], Authorization=[Bearer ....]
This means that in all your subsequent requests you will need to add a header "Authorization" that will hold the value "Bearer ....". Then the server will recognize this request as coming from an authenticated user.
Also, I suggest that you may use a 3d party HTTP client. Some well-known clients are Appache HTTP client and OK HTTP client. I use my own Http client that I wrote. It is also available as part of a MgntUtils Open Source Library. Here is the link to Maven artifacts and it is also available on Github with source code and JavaDoc. And here is a javadoc page for HttpClient. Here is the code sample on how you may obtain the token and use it:
try {
HttpClient loginClient = new HttpClient();
loginClient.setConnectionUrl("http://your_url/login");
loginClient.setRequestProperty("accept", "application/json;charset=UTF-8");
loginClient.setContentType("application/json");
String result = loginClient.sendHttpRequest(HttpMethod.POST, "{ \"username\": \"your_user_name\", \"password\": \"Your_password\"}");
System.out.println(result);
System.out.println("HTTP " + loginClient.getLastResponseCode() + " " + loginClient.getLastResponseMessage());
System.out.println("Response headers: " + loginClient.getLastResponseHeaders());
String accessControlExposeHeader = loginClient.getLastResponseHeader("access-control-expose-headers").get(0);
String accessKey = loginClient.getLastResponseHeader(accessControlExposeHeader).get(0);
HttpClient workingClient = new HttpClient();
workingClient.setRequestProperty("accept", "application/json;charset=UTF-8");
workingClient.setContentType("application/json");
workingClient.setRequestProperty(accessControlExposeHeader, accessKey);
workingClient.setConnectionUrl("http://yourUrl/yourPath");
System.out.println(workingClient.sendHttpRequest(HttpMethod.GET));
} catch (IOException e) {
System.out.println(TextUtils.getStacktrace(e));
}

Related

Microsoft Graph: Requesting an Extension returns http 400 bad request

I added an open extension to an event in a calendar and am trying to read it back.
Here is the url:
https://graph.microsoft.com/v1.0/users/{userid}/calendars/{calendarId}=/events?$expand=Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event')
I cannot get this to work in a Java program. The following combinations do work:
It works my Java program if I remove the $expand... parameter. I can also ask for certain fields, that works too.
The request works in Postman (I just have to set the token)
The request works in Graph Explorer when I log in as the owner of the calendar
Here is the extension (inside one of the events) when I use Postman to read the event. It is the last item in the event:
"extensions#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('{userid}')/calendars('{calendarId}')/events('{eventId})/extensions",
"extensions": [
{
"#odata.type": "#microsoft.graph.openTypeExtension",
"id": "Microsoft.OutlookServices.OpenTypeExtension.c.i.m.p.server.entities.outlook.Event",
"extensionName": "c.i.m.p.server.entities.outlook.Event",
"adherentId": "12346",
"timeSlotID": "346463"
}
]
Here is the Java code (Java 8, using java.io and java.net libraries):
private static void doSomething(String _accessToken) throws IOException {
String urlString = "https://graph.microsoft.com/v1.0/users/{userId}/calendars/{calendarId}/events?$expand=Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event')";
URL url = new URL(urlString);
Proxy webProxy
= new Proxy(Proxy.Type.HTTP, new InetSocketAddress({proxy-address}, {port}));
HttpURLConnection connection = (HttpURLConnection) url.openConnection(webProxy);
// Set the appropriate header fields in the request header.
connection.setRequestProperty("Authorization", "Bearer " + _accessToken);
connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
connection.setReadTimeout(5000);
connection.setRequestMethod(HttpMethod.GET);
try {
connection.connect();
int responseCode = connection.getResponseCode();
System.out.println("execute(), response code = " + responseCode);
String responseMessage = connection.getResponseMessage();
System.out.println("execute(), response Message = " + responseMessage);
String responseString = null;
try {
InputStream ins = connection.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(ins));
StringBuffer sb=new StringBuffer();
String line;
while ((line=br.readLine()) != null) {
sb.append(line);
}
responseString = sb.toString();
} catch (Exception e) {
System.out.println("Could not get input stream from response, error is " + e.toString());
}
System.out.println("execute(), httpResult = " + responseString);
} catch (IOException e) {
System.out.println(".execute(), IOException : " + e.toString());
} finally {
connection.disconnect();
}
}
How do I fix this? Thanks!
400 means bad request. It could be because of url encoding. Url encode the query string.
Something like
String query = "Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event'";
String url = "https://graph.microsoft.com/v1.0/users/{userId}/calendars/{calendarId}/events?
$expand=" + URLEncoder.encode(query, StandardCharsets.UTF_8.name());
Alternatively you could use graph service java api based on your need which will help abstract all the interactions for you or you could use any of the rest clients available.
First of all, you should provide more info on the error - Stacktrace and error message. But 400 code indicates that was a user mistake, meaning that you are sending an invalid request. Since you say that postman request works then compare all the headers that are sent by postman and see if your code misses some hearer. As for the code, instead of coding your own Http client functionality I would suggest using 3d party Http client. Here are a few suggestions:
Apache Http client - very popular and well known 3d party Http Client
OK Http client - Open-source Http client. Here is tutorial
MgntUtils Http client - very simple 3d party HttpClient: Provided in MgntUtils Open source library (written by me). Very simple in use. Take a look at Javadoc. Library itself provided as Maven artifacts and on Git (including source code and Javadoc).

Image share with linkedin v2 api not posting on page feed

Ok so this is my problem.. To share an image post via linkedin api, you first have to register your image file, you do that via a post request in which you send your binary file. Then you use the the image URN in the original request to submit your post. My request goes through, returns 201 code (which should be a successful request) but ends up not posting the image or the text. If i try to post only text, it works. I've tried registering my image using curl, and it posted on linkedin, so i think i'm not sending the binary file in a request properly, this is my request:
HttpClient client = HttpClientBuilder.create().build();
HttpPut request = new HttpPut(uploadUrl);
request.addHeader("Content-Type", "data/binary");
request.setHeader("X-Restli-Protocol-Version", "2.0.0");
request.setHeader("Authorization", "Bearer " + myToken);
File file = new File(pictureUrl);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
builder.addBinaryBody("upload-file", file);
request.setEntity(builder.build());
HttpResponse response = client.execute(request);
I get code 201 with this code, but it still doesn't post.
This is the curl example of the request that they give on Share API doc.
curl -i --upload-file /Users/peter/Desktop/superneatimage.png --header "Authorization: Bearer redacted" 'https://api.linkedin.com/mediaUpload/C5522AQGTYER3k3ByHQ/feedshare-uploadedImage/0?ca=vector_feedshare&cn=uploads&m=AQJbrN86Zm265gAAAWemyz2pxPSgONtBiZdchrgG872QltnfYjnMdb2j3A&app=1953784&sync=0&v=beta&ut=2H-IhpbfXrRow1'
Can you tell me what is wrong with my java equivalent?
Edit: Forgot to say i even tried calling curl from java, the same code i used in the terminal, and it still didn't work..
Process p;
try {
p = Runtime.getRuntime().exec("curl -i --upload-file" + " " + pictureUrl + " " + "--header \"Authorization: Bearer " + myToken + "\" '" + uploadUrl + "'");
p.waitFor();
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
String outputController = "";
while ((line = reader.readLine()) != null) {
outputController = outputController + '\n' + line;
}
System.out.println("out: ");
System.out.println(outputController);
return true;
} catch (IOException | InterruptedException ex) {
return false;
}
Output returned an empty String.
Edit2: Another funny thing, when i execute the main request, in which i send the text, and media urns that i get after submitting images, i get 201 again, like it's successful, and in the response i even get the post id. Then i try to use the another api endpoint and pull that post using the id i got from the response, and i get all the data, like the post is posted. It even says in the json that i get that lifecycle is PUBLISHED and the status of the medias is READY. Only thing that's different from the json an image post that is on linkedin is that the media object have thumbnails, and in this case they don't, it's just an empty json array.
I found curl to C# converter https://curl.olsh.me/
below is code snippet is generated:
using (var httpClient = new HttpClient())
{
using (var request = new HttpRequestMessage(new HttpMethod("PUT"), "https://api.linkedin.com/mediaUpload/C5522AQGTYER3k3ByHQ/feedshare-uploadedImage/0?ca=vector_feedshare&cn=uploads&m=AQJbrN86Zm265gAAAWemyz2pxPSgONtBiZdchrgG872QltnfYjnMdb2j3A&app=1953784&sync=0&v=beta&ut=2H-IhpbfXrRow1"))
{
request.Headers.TryAddWithoutValidation("Authorization", "Bearer redacted");
request.Content = new ByteArrayContent(File.ReadAllBytes("/test.png"));
var response = await httpClient.SendAsync(request);
}
}
Ok I've solved it, if anyone encounters the same problem, this is what i did wrong. In the request i added a multipart to request body, this is wrong, you just go RAW. So instead of
File file = new File(pictureUrl);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
builder.addBinaryBody("upload-file", file);
request.setEntity(builder.build());
you just put
request.setEntity(new FileEntity(new File(pictureUrl), ContentType.create(picture.getContentType())));
and then everything goes on ok.
Not familiar with Java, but I had the same problem using Ruby and I fixed it by adding the MIME type of the image I was uploading as the Content-Type in the request headers. So in your specific case it would be:
request.addHeader("Content-Type", "image/png");
Also take a look at my solution using Ruby's RestClient and Minimagick: https://stackoverflow.com/a/54902863/7844946
I was writing with kotlin and after struggling with the same problem for a long time I managed to fix it, for those struggling with the similar problem, I'll leave my sample codes below. For the visual you need to provide a direct URI path, also pay attention to the header structure.
Here are the codes.
var file = File(ImageURI)
val urls = URL(url)
var connection: HttpsURLConnection = urls.openConnection() as HttpsURLConnection
connection.setRequestProperty("Authorization","Bearer " + accesToken)
connection.setRequestProperty("Content-Type", "image/png")
connection.setRequestProperty("cache-control", "no-cache")
connection.setRequestProperty("X-Restli-Protocol-Version", "2.0.0")
connection.requestMethod = "POST"
connection.doOutput = true
connection.doInput = true
var request = DataOutputStream(connection.outputStream)
request.write(file.readBytes())
request.flush()
println("Response: "+connection.responseCode)

Linked in unable to to retrieve access token missing required parameters error

I have been working on integrating my application with LinkedIn by following the documentation located here. I have created my application in LinkedIn and am able to successfully retrieve the authorization code but I am getting the following error when trying to get the access token :
{"error_description":"missing required parameters, includes an invalid
parameter value, parameter more than once. : Unable to retrieve access token : appId or redirect uri does not match authorization code or authorization code expired","error":"invalid_request"}
I have verified the following to be true:
The redirect uri is the same for the authorization request and access token request
I am using the authorization code within 20 seconds of it being issued.
I am including "Content-Type:application/x-www-form-urlencoded" in the request header
Some things I have tried with no success:
Posting the request with the parameters as part of the url
Posting the request with parameters as part of the body
sending the redirect uri as both encoded and plain text
using a get request instead of a post.
Here is my current code:
linkedin_access_token_url = "https://www.linkedin.com/uas/oauth2/accessToken?"+
"grant_type=authorization_code"+
"&code="+ authCode
+ "&redirect_uri=https://localhost:8090/ProfileSetup/linkedInAuth.jsp
+ "&client_id=" + linkedin_client_id
+ "&client_secret=" + linkedin_client_secret;
HttpClient http = new DefaultHttpClient();
HttpPost httppost = new HttpPost(linkedin_access_token_url);
try {
httppost.setHeader("Content-Type", "application/x-www-form-urlencoded");
HttpResponse response = http.execute(httppost);
HttpEntity entity = response.getEntity();
System.out.println("status code " +
response.getStatusLine().getStatusCode());
System.out.println("statusreason"+
response.getStatusLine().getReasonPhrase());
InputStream stream = entity.getContent();
StringBuilder sb = new StringBuilder();
String line;
try {
br = new BufferedReader(new InputStreamReader(stream));
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (Exception e) {
}
String resp = sb.toString();
System.out.println("response " + resp);
} catch (Exception ex) {
System.out.println("linked in HttpResponse Error: " + ex);
} finally {
httppost.releaseConnection();
}
And the authorization url (actual client id is sent in place of linkedin_client_id):
https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=linkedin_client_id&redirect_uri=https://localhost:8090/ProfileSetup/linkedInAuth.jsp&state=0kcmjj5504tpgb9&scope=r_basicprofile
Does anyone see what I am doing wrong? If I take this compiled url and paste it in the browser, I am able to retrieve an access token without any issue. Is there a problem with my request?
Looks like you are including all of the parameters for the request as attributes in the URL, rather than in the POST body as x-www-form-urlencoded elements.
Check out this other Stack thread for details on how to send the values in the request body rather than as URL attributes:
Sending HTTP POST Request In Java
I was finally able to figure out what was wrong with my app. My local environment was not configured correctly for https. Moving the code onto our dev box set up with https fixed the issue.

Authenticate with CouchDB and insert new database via HTTP PUT in Android app

I am implementing an Android app that should upload data to CouchDB. Since I have restricted the admin access to one account, I have to authenticate before inserting a new database. And this is what I am currently struggling with: Authenticate and insert a new database. Operating via Terminal and using curl, everything is working out fine the following way:
> curl -X PUT http://admin_name:admin_password#url:port/database_to_be_inserted
First approach
My first approach was to simply do the same via HTTP PUT in my code like that:
private boolean putJSON(String json, String url) {
// url = http://admin_name:admin_password#url:port/database_to_be_inserted
HttpClient client = new DefaultHttpClient();
HttpPut put = new HttpPut(url);
try {
StringEntity stringEntity = new StringEntity(json,"utf-8");
put.setEntity(stringEntity);
put.setHeader("Content-type", "application/json; charset=utf-8");
put.setHeader("Accept", "application/json");
HttpResponse response = client.execute(put);
// ... buffered input reading on response...
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
However, doing so I retrieve the following error and JSON array:
Authentication error: Unable to respond to any of these challenges: {}
{"error":"unauthorized","reason":"You are not a server admin."}
The point is, that using the same method for inserting a new user works out perfectly. So, if I am using the above method with a correctly formatted user JSON-Dictionary and the following url, the user is inserted correctly.
http://admin_name:admin_password#url:port/_users/org.couchdb.user:user_name
This should prove, that I am using the right admin data at least, shouldn't it?
Second approach
So, by now, I am trying to authenticate using the "Authorization" option in my HTTP PUT's header:
private boolean putDatabase(String userName, String password, String url) {
// url = "http://url:port/database_to_be_inserted"
HttpClient client = new DefaultHttpClient();
HttpPut put = new HttpPut(url);
String authenticationData = userName+":"+password;
String encoding = Base64.encodeToString(authenticationData.getBytes(Charset.forName("utf-8")), Base64.DEFAULT);
put.setHeader("Authorization", "Basic " + encoding);
try {
put.setHeader("Content-type", "application/json; charset=utf-8");
put.setHeader("Accept", "application/json");
HttpResponse response = client.execute(put);
// ... buffered input reading on response...
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
Still no success in inserting the database. The response I am parsing says:
Host not found
I have double checked the admin name, password, and url and everything seems correct. Does anyone of you see why this might not work out?
Ok, the answer is simple: The above code (at least the one of my second approach) is working fine. My mistake was to not explicitly specify the port via which the CouchDB should be accessed. This is, how I accidentally called the method:
putDatabase("adminName", "adminPassword", "http://url/database_to_be_inserted");
However, this is how I should have called it:
putDatabase("adminName", "adminPassword", "http://url:port/database_to_be_inserted");
Who is using iriscouch like me and does not know which port to specify here, can look it up in the config file. Using Futon this can be found in the entry "httpd > port" here:
> http://your_url_spec.iriscouch.com/_utils/config.html
More general and without Futon this can be found (and if you wish so edited) via command-line in the local.ini of your own CouchDB installation:
~$ cat etc/couchdb/local.ini

How to send a JSONObject to a REST service?

Retrieving data from the REST Server works well, but if I want to post an object it doesn't work:
public static void postJSONObject(int store_type, FavoriteItem favorite, String token, String objectName) {
String url = "";
switch(store_type) {
case STORE_PROJECT:
url = URL_STORE_PROJECT_PART1 + token + URL_STORE_PROJECT_PART2;
//data = favorite.getAsJSONObject();
break;
}
HttpClient httpClient = new DefaultHttpClient();
HttpPost postMethod = new HttpPost(url);
try {
HttpEntity entity = new StringEntity("{\"ID\":0,\"Name\":\"Mein Projekt10\"}");
postMethod.setEntity(entity);
HttpResponse response = httpClient.execute(postMethod);
Log.i("JSONStore", "Post request, to URL: " + url);
System.out.println("Status code: " + response.getStatusLine().getStatusCode());
} catch (ClientProtocolException e) {
I always get a 400 Error Code. Does anybody know whats wrong?
I have working C# code, but I can't convert:
System.Net.WebRequest wr = System.Net.HttpWebRequest.Create("http://localhost:51273/WSUser.svc/pak3omxtEuLrzHSUSbQP/project");
wr.Method = "POST";
string data = "{\"ID\":1,\"Name\":\"Mein Projekt\"}";
byte [] d = UTF8Encoding.UTF8.GetBytes(data);
wr.ContentLength = d.Length;
wr.ContentType = "application/json";
wr.GetRequestStream().Write(d, 0, d.Length);
System.Net.WebResponse wresp = wr.GetResponse();
System.IO.StreamReader sr = new System.IO.StreamReader(wresp.GetResponseStream());
string line = sr.ReadToEnd();
Try setting the content type header:
postMethod.addRequestHeader("Content-Type", "application/json");
Btw, I strongly recommend Jersey. It has a REST client library which makes these kind of things much easier and more readable
Your C# is different than your Java, and not just in syntax.
Your C# sends an application/json entity to the server via HTTP POST. I'll leave it up to HTTP purists as to whether that's appropriate use of POST (vs. PUT).
Your Java creates a form, with a field of jsonString (whose value is the JSON), and sends an application/x-www-form-urlencoded entity to the server containing that form.
I would go right to the server err_log or equivelant error log. The server knows why it rejected your request. If you don't have access, set up your own test server and duplicate the issue there so you can review the logs =)

Categories