mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-20 23:56:30 +00:00
Change access to API (#686)
* Update API.md * Update API.md * Update API.md * Update addons.py * Update addons.py * Update addons.py * Update addons.py * Update __init__.py * Update security.py * Update security.py * Update const.py * Update validate.py * Update __init__.py * Update validate.py * Update homeassistant.py * Update homeassistant.py * Update homeassistant.py * Update addon.py * Update addon.py * Update homeassistant.py * Fix lint * Fix lint * Backward combatibility * Make token more robust * Fix bug * Logic error * Fix access * fix valid
This commit is contained in:
parent
ff7f6a0b4c
commit
cecefd6972
18
API.md
18
API.md
@ -1,4 +1,4 @@
|
|||||||
# Hass.io Server
|
# Hass.io
|
||||||
|
|
||||||
## Hass.io RESTful API
|
## Hass.io RESTful API
|
||||||
|
|
||||||
@ -27,6 +27,9 @@ For access to API you need set the `X-HASSIO-KEY` they will be available for Add
|
|||||||
### Hass.io
|
### Hass.io
|
||||||
|
|
||||||
- GET `/supervisor/ping`
|
- GET `/supervisor/ping`
|
||||||
|
|
||||||
|
This API call don't need a token.
|
||||||
|
|
||||||
- GET `/supervisor/info`
|
- GET `/supervisor/info`
|
||||||
|
|
||||||
The addons from `addons` are only installed one.
|
The addons from `addons` are only installed one.
|
||||||
@ -412,6 +415,8 @@ Proxy to real websocket instance.
|
|||||||
|
|
||||||
### RESTful for API addons
|
### RESTful for API addons
|
||||||
|
|
||||||
|
If a add-on will call itself, you can use `/addons/self/...`.
|
||||||
|
|
||||||
- GET `/addons`
|
- GET `/addons`
|
||||||
|
|
||||||
Get all available addons.
|
Get all available addons.
|
||||||
@ -510,7 +515,6 @@ Get all available addons.
|
|||||||
"CONTAINER": "port|[ip, port]"
|
"CONTAINER": "port|[ip, port]"
|
||||||
},
|
},
|
||||||
"options": {},
|
"options": {},
|
||||||
"protected": "bool",
|
|
||||||
"audio_output": "null|0,0",
|
"audio_output": "null|0,0",
|
||||||
"audio_input": "null|0,0"
|
"audio_input": "null|0,0"
|
||||||
}
|
}
|
||||||
@ -518,6 +522,16 @@ Get all available addons.
|
|||||||
|
|
||||||
Reset custom network/audio/options, set it `null`.
|
Reset custom network/audio/options, set it `null`.
|
||||||
|
|
||||||
|
- POST `/addons/{addon}/security`
|
||||||
|
|
||||||
|
This function is not callable by itself.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"protected": "bool",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- POST `/addons/{addon}/start`
|
- POST `/addons/{addon}/start`
|
||||||
|
|
||||||
- POST `/addons/{addon}/stop`
|
- POST `/addons/{addon}/stop`
|
||||||
|
@ -50,6 +50,13 @@ class AddonManager(CoreSysAttributes):
|
|||||||
return addon
|
return addon
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def from_token(self, token):
|
||||||
|
"""Return an add-on from hassio token."""
|
||||||
|
for addon in self.list_addons:
|
||||||
|
if addon.is_installed and token == addon.hassio_token:
|
||||||
|
return addon
|
||||||
|
return None
|
||||||
|
|
||||||
async def load(self):
|
async def load(self):
|
||||||
"""Startup addon management."""
|
"""Startup addon management."""
|
||||||
self.data.reload()
|
self.data.reload()
|
||||||
|
@ -26,10 +26,11 @@ from ..const import (
|
|||||||
ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC,
|
ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC,
|
||||||
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES,
|
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES,
|
||||||
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS,
|
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS,
|
||||||
ATTR_PROTECTED,
|
ATTR_PROTECTED, ATTR_ACCESS_TOKEN,
|
||||||
SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT)
|
SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..docker.addon import DockerAddon
|
from ..docker.addon import DockerAddon
|
||||||
|
from ..utils import create_token
|
||||||
from ..utils.json import write_json_file, read_json_file
|
from ..utils.json import write_json_file, read_json_file
|
||||||
from ..utils.apparmor import adjust_profile
|
from ..utils.apparmor import adjust_profile
|
||||||
from ..exceptions import HostAppArmorError
|
from ..exceptions import HostAppArmorError
|
||||||
@ -172,6 +173,13 @@ class Addon(CoreSysAttributes):
|
|||||||
return self._data.user[self._id][ATTR_UUID]
|
return self._data.user[self._id][ATTR_UUID]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hassio_token(self):
|
||||||
|
"""Return access token for hass.io API."""
|
||||||
|
if self.is_installed:
|
||||||
|
return self._data.user[self._id].get(ATTR_ACCESS_TOKEN)
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
"""Return description of addon."""
|
"""Return description of addon."""
|
||||||
@ -686,6 +694,14 @@ class Addon(CoreSysAttributes):
|
|||||||
@check_installed
|
@check_installed
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""Set options and start addon."""
|
"""Set options and start addon."""
|
||||||
|
if await self.instance.is_running():
|
||||||
|
_LOGGER.warning("%s allready running!", self.slug)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Access Token
|
||||||
|
self._data.user[self._id][ATTR_ACCESS_TOKEN] = create_token()
|
||||||
|
self._data.save_data()
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
if not self.write_options():
|
if not self.write_options():
|
||||||
return False
|
return False
|
||||||
|
@ -19,7 +19,7 @@ from ..const import (
|
|||||||
ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY,
|
ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY,
|
||||||
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY,
|
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY,
|
||||||
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED,
|
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED,
|
||||||
ATTR_FULL_ACCESS,
|
ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN,
|
||||||
PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO,
|
PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO,
|
||||||
PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE,
|
PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE,
|
||||||
PRIVILEGED_SYS_RESOURCE)
|
PRIVILEGED_SYS_RESOURCE)
|
||||||
@ -168,6 +168,7 @@ SCHEMA_ADDON_USER = vol.Schema({
|
|||||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
|
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
|
||||||
vol.Match(r"^[0-9a-f]{32}$"),
|
vol.Match(r"^[0-9a-f]{32}$"),
|
||||||
|
vol.Optional(ATTR_ACCESS_TOKEN): vol.Match(r"^[0-9a-f]{64}$"),
|
||||||
vol.Optional(ATTR_OPTIONS, default=dict): dict,
|
vol.Optional(ATTR_OPTIONS, default=dict): dict,
|
||||||
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
|
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_BOOT):
|
vol.Optional(ATTR_BOOT):
|
||||||
|
@ -158,6 +158,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get('/addons/{addon}/logo', api_addons.logo),
|
web.get('/addons/{addon}/logo', api_addons.logo),
|
||||||
web.get('/addons/{addon}/changelog', api_addons.changelog),
|
web.get('/addons/{addon}/changelog', api_addons.changelog),
|
||||||
web.post('/addons/{addon}/stdin', api_addons.stdin),
|
web.post('/addons/{addon}/stdin', api_addons.stdin),
|
||||||
|
web.post('/addons/{addon}/security', api_addons.security),
|
||||||
web.get('/addons/{addon}/stats', api_addons.stats),
|
web.get('/addons/{addon}/stats', api_addons.stats),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -20,7 +20,8 @@ from ..const import (
|
|||||||
ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES,
|
ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES,
|
||||||
ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API,
|
ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API,
|
||||||
ATTR_FULL_ACCESS, ATTR_PROTECTED, ATTR_RATING,
|
ATTR_FULL_ACCESS, ATTR_PROTECTED, ATTR_RATING,
|
||||||
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT, REQUEST_FROM)
|
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT,
|
||||||
|
REQUEST_FROM)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..validate import DOCKER_PORTS, ALSA_DEVICE
|
from ..validate import DOCKER_PORTS, ALSA_DEVICE
|
||||||
from ..exceptions import APINotSupportedError
|
from ..exceptions import APINotSupportedError
|
||||||
@ -38,6 +39,10 @@ SCHEMA_OPTIONS = vol.Schema({
|
|||||||
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
|
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
|
||||||
vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE,
|
vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE,
|
||||||
vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE,
|
vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE,
|
||||||
|
})
|
||||||
|
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
|
SCHEMA_SECURITY = vol.Schema({
|
||||||
vol.Optional(ATTR_PROTECTED): vol.Boolean(),
|
vol.Optional(ATTR_PROTECTED): vol.Boolean(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -47,7 +52,13 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
def _extract_addon(self, request, check_installed=True):
|
def _extract_addon(self, request, check_installed=True):
|
||||||
"""Return addon, throw an exception it it doesn't exist."""
|
"""Return addon, throw an exception it it doesn't exist."""
|
||||||
addon = self.sys_addons.get(request.match_info.get('addon'))
|
addon_slug = request.match_info.get('addon')
|
||||||
|
|
||||||
|
# Lookup itself
|
||||||
|
if addon_slug == 'self':
|
||||||
|
addon_slug = request.get(REQUEST_FROM)
|
||||||
|
|
||||||
|
addon = self.sys_addons.get(addon_slug)
|
||||||
if not addon:
|
if not addon:
|
||||||
raise RuntimeError("Addon does not exist")
|
raise RuntimeError("Addon does not exist")
|
||||||
|
|
||||||
@ -157,11 +168,6 @@ class APIAddons(CoreSysAttributes):
|
|||||||
"""Store user options for addon."""
|
"""Store user options for addon."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
# Have Access
|
|
||||||
if addon.slug == request[REQUEST_FROM]:
|
|
||||||
_LOGGER.error("Add-on can't self modify his options!")
|
|
||||||
raise APINotSupportedError()
|
|
||||||
|
|
||||||
addon_schema = SCHEMA_OPTIONS.extend({
|
addon_schema = SCHEMA_OPTIONS.extend({
|
||||||
vol.Optional(ATTR_OPTIONS): vol.Any(None, addon.schema),
|
vol.Optional(ATTR_OPTIONS): vol.Any(None, addon.schema),
|
||||||
})
|
})
|
||||||
@ -180,6 +186,22 @@ class APIAddons(CoreSysAttributes):
|
|||||||
addon.audio_input = body[ATTR_AUDIO_INPUT]
|
addon.audio_input = body[ATTR_AUDIO_INPUT]
|
||||||
if ATTR_AUDIO_OUTPUT in body:
|
if ATTR_AUDIO_OUTPUT in body:
|
||||||
addon.audio_output = body[ATTR_AUDIO_OUTPUT]
|
addon.audio_output = body[ATTR_AUDIO_OUTPUT]
|
||||||
|
|
||||||
|
addon.save_data()
|
||||||
|
return True
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def security(self, request):
|
||||||
|
"""Store security options for addon."""
|
||||||
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
|
# Have Access
|
||||||
|
if addon.slug == request[REQUEST_FROM]:
|
||||||
|
_LOGGER.error("Can't self modify his security!")
|
||||||
|
raise APINotSupportedError()
|
||||||
|
|
||||||
|
body = await api_validate(SCHEMA_SECURITY, request)
|
||||||
|
|
||||||
if ATTR_PROTECTED in body:
|
if ATTR_PROTECTED in body:
|
||||||
_LOGGER.warning("Protected flag changing for %s!", addon.slug)
|
_LOGGER.warning("Protected flag changing for %s!", addon.slug)
|
||||||
addon.protected = body[ATTR_PROTECTED]
|
addon.protected = body[ATTR_PROTECTED]
|
||||||
|
@ -3,18 +3,28 @@ import logging
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from aiohttp.web import middleware
|
from aiohttp.web import middleware
|
||||||
from aiohttp.web_exceptions import HTTPUnauthorized
|
from aiohttp.web_exceptions import HTTPUnauthorized, HTTPForbidden
|
||||||
|
|
||||||
from ..const import HEADER_TOKEN, REQUEST_FROM
|
from ..const import HEADER_TOKEN, REQUEST_FROM
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
NO_SECURITY_CHECK = set((
|
NO_SECURITY_CHECK = re.compile(
|
||||||
re.compile(r"^/homeassistant/api/.*$"),
|
r"^(?:"
|
||||||
re.compile(r"^/homeassistant/websocket$"),
|
r"|/homeassistant/api/.*$"
|
||||||
re.compile(r"^/supervisor/ping$"),
|
r"|/homeassistant/websocket$"
|
||||||
))
|
r"|/supervisor/ping$"
|
||||||
|
r")$"
|
||||||
|
)
|
||||||
|
|
||||||
|
ADDONS_API_BYPASS = re.compile(
|
||||||
|
r"^(?:"
|
||||||
|
r"|/homeassistant/info$"
|
||||||
|
r"|/supervisor/info$"
|
||||||
|
r"|/addons(?:/self/[^/]+)?$"
|
||||||
|
r")$"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SecurityMiddleware(CoreSysAttributes):
|
class SecurityMiddleware(CoreSysAttributes):
|
||||||
@ -27,33 +37,50 @@ class SecurityMiddleware(CoreSysAttributes):
|
|||||||
@middleware
|
@middleware
|
||||||
async def token_validation(self, request, handler):
|
async def token_validation(self, request, handler):
|
||||||
"""Check security access of this layer."""
|
"""Check security access of this layer."""
|
||||||
|
request_from = None
|
||||||
hassio_token = request.headers.get(HEADER_TOKEN)
|
hassio_token = request.headers.get(HEADER_TOKEN)
|
||||||
|
|
||||||
# Ignore security check
|
# Ignore security check
|
||||||
for rule in NO_SECURITY_CHECK:
|
if NO_SECURITY_CHECK.match(request.path):
|
||||||
if rule.match(request.path):
|
_LOGGER.debug("Passthrough %s", request.path)
|
||||||
_LOGGER.debug("Passthrough %s", request.path)
|
return await handler(request)
|
||||||
return await handler(request)
|
|
||||||
|
# Not token
|
||||||
|
if not hassio_token:
|
||||||
|
_LOGGER.warning("No API token provided for %s", request.path)
|
||||||
|
raise HTTPUnauthorized()
|
||||||
|
|
||||||
# Home-Assistant
|
# Home-Assistant
|
||||||
if hassio_token == self.sys_homeassistant.uuid:
|
# UUID check need removed with 130
|
||||||
|
if hassio_token in (self.sys_homeassistant.uuid,
|
||||||
|
self.sys_homeassistant.hassio_token):
|
||||||
_LOGGER.debug("%s access from Home-Assistant", request.path)
|
_LOGGER.debug("%s access from Home-Assistant", request.path)
|
||||||
request[REQUEST_FROM] = 'homeassistant'
|
request_from = 'homeassistant'
|
||||||
|
|
||||||
# Host
|
# Host
|
||||||
if hassio_token == self.sys_machine_id:
|
if hassio_token == self.sys_machine_id:
|
||||||
_LOGGER.debug("%s access from Host", request.path)
|
_LOGGER.debug("%s access from Host", request.path)
|
||||||
request[REQUEST_FROM] = 'host'
|
request_from = 'host'
|
||||||
|
|
||||||
# Add-on
|
# Add-on
|
||||||
addon = self.sys_addons.from_uuid(hassio_token) \
|
addon = None
|
||||||
if hassio_token else None
|
if hassio_token and not request_from:
|
||||||
if addon:
|
addon = self.sys_addons.from_token(hassio_token)
|
||||||
_LOGGER.info("%s access from %s", request.path, addon.slug)
|
# Need removed with 130
|
||||||
request[REQUEST_FROM] = addon.slug
|
if not addon:
|
||||||
|
addon = self.sys_addons.from_uuid(hassio_token)
|
||||||
|
|
||||||
if request.get(REQUEST_FROM):
|
# Check Add-on API access
|
||||||
|
if addon and addon.access_hassio_api:
|
||||||
|
_LOGGER.info("%s access from %s", request.path, addon.slug)
|
||||||
|
request_from = addon.slug
|
||||||
|
elif addon and ADDONS_API_BYPASS.match(request.path):
|
||||||
|
_LOGGER.debug("Passthrough %s from %s", request.path, addon.slug)
|
||||||
|
request_from = addon.slug
|
||||||
|
|
||||||
|
if request_from:
|
||||||
|
request[REQUEST_FROM] = request_from
|
||||||
return await handler(request)
|
return await handler(request)
|
||||||
|
|
||||||
_LOGGER.warning("Invalid token for access %s", request.path)
|
_LOGGER.warning("Invalid token for access %s", request.path)
|
||||||
raise HTTPUnauthorized()
|
raise HTTPForbidden()
|
||||||
|
@ -178,6 +178,7 @@ ATTR_HASSOS_CLI = 'hassos_cli'
|
|||||||
ATTR_VERSION_CLI = 'version_cli'
|
ATTR_VERSION_CLI = 'version_cli'
|
||||||
ATTR_VERSION_CLI_LATEST = 'version_cli_latest'
|
ATTR_VERSION_CLI_LATEST = 'version_cli_latest'
|
||||||
ATTR_REFRESH_TOKEN = 'refresh_token'
|
ATTR_REFRESH_TOKEN = 'refresh_token'
|
||||||
|
ATTR_ACCESS_TOKEN = 'access_token'
|
||||||
ATTR_DOCKER_API = 'docker_api'
|
ATTR_DOCKER_API = 'docker_api'
|
||||||
ATTR_FULL_ACCESS = 'full_access'
|
ATTR_FULL_ACCESS = 'full_access'
|
||||||
ATTR_PROTECTED = 'protected'
|
ATTR_PROTECTED = 'protected'
|
||||||
|
@ -92,7 +92,7 @@ class DockerAddon(DockerInterface):
|
|||||||
return {
|
return {
|
||||||
**addon_env,
|
**addon_env,
|
||||||
ENV_TIME: self.sys_timezone,
|
ENV_TIME: self.sys_timezone,
|
||||||
ENV_TOKEN: self.addon.uuid,
|
ENV_TOKEN: self.addon.hassio_token,
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -62,7 +62,7 @@ class DockerHomeAssistant(DockerInterface):
|
|||||||
environment={
|
environment={
|
||||||
'HASSIO': self.sys_docker.network.supervisor,
|
'HASSIO': self.sys_docker.network.supervisor,
|
||||||
ENV_TIME: self.sys_timezone,
|
ENV_TIME: self.sys_timezone,
|
||||||
ENV_TOKEN: self.sys_homeassistant.uuid,
|
ENV_TOKEN: self.sys_homeassistant.hassio_token,
|
||||||
},
|
},
|
||||||
volumes={
|
volumes={
|
||||||
str(self.sys_config.path_extern_homeassistant):
|
str(self.sys_config.path_extern_homeassistant):
|
||||||
|
@ -16,14 +16,14 @@ import attr
|
|||||||
from .const import (
|
from .const import (
|
||||||
FILE_HASSIO_HOMEASSISTANT, ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_UUID,
|
FILE_HASSIO_HOMEASSISTANT, ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_UUID,
|
||||||
ATTR_BOOT, ATTR_PASSWORD, ATTR_PORT, ATTR_SSL, ATTR_WATCHDOG,
|
ATTR_BOOT, ATTR_PASSWORD, ATTR_PORT, ATTR_SSL, ATTR_WATCHDOG,
|
||||||
ATTR_WAIT_BOOT, ATTR_REFRESH_TOKEN,
|
ATTR_WAIT_BOOT, ATTR_REFRESH_TOKEN, ATTR_ACCESS_TOKEN,
|
||||||
HEADER_HA_ACCESS)
|
HEADER_HA_ACCESS)
|
||||||
from .coresys import CoreSysAttributes
|
from .coresys import CoreSysAttributes
|
||||||
from .docker.homeassistant import DockerHomeAssistant
|
from .docker.homeassistant import DockerHomeAssistant
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
HomeAssistantUpdateError, HomeAssistantError, HomeAssistantAPIError,
|
HomeAssistantUpdateError, HomeAssistantError, HomeAssistantAPIError,
|
||||||
HomeAssistantAuthError)
|
HomeAssistantAuthError)
|
||||||
from .utils import convert_to_ascii, process_lock
|
from .utils import convert_to_ascii, process_lock, create_token
|
||||||
from .utils.json import JsonConfig
|
from .utils.json import JsonConfig
|
||||||
from .validate import SCHEMA_HASS_CONFIG
|
from .validate import SCHEMA_HASS_CONFIG
|
||||||
|
|
||||||
@ -185,6 +185,11 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
"""Return a UUID of this HomeAssistant."""
|
"""Return a UUID of this HomeAssistant."""
|
||||||
return self._data[ATTR_UUID]
|
return self._data[ATTR_UUID]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hassio_token(self):
|
||||||
|
"""Return a access token for Hass.io API."""
|
||||||
|
return self._data.get(ATTR_ACCESS_TOKEN)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def refresh_token(self):
|
def refresh_token(self):
|
||||||
"""Return the refresh token to authenticate with HomeAssistant."""
|
"""Return the refresh token to authenticate with HomeAssistant."""
|
||||||
@ -277,6 +282,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
async def _start(self):
|
async def _start(self):
|
||||||
"""Start HomeAssistant docker & wait."""
|
"""Start HomeAssistant docker & wait."""
|
||||||
|
if await self.instance.is_running():
|
||||||
|
_LOGGER.warning("HomeAssistant allready running!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create new API token
|
||||||
|
self._data[ATTR_ACCESS_TOKEN] = create_token()
|
||||||
|
self.save_data()
|
||||||
|
|
||||||
if not await self.instance.run():
|
if not await self.instance.run():
|
||||||
raise HomeAssistantError()
|
raise HomeAssistantError()
|
||||||
await self._block_till_run()
|
await self._block_till_run()
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
"""Tools file for HassIO."""
|
"""Tools file for HassIO."""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
import uuid
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))")
|
RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))")
|
||||||
@ -12,6 +14,11 @@ def convert_to_ascii(raw):
|
|||||||
return RE_STRING.sub("", raw.decode())
|
return RE_STRING.sub("", raw.decode())
|
||||||
|
|
||||||
|
|
||||||
|
def create_token():
|
||||||
|
"""Create token for API access."""
|
||||||
|
return hashlib.sha256(uuid.uuid4().bytes).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def process_lock(method):
|
def process_lock(method):
|
||||||
"""Wrap function with only run once."""
|
"""Wrap function with only run once."""
|
||||||
async def wrap_api(api, *args, **kwargs):
|
async def wrap_api(api, *args, **kwargs):
|
||||||
|
@ -10,6 +10,7 @@ from .const import (
|
|||||||
ATTR_ADDONS_CUSTOM_LIST, ATTR_PASSWORD, ATTR_HOMEASSISTANT, ATTR_HASSIO,
|
ATTR_ADDONS_CUSTOM_LIST, ATTR_PASSWORD, ATTR_HOMEASSISTANT, ATTR_HASSIO,
|
||||||
ATTR_BOOT, ATTR_LAST_BOOT, ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG,
|
ATTR_BOOT, ATTR_LAST_BOOT, ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG,
|
||||||
ATTR_WAIT_BOOT, ATTR_UUID, ATTR_REFRESH_TOKEN, ATTR_HASSOS_CLI,
|
ATTR_WAIT_BOOT, ATTR_UUID, ATTR_REFRESH_TOKEN, ATTR_HASSOS_CLI,
|
||||||
|
ATTR_ACCESS_TOKEN,
|
||||||
CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV)
|
CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV)
|
||||||
|
|
||||||
|
|
||||||
@ -84,6 +85,7 @@ DOCKER_PORTS = vol.Schema({
|
|||||||
SCHEMA_HASS_CONFIG = vol.Schema({
|
SCHEMA_HASS_CONFIG = vol.Schema({
|
||||||
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
|
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
|
||||||
vol.Match(r"^[0-9a-f]{32}$"),
|
vol.Match(r"^[0-9a-f]{32}$"),
|
||||||
|
vol.Optional(ATTR_ACCESS_TOKEN): vol.Match(r"^[0-9a-f]{64}$"),
|
||||||
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
||||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): DOCKER_IMAGE,
|
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): DOCKER_IMAGE,
|
||||||
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str),
|
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user