finish06 / pyunifi

https://unifi-sdn.ubnt.com/
MIT License
223 stars 100 forks source link

Unable to login to Controller v5.9 #29

Closed makuser closed 5 years ago

makuser commented 5 years ago

Logging in always returns "Login failed - status code: 400".

Trace:

>>> from pyunifi.controller import Controller, APIError
>>> c = Controller('myhostname.tld', 'username', 'password', 8443, version='v5', site_id='site')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/site-packages/pyunifi/controller.py", line 89, in __init__
    self._login(version)
  File "/usr/local/lib/python3.6/site-packages/pyunifi/controller.py", line 151, in _login
    raise APIError("Login failed - status code: %i" % r.status_code)
pyunifi.controller.APIError: Login failed - status code: 400
>>> try:
...     c = Controller('myhostname.tld', 'username', 'password', 8443, version='v5', site_id='site')
... except APIError as e:
...     print(e)
... 
Login failed - status code: 400
>>> 
finish06 commented 5 years ago

Does the same error occur when using the IP address instead of the host name?

makuser commented 5 years ago

For some reason I did not get a notification from github. Please mention @finish06 me next time.

Does the same error occur when using the IP address instead of the host name?

Yes, why wouldn't it happen when using an IP address? UBNT changed the API in UniFi v5 vs v4.

>>> c = Controller('1.3.3.7', 'username', 'password', 8443, version='v5', site_id='site')
Traceback (most recent call last):
  File "/home/marc/.local/lib/python3.6/site-packages/urllib3/connectionpool.py", line 600, in urlopen
    chunked=chunked)
  File "/home/marc/.local/lib/python3.6/site-packages/urllib3/connectionpool.py", line 343, in _make_request
    self._validate_conn(conn)
  File "/home/marc/.local/lib/python3.6/site-packages/urllib3/connectionpool.py", line 839, in _validate_conn
    conn.connect()
  File "/home/marc/.local/lib/python3.6/site-packages/urllib3/connection.py", line 364, in connect
    _match_hostname(cert, self.assert_hostname or server_hostname)
  File "/home/marc/.local/lib/python3.6/site-packages/urllib3/connection.py", line 374, in _match_hostname
    match_hostname(cert, asserted_hostname)
  File "/usr/lib/python3.6/ssl.py", line 331, in match_hostname
    % (hostname, dnsnames[0]))
ssl.CertificateError: hostname '1.3.3.7' doesn't match 'myhostname.tld'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/marc/.local/lib/python3.6/site-packages/requests/adapters.py", line 449, in send
    timeout=timeout
  File "/home/marc/.local/lib/python3.6/site-packages/urllib3/connectionpool.py", line 638, in urlopen
    _stacktrace=sys.exc_info()[2])
  File "/home/marc/.local/lib/python3.6/site-packages/urllib3/util/retry.py", line 398, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='1.3.3.7', port=8443): Max retries exceeded with url: /api/login (Caused by SSLError(CertificateError("hostname '1.3.3.7' doesn't match 'myhostname.tld'",),))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/marc/.local/lib/python3.6/site-packages/pyunifi/controller.py", line 89, in __init__
    self._login()
  File "/home/marc/.local/lib/python3.6/site-packages/pyunifi/controller.py", line 137, in _login
    r = self.session.post(login_url, params)
  File "/home/marc/.local/lib/python3.6/site-packages/requests/sessions.py", line 581, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
  File "/home/marc/.local/lib/python3.6/site-packages/requests/sessions.py", line 533, in request
    resp = self.send(prep, **send_kwargs)
  File "/home/marc/.local/lib/python3.6/site-packages/requests/sessions.py", line 646, in send
    r = adapter.send(request, **kwargs)
  File "/home/marc/.local/lib/python3.6/site-packages/requests/adapters.py", line 514, in send
    raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='1.3.3.7', port=8443): Max retries exceeded with url: /api/login (Caused by SSLError(CertificateError("hostname '1.3.3.7' doesn't match 'myhostname.tld'",),))
>>> c = Controller('1.3.3.7', 'username', 'password', 8443, version='v5', site_id='site', ssl_verify=False)
/home/marc/.local/lib/python3.6/site-packages/urllib3/connectionpool.py:847: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
  InsecureRequestWarning)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/marc/.local/lib/python3.6/site-packages/pyunifi/controller.py", line 89, in __init__
    self._login()
  File "/home/marc/.local/lib/python3.6/site-packages/pyunifi/controller.py", line 139, in _login
    raise APIError("Login failed - status code: %i" % r.status_code)
pyunifi.controller.APIError: Login failed - status code: 400
>>> 

Unfortunately I currently don't really have a lot of time to implement the necessary adjustments myself, but I'll gladly join you to test things, etc.

Let me know if you'd like access to a v5 controller.

makuser commented 5 years ago

@finish06 Please merge #31, as it should fix it. The solution was way simpler than I thought.

I hope this will fix the home assistant unifi device_tracker component, as there are several threads open, complaining it does not work, when it's actually this library that doesn't work.

finish06 commented 5 years ago

@makuser I am on controller version 5.9 and everything works fine. Did you test the code change with your controller? When I test my 5.9.29 controller against the current code, it works. When I test my controller against your pull request, it also works.

makuser commented 5 years ago

@makuser I am on controller version 5.9 and everything works fine. Did you test the code change with your controller? When I test my 5.29 controller against the current code, it works. When I test my controller against your pull request, it also works.

Yes, I have tested it.

The reason why your code did not work is because you weren't sending valid json (at least in my environments that was the case) and more recent versions parse it differently. Previously a single quote might have worked, now only double quoted strings are allowed. This has always been the case for the JSON standard, but only now the UniFi controller actually checks this.

Test this yourself:

### Change it to your env
export username=ubnt
export password=ubnt
export baseurl=https://controller:8443

### Don't change from here on
cookie=$(mktemp)
curl_cmd="curl --tlsv1 --silent --cookie ${cookie} --cookie-jar ${cookie} --insecure "

echo "Double quoted:"
${curl_cmd} --data "{\"username\":\"$username\", \"password\":\"$password\"}" $baseurl/api/login
echo ""
echo "Single quoted:"
${curl_cmd} --data "{'username':'$username', 'password':'$password'}" $baseurl/api/login
echo ""

You've used the request.post / request.put with keyword argument json=value virtually everywhere else, but the login function does not use it, and instead uses the raw python dict (that you then converted to a string).

This (at least in with my python version) will convert the python dict to a single quoted JSON-similar string.

The right way would have been to json.dump(value) it first and send it then, OR simply use the keyword argument json=value directly, which is what my patch does.

Steltek commented 5 years ago

Can confirm that I have the same issue with homeassistant (relying on this library) and have had it over multiple 5.9 controller versions. The issue might not show up if you don't have any special characters in your password (though I did not test that).

finish06 commented 5 years ago

The issue automatically closed once the pull was merged. If you referenced the pull request, it was stated the pypi package would be updated soon.