Allow for running Home Assistant without password

This commit is contained in:
Paulus Schoutsen 2015-01-17 21:55:33 -08:00
parent 50eecd11c1
commit ed05ff6fd9
7 changed files with 54 additions and 39 deletions

View File

@ -45,7 +45,7 @@ pip3 install -r requirements.txt
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
@ -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
```
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).
*Note:* you can append `?api_password=YOUR_PASSWORD` to the url of the web interface to log in automatically.

View File

@ -54,9 +54,8 @@ def ensure_config_path(config_dir):
if not os.path.isfile(config_path):
try:
with open(config_path, 'w') as conf:
conf.write("[http]\n")
conf.write("api_password=password\n\n")
conf.write("[demo]\n")
conf.write("[http]\n\n")
conf.write("[demo]\n\n")
except IOError:
print(('Fatal Error: No configuration file found and unable '
'to write a default one to {}').format(config_path))

View File

@ -86,7 +86,7 @@ import homeassistant as ha
from homeassistant.const import (
SERVER_PORT, URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES,
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.util as util
from . import frontend
@ -117,13 +117,18 @@ DATA_API_PASSWORD = 'api_password'
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
def setup(hass, config=None):
""" Sets up the HTTP API and debug interface. """
if not validate_config(config, {DOMAIN: [CONF_API_PASSWORD]}, _LOGGER):
return False
if config is None or DOMAIN not in config:
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
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"
server = HomeAssistantHTTPServer((server_host, server_port),
RequestHandler, hass, api_password,
development)
server = HomeAssistantHTTPServer(
(server_host, server_port), RequestHandler, hass, api_password,
development, no_password_set)
hass.bus.listen_once(
ha.EVENT_HOMEASSISTANT_START,
@ -155,13 +160,14 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
# pylint: disable=too-many-arguments
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)
self.server_address = server_address
self.hass = hass
self.api_password = api_password
self.development = development
self.no_password_set = no_password_set
# We will lazy init this one if needed
self.event_forwarder = None
@ -270,10 +276,13 @@ class RequestHandler(SimpleHTTPRequestHandler):
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
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:
api_password = data[DATA_API_PASSWORD]
if not api_password and DATA_API_PASSWORD in data:
api_password = data[DATA_API_PASSWORD]
if '_METHOD' in data:
method = data.pop('_METHOD')
@ -357,6 +366,10 @@ class RequestHandler(SimpleHTTPRequestHandler):
else:
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>"
"<html>"
"<head><title>Home Assistant</title>"
@ -375,7 +388,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
" src='/static/webcomponents.min.js'></script>"
"<link rel='import' href='/static/{}' />"
"<splash-login auth='{}'></splash-login>"
"</body></html>").format(app_url, data.get('api_password', '')))
"</body></html>").format(app_url, auth))
# pylint: disable=unused-argument
def _handle_get_api(self, path_match, data):

View File

@ -112,17 +112,11 @@ class HomeAssistant(ha.HomeAssistant):
self.states = StateMachine(self.bus, self.remote_api)
def start(self):
# If there is no local API setup but we do want to connect with remote
# We create a random password and set up a local api
# Ensure a local API exists to connect with remote
if self.local_api is None:
import homeassistant.components.http as http
import random
# pylint: disable=too-many-format-args
random_password = '{:30}'.format(random.randrange(16**30))
http.setup(
self, {http.DOMAIN: {http.CONF_API_PASSWORD: random_password}})
http.setup(self)
ha.Timer(self)

View File

@ -12,6 +12,8 @@ from datetime import datetime, timedelta
import re
import enum
import socket
import random
import string
from functools import wraps
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):
""" Returns a string that is not present in current_strings.
If preferred string exists will append _2, _3, .. """
string = preferred_string
test_string = preferred_string
current_strings = list(current_strings)
tries = 1
while string in current_strings:
while test_string in current_strings:
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
@ -163,6 +165,15 @@ def get_local_ip():
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):
""" Taken from Python 3.4.0 docs. """
# pylint: disable=no-init, too-few-public-methods

View File

@ -58,11 +58,6 @@ def tearDownModule(): # pylint: disable=invalid-name
class TestHTTP(unittest.TestCase):
""" 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):
""" Tests if we can get the frontend. """
req = requests.get(_url(""))

View File

@ -68,12 +68,13 @@ class TestRemoteMethods(unittest.TestCase):
""" Test Python API validate_api. """
self.assertEqual(remote.APIStatus.OK, remote.validate_api(master_api))
self.assertEqual(remote.APIStatus.INVALID_PASSWORD,
remote.validate_api(
remote.API("127.0.0.1", API_PASSWORD + "A")))
self.assertEqual(
remote.APIStatus.INVALID_PASSWORD,
remote.validate_api(
remote.API("127.0.0.1", API_PASSWORD + "A", 8122)))
self.assertEqual(remote.APIStatus.CANNOT_CONNECT,
remote.validate_api(broken_api))
self.assertEqual(
remote.APIStatus.CANNOT_CONNECT, remote.validate_api(broken_api))
def test_get_event_listeners(self):
""" Test Python API get_event_listeners. """