diff --git a/API.md b/API.md index 5123745bf..e6720bd71 100644 --- a/API.md +++ b/API.md @@ -4,7 +4,7 @@ Interface for Home Assistant to control things from supervisor. -On error: +On error / Code 400: ```json { @@ -13,7 +13,7 @@ On error: } ``` -On success: +On success / Code 200: ```json { @@ -22,6 +22,8 @@ On success: } ``` +For access to API you need set the `X-HASSIO-KEY` they will be available for Add-ons/HomeAssistant with envoriment `HASSIO_TOKEN`. + ### Hass.io - GET `/supervisor/ping` @@ -99,44 +101,7 @@ Output is the raw docker log. } ``` -### Security - -- GET `/security/info` - -```json -{ - "initialize": "bool", - "totp": "bool" -} -``` - -- POST `/security/options` - -```json -{ - "password": "xy" -} -``` - -- POST `/security/totp` - -```json -{ - "password": "xy" -} -``` - -Return QR-Code - -- POST `/security/session` -```json -{ - "password": "xy", - "totp": "null|123456" -} -``` - -### Backup/Snapshot +### Snapshot - GET `/snapshots` diff --git a/hassio/api/__init__.py b/hassio/api/__init__.py index 5601d469d..39b01cfc8 100644 --- a/hassio/api/__init__.py +++ b/hassio/api/__init__.py @@ -10,7 +10,6 @@ from .host import APIHost from .network import APINetwork from .proxy import APIProxy from .supervisor import APISupervisor -from .security import APISecurity from .snapshots import APISnapshots from ..coresys import CoreSysAttributes @@ -38,7 +37,6 @@ class RestAPI(CoreSysAttributes): self._register_panel() self._register_addons() self._register_snapshots() - self._register_security() self._register_network() def _register_host(self): @@ -138,16 +136,6 @@ class RestAPI(CoreSysAttributes): self.webapp.router.add_post('/addons/{addon}/stdin', api_addons.stdin) self.webapp.router.add_get('/addons/{addon}/stats', api_addons.stats) - def _register_security(self): - """Register security function.""" - api_security = APISecurity() - api_security.coresys = self.coresys - - self.webapp.router.add_get('/security/info', api_security.info) - self.webapp.router.add_post('/security/options', api_security.options) - self.webapp.router.add_post('/security/totp', api_security.totp) - self.webapp.router.add_post('/security/session', api_security.session) - def _register_snapshots(self): """Register snapshots function.""" api_snapshots = APISnapshots() diff --git a/hassio/api/security.py b/hassio/api/security.py deleted file mode 100644 index cbb56b0b0..000000000 --- a/hassio/api/security.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Init file for HassIO security rest api.""" -from datetime import datetime, timedelta -import io -import logging -import hashlib -import os - -from aiohttp import web -import voluptuous as vol -import pyotp -import pyqrcode - -from .utils import api_process, api_validate, hash_password -from ..const import ATTR_INITIALIZE, ATTR_PASSWORD, ATTR_TOTP, ATTR_SESSION -from ..coresys import CoreSysAttributes - -_LOGGER = logging.getLogger(__name__) - -SCHEMA_PASSWORD = vol.Schema({ - vol.Required(ATTR_PASSWORD): vol.Coerce(str), -}) - -SCHEMA_SESSION = SCHEMA_PASSWORD.extend({ - vol.Optional(ATTR_TOTP, default=None): vol.Coerce(str), -}) - - -class APISecurity(CoreSysAttributes): - """Handle rest api for security functions.""" - - def _check_password(self, body): - """Check if password is valid and security is initialize.""" - if not self._config.security_initialize: - raise RuntimeError("First set a password") - - password = hash_password(body[ATTR_PASSWORD]) - if password != self._config.security_password: - raise RuntimeError("Wrong password") - - @api_process - async def info(self, request): - """Return host information.""" - return { - ATTR_INITIALIZE: self._config.security_initialize, - ATTR_TOTP: self._config.security_totp is not None, - } - - @api_process - async def options(self, request): - """Set options / password.""" - body = await api_validate(SCHEMA_PASSWORD, request) - - if self._config.security_initialize: - raise RuntimeError("Password is already set!") - - self._config.security_password = hash_password(body[ATTR_PASSWORD]) - self._config.security_initialize = True - return True - - @api_process - async def totp(self, request): - """Set and initialze TOTP.""" - body = await api_validate(SCHEMA_PASSWORD, request) - self._check_password(body) - - # generate TOTP - totp_init_key = pyotp.random_base32() - totp = pyotp.TOTP(totp_init_key) - - # init qrcode - buff = io.BytesIO() - - qrcode = pyqrcode.create(totp.provisioning_uri("Hass.IO")) - qrcode.svg(buff) - - # finish - self._config.security_totp = totp_init_key - return web.Response(body=buff.getvalue(), content_type='image/svg+xml') - - @api_process - async def session(self, request): - """Set and initialze session.""" - body = await api_validate(SCHEMA_SESSION, request) - self._check_password(body) - - # check TOTP - if self._config.security_totp: - totp = pyotp.TOTP(self._config.security_totp) - if body[ATTR_TOTP] != totp.now(): - raise RuntimeError("Invalid TOTP token!") - - # create session - valid_until = datetime.now() + timedelta(days=1) - session = hashlib.sha256(os.urandom(54)).hexdigest() - - # store session - self._config.add_security_session(session, valid_until) - return {ATTR_SESSION: session} diff --git a/hassio/config.py b/hassio/config.py index 3df086e07..6e50f0bc2 100644 --- a/hassio/config.py +++ b/hassio/config.py @@ -5,8 +5,7 @@ import os from pathlib import Path, PurePath from .const import ( - FILE_HASSIO_CONFIG, HASSIO_DATA, ATTR_SECURITY, ATTR_SESSIONS, - ATTR_PASSWORD, ATTR_TOTP, ATTR_TIMEZONE, ATTR_ADDONS_CUSTOM_LIST, + FILE_HASSIO_CONFIG, HASSIO_DATA, ATTR_TIMEZONE, ATTR_ADDONS_CUSTOM_LIST, ATTR_AUDIO_INPUT, ATTR_AUDIO_OUTPUT, ATTR_LAST_BOOT, ATTR_WAIT_BOOT) from .utils.dt import parse_datetime from .utils.json import JsonConfig @@ -181,59 +180,6 @@ class CoreConfig(JsonConfig): self._data[ATTR_ADDONS_CUSTOM_LIST].remove(repo) self.save() - @property - def security_initialize(self): - """Return is security was initialize.""" - return self._data[ATTR_SECURITY] - - @security_initialize.setter - def security_initialize(self, value): - """Set is security initialize.""" - self._data[ATTR_SECURITY] = value - self.save() - - @property - def security_totp(self): - """Return the TOTP key.""" - return self._data.get(ATTR_TOTP) - - @security_totp.setter - def security_totp(self, value): - """Set the TOTP key.""" - self._data[ATTR_TOTP] = value - self.save() - - @property - def security_password(self): - """Return the password key.""" - return self._data.get(ATTR_PASSWORD) - - @security_password.setter - def security_password(self, value): - """Set the password key.""" - self._data[ATTR_PASSWORD] = value - self.save() - - @property - def security_sessions(self): - """Return api sessions.""" - return { - session: parse_datetime(until) for - session, until in self._data[ATTR_SESSIONS].items() - } - - def add_security_session(self, session, valid): - """Set the a new session.""" - self._data[ATTR_SESSIONS].update( - {session: valid.isoformat()} - ) - self.save() - - def drop_security_session(self, session): - """Delete the a session.""" - self._data[ATTR_SESSIONS].pop(session, None) - self.save() - @property def audio_output(self): """Return ALSA audio output card,dev.""" diff --git a/hassio/tasks.py b/hassio/tasks.py index 2ca22ef86..277064781 100644 --- a/hassio/tasks.py +++ b/hassio/tasks.py @@ -1,6 +1,5 @@ """Multible tasks.""" import asyncio -from datetime import datetime import logging from .coresys import CoreSysAttributes @@ -22,8 +21,6 @@ class Tasks(CoreSysAttributes): RUN_WATCHDOG_HOMEASSISTANT_DOCKER = 15 RUN_WATCHDOG_HOMEASSISTANT_API = 300 - RUN_CLEANUP_API_SESSIONS = 900 - def __init__(self, coresys): """Initialize Tasks.""" self.coresys = coresys @@ -55,13 +52,6 @@ class Tasks(CoreSysAttributes): _LOGGER.info("All core tasks are scheduled") - async def _cleanup_sessions(self): - """Cleanup old api sessions.""" - now = datetime.now() - for session, until_valid in self._config.security_sessions.items(): - if now >= until_valid: - self._config.drop_security_session(session) - async def _update_addons(self): """Check if a update is available of a addon and update it.""" tasks = [] diff --git a/hassio/validate.py b/hassio/validate.py index 0c56d2405..71b843d81 100644 --- a/hassio/validate.py +++ b/hassio/validate.py @@ -5,11 +5,10 @@ import voluptuous as vol import pytz from .const import ( - ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_SESSIONS, ATTR_PASSWORD, ATTR_TOTP, - ATTR_SECURITY, ATTR_BETA_CHANNEL, ATTR_TIMEZONE, ATTR_ADDONS_CUSTOM_LIST, - ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT, ATTR_HOMEASSISTANT, ATTR_HASSIO, - ATTR_BOOT, ATTR_LAST_BOOT, ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, - ATTR_WAIT_BOOT, ATTR_UUID) + ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_BETA_CHANNEL, ATTR_TIMEZONE, + ATTR_ADDONS_CUSTOM_LIST, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT, + ATTR_PASSWORD, ATTR_HOMEASSISTANT, ATTR_HASSIO, ATTR_BOOT, ATTR_LAST_BOOT, + ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, ATTR_WAIT_BOOT, ATTR_UUID) NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) @@ -87,11 +86,6 @@ SCHEMA_HASSIO_CONFIG = vol.Schema({ vol.Optional(ATTR_ADDONS_CUSTOM_LIST, default=[ "https://github.com/hassio-addons/repository", ]): [vol.Url()], - vol.Optional(ATTR_SECURITY, default=False): vol.Boolean(), - vol.Optional(ATTR_TOTP): vol.Coerce(str), - vol.Optional(ATTR_PASSWORD): vol.Coerce(str), - vol.Optional(ATTR_SESSIONS, default={}): - vol.Schema({vol.Coerce(str): vol.Coerce(str)}), vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_CHANNEL, vol.Optional(ATTR_AUDIO_INPUT): ALSA_CHANNEL, vol.Optional(ATTR_WAIT_BOOT, default=5): WAIT_BOOT, diff --git a/misc/hassio.png b/misc/hassio.png index 22c013125..3ccbf7ae9 100644 Binary files a/misc/hassio.png and b/misc/hassio.png differ diff --git a/misc/hassio.xml b/misc/hassio.xml index 42fa0b23c..685610ae1 100644 --- a/misc/hassio.xml +++ b/misc/hassio.xml @@ -1 +1 @@ -5Vptc6M2EP41/ng3gHj9mPiSy820c5n6Q3sfsVBsNTJyhYid/voKkABZkOBY+KYtmYnR6pVn99ld1l6A5e74laX77a80Q2ThOdlxAb4sPC8OY/G/Erw2At9xG8GG4awR9QQr/DeSQkdKS5yhQhvIKSUc73UhpHmOINdkKWP0oA97okTfdZ9ukCFYwZSY0t9xxrdS6oZJ1/GA8GYrt469sOlYp/B5w2iZy/0WHniqr6Z7l6q15IMW2zSjh54I3C3AklHKm7vdcYlIBa2CrZl3P9LbnpuhnE+Z4DUTXlJSInXikIipt09UrCAOyF8lKOFfJVUdn4paZTdigNjtKD5ERw206DtIYKrenLJdSrrJ4m5TfX5fqX3E2Zqtmg4JS7urd9hijlb7FFbtg7A2MWjLd0S03Oo0mJAlJZTVowXYKIRQyAvO6DPq9Tj1Jc+/kutLvF4Q4+g4CqHbKkbYO6I7xNmrGKImJKCZIm09SKRuD53l+Arobc9oQjkulca6aZfuFCZupM6G9QcM/X3LcaW31WvB0e5CNGGG1vF6CE0QggRkrb7sAhhNBNCzAKBvAPiFwmfELkUOokCQ/trI+SZy3hBywAJyoYHcw9JArXaFqJpRUe9MLscQDXN5HQd+4NjB0A8DHcPQxDBwTAgDCxAmBl4oE3FINinjW7qheUruOumtjmgPPXTE/I9K/DkKZPOH6srFwZq+QDV/yBX+RJy/ygiclpwKUbfxL5Tu5RrNUavzvQ20eBxaMihHRTJ4p2yDeM9uTHUwRFKOX/TVLwFX5RK20fXeQDcB3im+deMRMSweALGfBbp/JdCj0Xxi3UX48xIMN6wSjNMEYlXuEXvBhXAJagOm+h7Sovj2fTTBaMXr0aSjMwP3fbdluKflMgybVEN3aFmA4sy347ZAoLstMJB1uPGA33JtRE3Xm4Nbbo9Yyou13NJ4VbuxeUnkqveOHouiK7EIzOO6NHh1dE/iQtc89VyFwIPfVK9YQgCJYBqGSnyPidpzqm5QnpmLCWFvqcFMfrm0qlgvvlZQUm8cvaxJrPLpRjy6wLByU9dxRSmKn6CtLFR3Rd5A/t56HS1/9224ovDKXHE/O3qQ/+zG8aWBfiKtPmjxwLR4d0Sn1i3enyVUSJ30srCJCPYcTk5zpHmb8xQ2Vl+AJXtp+WpPYdeKPa5ZUrjJMpoXhhqLbbqvbveMQlQU73sn3ZVN9lX34qr9fZMTCt07XhiBxANhEHtx7PhgpqRqyJN5bmB6ssSCI1O1nDmJ0rVOHdWlqYAkU59uc7zoXEAAOfWR4vq9Q5WqneE0Wq3Q0FJO6hdSz1ynobKxTm0U7dNMs5PYJCjk1KxYKX6WO9IMALcVOzAUyKdrRB5pgTmmuRiyppzTnRhAqo7btoitVVbrMna3xg3Bm2oup+fRvCvEnpZu5QYWiHxS0wEDNR0wkJBYqciaNJ5AUifSWOq/x1LX5OgUOk5Ity8PgO97LQshEng/L0SqvXsMPBwOpvcmBO+LWg2SiZDQMrs4Tl6FQInuz3xnIKeP5iovgLcLo9K4P5DEn8mRmTLEXqzt3hyaQ3qj0faDNPFNmjTmaz+S+icmc+pN7YVAMP6tjfNQrkcjIUzZ5fQL62uAfkH1Z4d+CThJJ4boN1TdsxLBopnY17f7yGaWOT9lP8i+YAb2TVZjYJDkK+bbuekxFp2QmwUomocevnppvQo94v9LcEpCnaOR5dgU/idjk/m9+G9oX71qUYbReBXl30s+Vf6dgXyi2f0WqlFG93szcPcP \ No newline at end of file +5VrLcqM4FP0aLzsFiOcycefRVTPVqfFippdYKLYmMvII4cd8/QiQDEKQ4Bicnmp7Yevqybk691zJnoH55vDI4u36d5ogMnOs5DADX2eOY1vAER+F5VhZ3DCoDCuGE9moNizwv0j1lNYcJyjTGnJKCcdb3QhpmiLINVvMGN3rzV4o0WfdxitkGBYwJqb1T5zwtbTaflRXPCG8WsupQ8evKpYxfF0xmqdyvpkDXspXVb2J1VjyQbN1nNB9wwTuZ2DOKOXVt81hjkiBrYKt6vfQU3taN0MpH9JB+mkXkxypFftEdL17oWIEsUB+lKD4/+RUVXzJSpfdigZitoP4EBUl0KJuL4EpalPKNjGpO4tvq+Lz+0LNI9ZWTVVVSFhOszr7NeZosY1hUd6L7SYarfmGiJJdrAYTMqeEsrK1ABv5EAp7xhl9RY0aq3zJ9S/k+B14SdMOMY4ODZPE7xHRDeLsKJqo2ghUXeRe9yLp2329c1wF9LqxaXzZLpabdXUaunaY+CJ91u0/YPjvW4oLvy2OGUebC9GECVqGyy40gQ8ikJz8NS6AwUAAnREAdA0Av1L4itilyEHkCdJfGznXRM7pQg6MgJxvIPc0N1ArQyEqehTUO5PLIUTdXF6GnutZ42Do+p6OoW9i6FkmhN4IEEYGXigROiSLlPE1XdE0Jve19U5HtIEeOmD+V2G+CTxZ/KGqUrGwqs5TxR9yhL8R50epwHHOqTDVE/9G6VaO0Qt1RnMG5fKlyvOYrRDXtknxYG+6gyESc7zTBfgScFUuMTa6zhvoRiLxaeFbFp4Rw+IBELsS6O5ngR705hPLWuHPSzBsv0gw2gnEIt8itsOZCAlqAqbqnuIs+/a9N8E4mZe9SUe9Dez3w5YRnuZz369SDT2gJR4KE3ecsAU8PWyBjqzDDjvilj2GatrOFNyyG8RSUezELY1XZRgbSqJMMIPfFqcCYYBEbA4MlfkBE7WKQVyz1WmkQbbgs8gGpolwmhd0J7Tkoy62A9xAzIe6EKWJOZgwNobqTPjn80sc64Sfpl0qHjSSKzHKl1vx6ALDIppdJ2LFKHyBYyWresRyOtL8U3DS0nx3jIjlX5kr9o2l5wI3dhhemg8MpFWDLilNkcaVN9NmjRHAZITal9dnhDuJ4kifNZK5kRAe7tC+awqYs92Jzx922Kdpk2veTHzAgRoIvd4832d9InK52zrx/rjrrqE1pqduk4SmmeGvbB1vi69bRiHKsvd1RhelwarzIF6lcleHAMFSy/EDEDnA90InDC0XTJRFd2mSY3umJkUjSJK6vJsypNWltuRcmtTJsNck2Sgn2/FClez6THF50JQuV2ei9rlJjVDRUnZyGjfnZ45TUdkYp9wUp6cZtk9Ck6CQU/OKUvEz35CqAbgrqIChQD5eIvJMM8wxTUWTJeWcbkQDUlTcnX610K7Sy98t6jFuCV4VfTk9j+b1zXv7rl5OMAKRW5d4oOMSD3SklqNcwZs0HkBSK9BY6r7HUtvk6BA6XkXzztTxQYqofkH8KZIZtZgGA/f7vRm9CcHbrHSDZCIkNE8u1smrECjS45lrdZzOgqnuk8DbN+Fyc3/gOHYmRybK5RtaW58Bq0U6vWo7jCauSRO1WydXUre1ZdrRdDwJBP0/01lP+bJXCWHMLqefX7466OcV73HoF4FWOtFFv67r3FEULJiIfc19H4yZZU5P2WHs867BvsFu9AySPGK+npoefeqE7MRDwTT0cNWh9Sr0CH8VcYp8naPBZdrk/xraZP4R4g+0LY5alGHUf4vy/yWfusifgHyiWP/5rXJG/Q9DcP8f \ No newline at end of file diff --git a/misc/security.png b/misc/security.png deleted file mode 100644 index 8343b0e84..000000000 Binary files a/misc/security.png and /dev/null differ diff --git a/misc/security.xml b/misc/security.xml deleted file mode 100644 index 162e082f7..000000000 --- a/misc/security.xml +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/setup.py b/setup.py index 7b17c82c1..ac212a027 100644 --- a/setup.py +++ b/setup.py @@ -46,8 +46,6 @@ setup( 'colorlog', 'voluptuous', 'gitpython', - 'pyotp', - 'pyqrcode', 'pytz', 'pyudev' ]