Skip to content

Commit

Permalink
ssl verification
Browse files Browse the repository at this point in the history
  • Loading branch information
imhotep committed Nov 2, 2023
1 parent 8893d0f commit 598e49e
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 40 deletions.
6 changes: 1 addition & 5 deletions custom_components/unifi_access/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,9 @@

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Unifi Access from a config entry."""
api = UnifiAccessApi(entry.data["host"])
api = UnifiAccessApi(entry.data["host"], entry.data["verify_ssl"])
api.set_api_token(entry.data["api_token"])
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = api
# TODO 1. Create API instance
# TODO 2. Validate the API connection (and authentication)
# TODO 3. Store an API object for your platforms to access
# hass.data[DOMAIN][entry.entry_id] = MyApi(...)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

Expand Down
40 changes: 26 additions & 14 deletions custom_components/unifi_access/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .const import DOORS_URL, DOOR_UNLOCK_URL, UNIFI_ACCESS_API_PORT

from requests import request
from requests.exceptions import SSLError
import logging

from homeassistant.helpers.update_coordinator import (
Expand All @@ -14,12 +15,8 @@
from datetime import timedelta
import urllib3

urllib3.disable_warnings()

from urllib.parse import urlparse

import asyncio

_LOGGER = logging.getLogger(__name__)


Expand All @@ -37,8 +34,13 @@ class UnifiAccessApi:
TODO Remove this placeholder class and replace with things from your PyPI package.
"""

def __init__(self, host: str) -> None:
def __init__(self, host: str, verify_ssl: bool = False) -> None:
"""Initialize."""
self.verify_ssl = verify_ssl
if self.verify_ssl == False:
_LOGGER.warning(f"SSL Verification disabled for {host}")
urllib3.disable_warnings()

hostname = (
urlparse(host).hostname if urlparse(host).hostname else host.split(":")[0]
)
Expand All @@ -55,7 +57,6 @@ def set_api_token(self, api_token):

def update(self):
# _LOGGER.info(f"Getting door updates from Unifi Access {self.host}")

data = self._make_http_request(f"{self.host}{DOORS_URL}")

doors: list[UnifiAccessDoor] = []
Expand All @@ -77,16 +78,27 @@ def update_door(self, door_id: int) -> None:
_LOGGER.info(f"Getting door update from Unifi Access with id {door_id}")
self._make_http_request(f"{self.host}{DOORS_URL}/{door_id}")

def authenticate(self, api_token: str) -> bool:
def authenticate(self, api_token: str) -> str:
"""Test if we can authenticate with the host."""
self.set_api_token(api_token)
_LOGGER.info(f"Authenticating {self.host}")
try:
self.update()
except ApiError:
_LOGGER.error(f"Error authenticating {self.host}")
return False
return True
_LOGGER.error(
f"Could perform action with {self.host}. Check host and token."
)
return "api_error"
except ApiAuthError:
_LOGGER.error(
f"Could not authenticate with {self.host}. Check host and token."
)
return "api_auth_error"
except SSLError:
_LOGGER.error(f"Error validating SSL Certificate for {self.host}.")
return "ssl_error"

return "ok"

def unlock_door(self, door_id: str) -> None:
"""Test if we can authenticate with the host."""
Expand All @@ -100,14 +112,14 @@ def _make_http_request(self, url, method="GET") -> None:
method,
url,
headers=self._headers,
verify=False,
verify=self.verify_ssl,
timeout=10,
)

if r.status_code == 401:
raise ApiAuthError

if r.status_code != 200:
_LOGGER.error(
f"Could not authenticate with {self.host}. Check host and token. URL: {url}"
)
raise ApiError

response = r.json()
Expand Down
40 changes: 23 additions & 17 deletions custom_components/unifi_access/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
{
vol.Required("host"): str,
vol.Required("api_token"): str,
vol.Required("verify_ssl"): bool,
}
)

Expand All @@ -30,23 +31,22 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
# TODO validate the data can be used to set up a connection.

# If your PyPI package is not built with async, pass your methods
# to the executor:
# await hass.async_add_executor_job(
# your_validate_func, data["username"], data["password"]
# )
api = UnifiAccessApi(data["host"], data["verify_ssl"])

api = UnifiAccessApi(data["host"])
auth_response = await hass.async_add_executor_job(
api.authenticate, data["api_token"]
)

if not await hass.async_add_executor_job(api.authenticate, data["api_token"]):
raise InvalidAuth

# If you cannot connect:
# throw CannotConnect
# If the authentication is wrong:
# InvalidAuth
match auth_response:
case "api_error":
raise CannotConnect
case "api_auth_error":
raise InvalidApiKey
case "ssl_error":
raise SSLVerificationError
case "ok":
_LOGGER.info("Unifi Access API authorized")

# Return info that you want to store in the config entry.
return {"title": "Unifi Access Doors"}
Expand All @@ -69,8 +69,10 @@ async def async_step_user(
info = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
except InvalidApiKey:
errors["base"] = "invalid_api_key"
except SSLVerificationError:
errors["base"] = "ssl_error"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
Expand All @@ -88,5 +90,9 @@ class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""


class InvalidAuth(HomeAssistantError):
class SSLVerificationError(HomeAssistantError):
"""Error to indicate there is failed SSL certificate verification."""


class InvalidApiKey(HomeAssistantError):
"""Error to indicate there is invalid auth."""
6 changes: 4 additions & 2 deletions custom_components/unifi_access/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"api_token": "[%key:common::config_flow::data::api_token%]"
"api_token": "[%key:common::config_flow::data::api_token%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]",
"ssl_error": "Error validating SSL certificate",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
Expand Down
6 changes: 4 additions & 2 deletions custom_components/unifi_access/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
},
"error": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"invalid_api_key": "Invalid API key",
"ssl_error": "Error validating SSL certificate",
"unknown": "Unexpected error"
},
"step": {
"user": {
"data": {
"api_token": "API Token",
"host": "Host"
"host": "Host",
"verify_ssl": "Verify SSL certificate"
}
}
}
Expand Down

0 comments on commit 598e49e

Please sign in to comment.