-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
The Fronius inverter for photovoltaic systems uses for its local REST API digest authentication.
It uses in its initial response header "X-WWW-Authenticate" instead of "www-authenticate" and hence make the httpx request fail.
Here an initial curl command with the response header:
❯ curl -I "http://192.168.1.141/config/emrs"
HTTP/1.1 401 Unauthorized
X-WWW-Authenticate: Digest realm="Webinterface area", charset="UTF-8", algorithm=MD5, nonce="66ec2f8d:36a65537553cf788606762854336a0b3", qop="auth"
Content-Type: text/html
Content-Length: 347
Date: Thu, 19 Sep 2024 14:05:01 GMT
Server: webserver
httpx 0.27.2 fails to extract X-WWW-Authenticate with nonce, qop, algorithm, realm in
Lines 201 to 212 in 87713d2
| if response.status_code != 401 or "www-authenticate" not in response.headers: | |
| # If the response is not a 401 then we don't | |
| # need to build an authenticated request. | |
| return | |
| for auth_header in response.headers.get_list("www-authenticate"): | |
| if auth_header.lower().startswith("digest "): | |
| break | |
| else: | |
| # If the response does not include a 'WWW-Authenticate: Digest ...' | |
| # header, then we don't need to build an authenticated request. | |
| return |
My script
import httpx
url = "http://192.168.1.141/config/emrs" # adjust IP to that one of your inverter
user = "technician"
pw = <INSERT PW>
auth = httpx.DigestAuth(user,pw)
with httpx.Client(auth=auth) as client:
r = client.get(url)
print(r.status_code)
print(r.json()['priorities'])
r = client.get(url)
print(r.status_code)
print(r.json()['priorities'])runs only with following quick diff
❯ git diff
diff --git a/httpx/_auth.py b/httpx/_auth.py
index b03971a..fb6ad69 100644
--- a/httpx/_auth.py
+++ b/httpx/_auth.py
@@ -198,12 +198,22 @@ class DigestAuth(Auth):
response = yield request
- if response.status_code != 401 or "www-authenticate" not in response.headers:
+ auth_strings = [ "www-authenticate", "x-www-authenticate" ]
+ auth_string = False
+ for a in auth_strings:
+ if a in response.headers:
+ if auth_string == False:
+ auth_string = a
+ else:
+ message = "Malformed Digest WWW-Authenticate response header"
+ raise ProtocolError(message, request=request)
+
+ if response.status_code != 401 or auth_string == False:
# If the response is not a 401 then we don't
# need to build an authenticated request.
return
- for auth_header in response.headers.get_list("www-authenticate"):
+ for auth_header in response.headers.get_list(auth_string):
if auth_header.lower().startswith("digest "):
break
else:Note, this diff is only to show that it works. I did not follow any coding guidelines of this project.
For reference, here a couple of projects which have written their custom digest authorization to handle the Fronius response header using the request library:
- https://github.com/wiggal/GEN24_Ladesteuerung/blob/main/FUNCTIONS/httprequest.py
- https://github.com/muexxl/batcontrol/blob/main/fronius/fronius.py#L326-L361
- https://github.com/jakkaj/ha_fronius_set_service/blob/main/fronius_settings_service/script.py#L28-L112
- https://github.com/sergioperez/fronius-auth-proxy/blob/master/makeRequest.js
I am not sure how common it is to use X-WWW-Authenticate. Google did not return many results:
However, I still think supporting this deviation might be acceptable. If Fronius screwed this up more heavily than I think, I understand that httpx cannot support all quirks of all digest implementations.