openhab / openhab-addons

Add-ons for openHAB
https://www.openhab.org/
Eclipse Public License 2.0
1.88k stars 3.59k forks source link

[HTTP] Basic auth not working when configured in thing #9567

Closed martingruening closed 3 years ago

martingruening commented 3 years ago

Problem: Basic Auth seems to be not working when filling username/password in the UI thing configuration

Version: OpenHAB 3.0 Release

Created HTTP thing with one channel to query a REST API with a GET request: (please ignore the additonal headers, I added them later on to get it going as a workaround)

UID: http:url:openems-rest
label: OpenEMS
thingTypeUID: http:url
configuration:
  authMode: BASIC
  ignoreSSLErrors: false
  baseURL: http://examplehost:8084/rest/channel/
  password: admin
  refresh: 30
  commandMethod: PUT
  contentType: application/json
  timeout: 3000
  username: admin
  bufferSize: 2048
  headers:
    - Authorization=Basic YWRtaW46YWRtaW4=
channels:
  - id: meter0ActivePower
    channelTypeUID: http:string
    label: meter0ActivePower
    description: ""
    configuration:
      mode: READONLY
      stateExtension: meter0/ActivePower

This happens:

20:28:14.396 [TRACE] [http.internal.http.RefreshingUrlCache] - Sending to 'http://kube.gruning.eu:8084/rest/channel/meter0/ActivePower': Method = {GET}, Headers = {Accept-Encoding: gzip, User-Agent: Jetty/9.4.20.v20190813}, Content = {null}
20:28:14.397 [TRACE] [tp.internal.http.HttpResponseListener] - Received from 'http://kube.gruning.eu:8084/rest/channel/meter0/ActivePower': Code = {400}, Headers = {Date: Mon, 28 Dec 2020 19:28:14 GMT, Content-Type: application/json, Content-Length: 127, Server: Jetty(9.4.32.v20200930)}, Content = {{"jsonrpc":"2.0","id":"00000000-0000-0000-0000-000000000000","error":{"code":1003,"message":"Authentication failed","data":[]}}}
20:28:14.398 [WARN ] [tp.internal.http.HttpResponseListener] - Requesting 'http://kube.gruning.eu:8084/rest/channel/meter0/ActivePower' (method='GET', content='null') failed: 400 Bad Request

Then I manually added the authorization header which then leads to the expected result:

20:29:31.162 [TRACE] [http.internal.http.RefreshingUrlCache] - Sending to 'http://kube.gruning.eu:8084/rest/channel/meter0/ActivePower': Method = {GET}, Headers = {Accept-Encoding: gzip, User-Agent: Jetty/9.4.20.v20190813, Authorization: Basic YWRtaW46YWRtaW4=}, Content = {null}
20:29:31.166 [TRACE] [tp.internal.http.HttpResponseListener] - Received from 'http://kube.gruning.eu:8084/rest/channel/meter0/ActivePower': Code = {200}, Headers = {Date: Mon, 28 Dec 2020 19:29:31 GMT, Content-Type: application/json, Content-Length: 48, Server: Jetty(9.4.32.v20200930)}, Content = {{"value":1427,"type":"integer","writable":false}}
20:29:31.167 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'OpenEMS_meter0ActivePower' changed from NULL to {"value":1427,"type":"integer","writable":false}

To me it looks like there is no authorization header in the request at all (despite config in the UI). Looks like a bug to me?

J-N-K commented 3 years ago

Why do you add the additional header? Works perfectly fine without it.

martingruening commented 3 years ago

No, in my case is doesn't. As you see under 'This happens' the authentication fails (400 Bad Request). The additional header is just a workaround for this problem and proofs that it works when the right basic auth header is present.

J-N-K commented 3 years ago

I just checked that

UID: http:url:d73477931b
label: TestBasic
thingTypeUID: http:url
configuration:
  authMode: BASIC
  ignoreSSLErrors: false
  baseURL: https://jigsaw.w3.org/HTTP/Basic
  password: guest
  delay: 0
  refresh: 30
  commandMethod: GET
  timeout: 3000
  username: guest
  bufferSize: 2048
channels:
  - id: string
    channelTypeUID: http:string
    label: Content
    description: ""
    configuration: {}

works as expected. The only difference I see is the commandMethod, but I would be surprised if that is the problem. Can you try my config and report if that works?

martingruening commented 3 years ago

Hmm, strange. That works like a charm. Even if I change it to include a state URL extension. I have no clue. In my setup I can query the URL when using a browser extension (like RESTer for Firefox) or when manually adding the header with the HTTP binding. I will try to dive into this by looking at the server side debug logs tomorrow.

J-N-K commented 3 years ago

Please let me know if you need additional information from binding side, I can prepare special jars for you.

martingruening commented 3 years ago

The server side debug logs just state 'REST call failed: Authentication failed' which is not helpful.

I fired up tcpdump on my docker host and captured the HTTP traffic between the binding and the server: wireshark

There is clearly no authorization header present. This still looks like a bug in the client side code unfortunately. It is just the question: why is it omitted in this case and properly send in another (like your w3.org sample)?

J-N-K commented 3 years ago

I‘ll have a look.

J-N-K commented 3 years ago

I think I found the issue. But no idea how to solve it.

The reason is that the jetty client ALWAYS first tries without authentication and expects a 401/Unauthorized to be returned. If that is the case, the request is re-tried with the proper authentication header. Then it succeeds.

Jigsaw properly responds. Your server answers with a 400/Bad Request instead of 401/Unauthorized, therefore Jetty is not trying again.

martingruening commented 3 years ago

I will check with API developer whether they can change the behaviour to send a 401 code. I think this is worth mentioning in the binding docs to prevent further duplicate bug reports. This one can be closed - many thanks for investigating this issue.

martingruening commented 3 years ago

Closed - it's a Jetty client feature, not a bug

MrMontesa commented 3 years ago

:-) Doesnt help in my case, since I query a public API without control over the server side. So recommendation would be to use additional header instead?

martingruening commented 3 years ago

Yes, create the auth header manually, insert into the binding config and off you go.

J-N-K commented 3 years ago

You can try update org.openhab.binding.http https://janessa.me/esh/org.openhab.binding.http-3.1.0-SNAPSHOT.jar and select "Preemptive Basic Authentication". In most cases this shpuld solve your issue.

martingruening commented 3 years ago

404 Not found

J-N-K commented 3 years ago

Fixed. Forgot an URL part.

martingruening commented 3 years ago

Thanks Jan! Now it works like a charm. Thanks for making this fix/workaround available.

One cosmetic thing: if you have added a header in the UI and then remove it completely your logs gets messed up with: [http.internal.http.RefreshingUrlCache] - Splitting header '' failed. No '=' was found. Ignoring

Can be fixed by manually editing the code of the thing and removing the header section completely. But I personally believe it should not be a warning in the logs when it is completely emtpy ('').

J-N-K commented 3 years ago

Yes, I‘ll add a check for that.