mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Allow for running Home Assistant without password
This commit is contained in:
parent
50eecd11c1
commit
ed05ff6fd9
@ -45,7 +45,7 @@ pip3 install -r requirements.txt
|
|||||||
python3 -m homeassistant --open-ui
|
python3 -m homeassistant --open-ui
|
||||||
```
|
```
|
||||||
|
|
||||||
This will start the Home Assistant server and create an initial configuration file in `config/home-assistant.conf` that is setup for demo mode. It will launch its web interface on [http://127.0.0.1:8123](http://127.0.0.1:8123). The default password is 'password'.
|
This will start the Home Assistant server and launch its webinterface. By default Home Assistant looks for the configuration file `config/home-assistant.conf`. A standard configuration file will be written if none exists.
|
||||||
|
|
||||||
If you're using Docker, you can use
|
If you're using Docker, you can use
|
||||||
|
|
||||||
@ -53,6 +53,8 @@ If you're using Docker, you can use
|
|||||||
docker run -d --name="home-assistant" -v /path/to/homeassistant/config:/config -v /etc/localtime:/etc/localtime:ro --net=host balloob/home-assistant
|
docker run -d --name="home-assistant" -v /path/to/homeassistant/config:/config -v /etc/localtime:/etc/localtime:ro --net=host balloob/home-assistant
|
||||||
```
|
```
|
||||||
|
|
||||||
|
After you have launched the Docker image, navigate to its web interface on [http://127.0.0.1:8123](http://127.0.0.1:8123).
|
||||||
|
|
||||||
After you got the demo mode running it is time to enable some real components and get started. An example configuration file has been provided in [/config/home-assistant.conf.example](https://github.com/balloob/home-assistant/blob/master/config/home-assistant.conf.example).
|
After you got the demo mode running it is time to enable some real components and get started. An example configuration file has been provided in [/config/home-assistant.conf.example](https://github.com/balloob/home-assistant/blob/master/config/home-assistant.conf.example).
|
||||||
|
|
||||||
*Note:* you can append `?api_password=YOUR_PASSWORD` to the url of the web interface to log in automatically.
|
*Note:* you can append `?api_password=YOUR_PASSWORD` to the url of the web interface to log in automatically.
|
||||||
|
@ -54,9 +54,8 @@ def ensure_config_path(config_dir):
|
|||||||
if not os.path.isfile(config_path):
|
if not os.path.isfile(config_path):
|
||||||
try:
|
try:
|
||||||
with open(config_path, 'w') as conf:
|
with open(config_path, 'w') as conf:
|
||||||
conf.write("[http]\n")
|
conf.write("[http]\n\n")
|
||||||
conf.write("api_password=password\n\n")
|
conf.write("[demo]\n\n")
|
||||||
conf.write("[demo]\n")
|
|
||||||
except IOError:
|
except IOError:
|
||||||
print(('Fatal Error: No configuration file found and unable '
|
print(('Fatal Error: No configuration file found and unable '
|
||||||
'to write a default one to {}').format(config_path))
|
'to write a default one to {}').format(config_path))
|
||||||
|
@ -86,7 +86,7 @@ import homeassistant as ha
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
SERVER_PORT, URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES,
|
SERVER_PORT, URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES,
|
||||||
URL_API_EVENT_FORWARD, URL_API_STATES_ENTITY, AUTH_HEADER)
|
URL_API_EVENT_FORWARD, URL_API_STATES_ENTITY, AUTH_HEADER)
|
||||||
from homeassistant.helpers import validate_config, TrackStates
|
from homeassistant.helpers import TrackStates
|
||||||
import homeassistant.remote as rem
|
import homeassistant.remote as rem
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from . import frontend
|
from . import frontend
|
||||||
@ -117,13 +117,18 @@ DATA_API_PASSWORD = 'api_password'
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config=None):
|
||||||
""" Sets up the HTTP API and debug interface. """
|
""" Sets up the HTTP API and debug interface. """
|
||||||
|
|
||||||
if not validate_config(config, {DOMAIN: [CONF_API_PASSWORD]}, _LOGGER):
|
if config is None or DOMAIN not in config:
|
||||||
return False
|
config = {DOMAIN: {}}
|
||||||
|
|
||||||
api_password = config[DOMAIN][CONF_API_PASSWORD]
|
api_password = config[DOMAIN].get(CONF_API_PASSWORD)
|
||||||
|
|
||||||
|
no_password_set = api_password is None
|
||||||
|
|
||||||
|
if no_password_set:
|
||||||
|
api_password = util.get_random_string()
|
||||||
|
|
||||||
# If no server host is given, accept all incoming requests
|
# If no server host is given, accept all incoming requests
|
||||||
server_host = config[DOMAIN].get(CONF_SERVER_HOST, '0.0.0.0')
|
server_host = config[DOMAIN].get(CONF_SERVER_HOST, '0.0.0.0')
|
||||||
@ -132,9 +137,9 @@ def setup(hass, config):
|
|||||||
|
|
||||||
development = config[DOMAIN].get(CONF_DEVELOPMENT, "") == "1"
|
development = config[DOMAIN].get(CONF_DEVELOPMENT, "") == "1"
|
||||||
|
|
||||||
server = HomeAssistantHTTPServer((server_host, server_port),
|
server = HomeAssistantHTTPServer(
|
||||||
RequestHandler, hass, api_password,
|
(server_host, server_port), RequestHandler, hass, api_password,
|
||||||
development)
|
development, no_password_set)
|
||||||
|
|
||||||
hass.bus.listen_once(
|
hass.bus.listen_once(
|
||||||
ha.EVENT_HOMEASSISTANT_START,
|
ha.EVENT_HOMEASSISTANT_START,
|
||||||
@ -155,13 +160,14 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
|||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def __init__(self, server_address, request_handler_class,
|
def __init__(self, server_address, request_handler_class,
|
||||||
hass, api_password, development=False):
|
hass, api_password, development, no_password_set):
|
||||||
super().__init__(server_address, request_handler_class)
|
super().__init__(server_address, request_handler_class)
|
||||||
|
|
||||||
self.server_address = server_address
|
self.server_address = server_address
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.api_password = api_password
|
self.api_password = api_password
|
||||||
self.development = development
|
self.development = development
|
||||||
|
self.no_password_set = no_password_set
|
||||||
|
|
||||||
# We will lazy init this one if needed
|
# We will lazy init this one if needed
|
||||||
self.event_forwarder = None
|
self.event_forwarder = None
|
||||||
@ -270,10 +276,13 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
|||||||
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
|
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
|
||||||
return
|
return
|
||||||
|
|
||||||
api_password = self.headers.get(AUTH_HEADER)
|
if self.server.no_password_set:
|
||||||
|
api_password = self.server.api_password
|
||||||
|
else:
|
||||||
|
api_password = self.headers.get(AUTH_HEADER)
|
||||||
|
|
||||||
if not api_password and DATA_API_PASSWORD in data:
|
if not api_password and DATA_API_PASSWORD in data:
|
||||||
api_password = data[DATA_API_PASSWORD]
|
api_password = data[DATA_API_PASSWORD]
|
||||||
|
|
||||||
if '_METHOD' in data:
|
if '_METHOD' in data:
|
||||||
method = data.pop('_METHOD')
|
method = data.pop('_METHOD')
|
||||||
@ -357,6 +366,10 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
|||||||
else:
|
else:
|
||||||
app_url = "frontend-{}.html".format(frontend.VERSION)
|
app_url = "frontend-{}.html".format(frontend.VERSION)
|
||||||
|
|
||||||
|
# auto login if no password was set, else check api_password param
|
||||||
|
auth = (self.server.api_password if self.server.no_password_set
|
||||||
|
else data.get('api_password', ''))
|
||||||
|
|
||||||
write(("<!doctype html>"
|
write(("<!doctype html>"
|
||||||
"<html>"
|
"<html>"
|
||||||
"<head><title>Home Assistant</title>"
|
"<head><title>Home Assistant</title>"
|
||||||
@ -375,7 +388,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
|||||||
" src='/static/webcomponents.min.js'></script>"
|
" src='/static/webcomponents.min.js'></script>"
|
||||||
"<link rel='import' href='/static/{}' />"
|
"<link rel='import' href='/static/{}' />"
|
||||||
"<splash-login auth='{}'></splash-login>"
|
"<splash-login auth='{}'></splash-login>"
|
||||||
"</body></html>").format(app_url, data.get('api_password', '')))
|
"</body></html>").format(app_url, auth))
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def _handle_get_api(self, path_match, data):
|
def _handle_get_api(self, path_match, data):
|
||||||
|
@ -112,17 +112,11 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
self.states = StateMachine(self.bus, self.remote_api)
|
self.states = StateMachine(self.bus, self.remote_api)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
# If there is no local API setup but we do want to connect with remote
|
# Ensure a local API exists to connect with remote
|
||||||
# We create a random password and set up a local api
|
|
||||||
if self.local_api is None:
|
if self.local_api is None:
|
||||||
import homeassistant.components.http as http
|
import homeassistant.components.http as http
|
||||||
import random
|
|
||||||
|
|
||||||
# pylint: disable=too-many-format-args
|
http.setup(self)
|
||||||
random_password = '{:30}'.format(random.randrange(16**30))
|
|
||||||
|
|
||||||
http.setup(
|
|
||||||
self, {http.DOMAIN: {http.CONF_API_PASSWORD: random_password}})
|
|
||||||
|
|
||||||
ha.Timer(self)
|
ha.Timer(self)
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ from datetime import datetime, timedelta
|
|||||||
import re
|
import re
|
||||||
import enum
|
import enum
|
||||||
import socket
|
import socket
|
||||||
|
import random
|
||||||
|
import string
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
|
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
|
||||||
@ -134,16 +136,16 @@ def convert(value, to_type, default=None):
|
|||||||
def ensure_unique_string(preferred_string, current_strings):
|
def ensure_unique_string(preferred_string, current_strings):
|
||||||
""" Returns a string that is not present in current_strings.
|
""" Returns a string that is not present in current_strings.
|
||||||
If preferred string exists will append _2, _3, .. """
|
If preferred string exists will append _2, _3, .. """
|
||||||
string = preferred_string
|
test_string = preferred_string
|
||||||
current_strings = list(current_strings)
|
current_strings = list(current_strings)
|
||||||
|
|
||||||
tries = 1
|
tries = 1
|
||||||
|
|
||||||
while string in current_strings:
|
while test_string in current_strings:
|
||||||
tries += 1
|
tries += 1
|
||||||
string = "{}_{}".format(preferred_string, tries)
|
test_string = "{}_{}".format(preferred_string, tries)
|
||||||
|
|
||||||
return string
|
return test_string
|
||||||
|
|
||||||
|
|
||||||
# Taken from: http://stackoverflow.com/a/11735897
|
# Taken from: http://stackoverflow.com/a/11735897
|
||||||
@ -163,6 +165,15 @@ def get_local_ip():
|
|||||||
return socket.gethostbyname(socket.gethostname())
|
return socket.gethostbyname(socket.gethostname())
|
||||||
|
|
||||||
|
|
||||||
|
# Taken from http://stackoverflow.com/a/23728630
|
||||||
|
def get_random_string(length=10):
|
||||||
|
""" Returns a random string with letters and digits. """
|
||||||
|
generator = random.SystemRandom()
|
||||||
|
source_chars = string.ascii_letters + string.digits
|
||||||
|
|
||||||
|
return ''.join(generator.choice(source_chars) for _ in range(length))
|
||||||
|
|
||||||
|
|
||||||
class OrderedEnum(enum.Enum):
|
class OrderedEnum(enum.Enum):
|
||||||
""" Taken from Python 3.4.0 docs. """
|
""" Taken from Python 3.4.0 docs. """
|
||||||
# pylint: disable=no-init, too-few-public-methods
|
# pylint: disable=no-init, too-few-public-methods
|
||||||
|
@ -58,11 +58,6 @@ def tearDownModule(): # pylint: disable=invalid-name
|
|||||||
class TestHTTP(unittest.TestCase):
|
class TestHTTP(unittest.TestCase):
|
||||||
""" Test the HTTP debug interface and API. """
|
""" Test the HTTP debug interface and API. """
|
||||||
|
|
||||||
def test_setup(self):
|
|
||||||
""" Test http.setup. """
|
|
||||||
self.assertFalse(http.setup(hass, {}))
|
|
||||||
self.assertFalse(http.setup(hass, {http.DOMAIN: {}}))
|
|
||||||
|
|
||||||
def test_frontend_and_static(self):
|
def test_frontend_and_static(self):
|
||||||
""" Tests if we can get the frontend. """
|
""" Tests if we can get the frontend. """
|
||||||
req = requests.get(_url(""))
|
req = requests.get(_url(""))
|
||||||
|
@ -68,12 +68,13 @@ class TestRemoteMethods(unittest.TestCase):
|
|||||||
""" Test Python API validate_api. """
|
""" Test Python API validate_api. """
|
||||||
self.assertEqual(remote.APIStatus.OK, remote.validate_api(master_api))
|
self.assertEqual(remote.APIStatus.OK, remote.validate_api(master_api))
|
||||||
|
|
||||||
self.assertEqual(remote.APIStatus.INVALID_PASSWORD,
|
self.assertEqual(
|
||||||
remote.validate_api(
|
remote.APIStatus.INVALID_PASSWORD,
|
||||||
remote.API("127.0.0.1", API_PASSWORD + "A")))
|
remote.validate_api(
|
||||||
|
remote.API("127.0.0.1", API_PASSWORD + "A", 8122)))
|
||||||
|
|
||||||
self.assertEqual(remote.APIStatus.CANNOT_CONNECT,
|
self.assertEqual(
|
||||||
remote.validate_api(broken_api))
|
remote.APIStatus.CANNOT_CONNECT, remote.validate_api(broken_api))
|
||||||
|
|
||||||
def test_get_event_listeners(self):
|
def test_get_event_listeners(self):
|
||||||
""" Test Python API get_event_listeners. """
|
""" Test Python API get_event_listeners. """
|
||||||
|
Loading…
x
Reference in New Issue
Block a user