3

I am attempting to use the REST api of Jenkins. Jenkins requires a POST request to a URL to delete a job. This results in the following:

  1. I tell my chosen Client to send a POST to the appropriate URL.
    The client sends a POST and authorizes itself with username and password.
  2. Jenkins deletes the job.
  3. Jenkins returns a "302 - Found" with the location of folder containing the deleted job.
  4. Client automatically sends a POST to the location.
  5. Jenkins answers with "200 - OK" and the full HTML of the folder page.

This works just fine with Postman (unless I disable "Automatically follow redirects" of course). Jersey however keeps running into a "404" at step 5 because I blocked anonymous users from viewing the folder in question. (Or a "403" if I blocked anonymous users altogether.) Note that the authentication works in step 1 because the job has been deleted successfully!

I was under the impression that Jersey should use the given authentication for all requests concerning the client. Is there a way to actually make this true? I really don't want to forbid redirects just to do every single redirect myself.

To clarify: The problem is that while Jersey follows the redirect, but fails to authenticate itself again, leading to the server rejecting the second request.

Code in question:

HttpAuthenticationFeature auth = HttpAuthenticationFeature.basicBuilder()
    .credentials(username, token)
    .build();
Client client = ClientBuilder.newBuilder()
    .register(auth)
    .build();
WebTarget deleteTarget = client.target("http://[Jenkins-IP]/job/RestTestingArea/job/testJob/doDelete")  
Response response = deleteTarget.request()
    .post(null);

EDIT: The "302-Found" only has 5 headers according to Postman: Date, X-Content-Type-Options ("nosniff"), Location, Content-Length (0) and Server. So neither any cookies nor any tokens that Postman might use and Jersey disregard.

Question loosely related to this one - if I were able to log the second request I might be able to understand what's happening behind the scenes.

EDIT2: I have also determined that the problem is clearly with the authentication. If I allow anonymous users to view the folder in question, the error disappears and the server answers with a 200.

MrThaler
  • 96
  • 6
  • So what happens after the first authentication, is there a token or cookie sent back? You haven't really explained the authentication part in regards to how it is successful with Postman. – Paul Samsotha Mar 04 '20 at 21:01
  • Hi Paul, I'm not sure I understand what you mean with that second part as I'm not super experienced with REST in general. The authorization type is "Basic Auth" if that's what you mean, simply a username and a password. (Well, the "password" is a token generated on a server, but it still needs the Basic Auth way of authorizing...) I added details to the response to my post - basically the response only contains "Date, X-Content-Type-Options, Location, Content-Length, Server". So no cookies or tokens unfortunately. – MrThaler Mar 05 '20 at 10:41
  • Oh ok, I see what you're saying; the redirect fails because the credentials aren't sent. I guess I misunderstood the whole scenario. – Paul Samsotha Mar 06 '20 at 06:31
  • I edited the question in an attempt to clarify. I also linked another question which might help solving this one. – MrThaler Mar 06 '20 at 09:13
  • 1
    It doesn't look like Jersey handles the redirects. Instead, the lower level Connector handles it. For instance if you use the default HttpUrlConnection Connector, it has a method to set if you want to follow redirects, and it gets that from the Jersey property that you set. [See here](https://github.com/eclipse-ee4j/jersey/blob/2.30.1/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java#L322) – Paul Samsotha Mar 07 '20 at 02:31
  • So what you're saying is I should open up another question for the HttpUrlConnection connector or try using another connector altogether? I previously turned off automatic redirects to perform the redirect myself with correct authentication, but having an automatic redirect _with_ authentication would be preferable of course. – MrThaler Mar 09 '20 at 10:01
  • 1
    With the information I provided, logically, I don't see how you could make this work without doing it manually, as you suggested. Other connectors have the same option of setting redirect. So either write your own Connector or simply handle the redirect yourself manually. – Paul Samsotha Mar 09 '20 at 13:08
  • That is extremely weird. One would think "Follow redirect but keep authenticating yourself" wouldn't be such a corner case for REST requests that one had to implement it manually. Thank you for your patience, I suppose I'll have to keep doing it manually. – MrThaler Mar 09 '20 at 13:54
  • 2
    How have you validated that the second request ( after redirect ) is also a POST ? It seems most of the internet works with the second request being converted to a GET - with the headers dropped. In such a scenario, it is likely you'll face the issue you are facing. Have you also tried the `http.strictPostRedirect=true` system property to see if it works ? – Gautam Mar 12 '20 at 06:15
  • I have not successfully validated this because I have been unable to log that (see my related quesiton [here](https://stackoverflow.com/questions/60505516/jersey-client-logging-http-redirects)). But I have used Postman to determine that Jenkins doesn't care if it receives a GET or a POST to the URL in question, it will always hand out the exact same HTML doc. – MrThaler Mar 12 '20 at 12:43
  • I tried setting the System property (did it wrong the first time) and it now works (and stops working again if I un-set the property). This makes *no* sense to me because as I said before, Jenkins doesn't seem to care whether I send a POST or a GET to the URL in question...unless they indeed not only turn the POST into a GET but also drop all headers. This is bonkers behavior. Only source I could find was [this](https://stackoverflow.com/a/14256082/12404165) other StackOverflow post. – MrThaler Mar 12 '20 at 13:45
  • So is that the solution to your problem? – Paul Samsotha Mar 13 '20 at 14:13
  • It is. I wrote an answer mentioning the both of you. – MrThaler Mar 16 '20 at 10:07

1 Answers1

2

I found the answer with the help of Paul Samsotha and Gautham.

TL;DR: This is intended behavior and you have to set the System property http.strictPostRedirect=true to make it work or perform the second request yourself.


As also described here, HttpURLConnection decided to not implement a redirect as it is defined in the HTTP standard but instead as many browsers implemented it (so in laymans terms, "Do it like everyone else instead of how it is supposed to work"). This leads to the following behavior:

  1. Send POST to URL_1.
  2. Server answers with a "302 - Found" and includes URL_2.
  3. Send GET to URL_2, dropping all the headers.
  4. Server answers with a "404 - Not Found" as the second request does not included correct authentication headers.
  5. The "404" response is the one received by the code, as steps 2 and 3 are "hidden" by the underlying code.

By dropping all headers, the authentication fails. As Jersey uses this class by default, this lead to the behavior I was experiencing.

MrThaler
  • 96
  • 6