MQTT embedded broker has to set its own password. (#15929)

pull/16027/head
Jason Hu 2018-08-13 02:26:06 -07:00 committed by Paulus Schoutsen
parent 34e1f1b6da
commit d0e4c95bbc
3 changed files with 82 additions and 25 deletions

View File

@ -32,7 +32,8 @@ from homeassistant.util.async_ import (
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, CONF_VALUE_TEMPLATE, CONF_USERNAME,
CONF_PASSWORD, CONF_PORT, CONF_PROTOCOL, CONF_PAYLOAD)
from homeassistant.components.mqtt.server import HBMQTT_CONFIG_SCHEMA
from .server import HBMQTT_CONFIG_SCHEMA
REQUIREMENTS = ['paho-mqtt==1.3.1']
@ -306,7 +307,8 @@ async def _async_setup_server(hass: HomeAssistantType,
return None
success, broker_config = \
await server.async_start(hass, conf.get(CONF_EMBEDDED))
await server.async_start(
hass, conf.get(CONF_PASSWORD), conf.get(CONF_EMBEDDED))
if not success:
return None
@ -349,6 +351,16 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
if CONF_EMBEDDED not in conf and CONF_BROKER in conf:
broker_config = None
else:
if (conf.get(CONF_PASSWORD) is None and
config.get('http') is not None and
config['http'].get('api_password') is not None):
_LOGGER.error("Starting from 0.77, embedded MQTT broker doesn't"
" use api_password as default password any more."
" Please set password configuration. See https://"
"home-assistant.io/docs/mqtt/broker#embedded-broker"
" for details")
return False
broker_config = await _async_setup_server(hass, config)
if CONF_BROKER in conf:

View File

@ -27,27 +27,29 @@ HBMQTT_CONFIG_SCHEMA = vol.Any(None, vol.Schema({
})
}, extra=vol.ALLOW_EXTRA))
_LOGGER = logging.getLogger(__name__)
@asyncio.coroutine
def async_start(hass, server_config):
def async_start(hass, password, server_config):
"""Initialize MQTT Server.
This method is a coroutine.
"""
from hbmqtt.broker import Broker, BrokerException
passwd = tempfile.NamedTemporaryFile()
try:
passwd = tempfile.NamedTemporaryFile()
if server_config is None:
server_config, client_config = generate_config(hass, passwd)
server_config, client_config = generate_config(
hass, passwd, password)
else:
client_config = None
broker = Broker(server_config, hass.loop)
yield from broker.start()
except BrokerException:
logging.getLogger(__name__).exception("Error initializing MQTT server")
_LOGGER.exception("Error initializing MQTT server")
return False, None
finally:
passwd.close()
@ -63,9 +65,10 @@ def async_start(hass, server_config):
return True, client_config
def generate_config(hass, passwd):
def generate_config(hass, passwd, password):
"""Generate a configuration based on current Home Assistant instance."""
from homeassistant.components.mqtt import PROTOCOL_311
from . import PROTOCOL_311
config = {
'listeners': {
'default': {
@ -79,29 +82,26 @@ def generate_config(hass, passwd):
},
},
'auth': {
'allow-anonymous': hass.config.api.api_password is None
'allow-anonymous': password is None
},
'plugins': ['auth_anonymous'],
}
if hass.config.api.api_password:
if password:
username = 'homeassistant'
password = hass.config.api.api_password
# Encrypt with what hbmqtt uses to verify
from passlib.apps import custom_app_context
passwd.write(
'homeassistant:{}\n'.format(
custom_app_context.encrypt(
hass.config.api.api_password)).encode('utf-8'))
custom_app_context.encrypt(password)).encode('utf-8'))
passwd.flush()
config['auth']['password-file'] = passwd.name
config['plugins'].append('auth_file')
else:
username = None
password = None
client_config = ('localhost', 1883, username, password, None, PROTOCOL_311)

View File

@ -4,6 +4,7 @@ import sys
import pytest
from homeassistant.const import CONF_PASSWORD
from homeassistant.setup import setup_component
import homeassistant.components.mqtt as mqtt
@ -19,9 +20,6 @@ class TestMQTT:
def setup_method(self, method):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
setup_component(self.hass, 'http', {
'api_password': 'super_secret'
})
def teardown_method(self, method):
"""Stop everything that was started."""
@ -32,14 +30,36 @@ class TestMQTT:
@patch('hbmqtt.broker.Broker', Mock(return_value=MagicMock()))
@patch('hbmqtt.broker.Broker.start', Mock(return_value=mock_coro()))
@patch('homeassistant.components.mqtt.MQTT')
def test_creating_config_with_http_pass(self, mock_mqtt):
"""Test if the MQTT server gets started and subscribe/publish msg."""
def test_creating_config_with_http_pass_only(self, mock_mqtt):
"""Test if the MQTT server failed starts.
Since 0.77, MQTT server has to setup its own password.
If user has api_password but don't have mqtt.password, MQTT component
will fail to start
"""
mock_mqtt().async_connect.return_value = mock_coro(True)
self.hass.bus.listen_once = MagicMock()
password = 'super_secret'
assert not setup_component(self.hass, mqtt.DOMAIN, {
'http': {'api_password': 'http_secret'}
})
self.hass.config.api = MagicMock(api_password=password)
assert setup_component(self.hass, mqtt.DOMAIN, {})
@patch('passlib.apps.custom_app_context', Mock(return_value=''))
@patch('tempfile.NamedTemporaryFile', Mock(return_value=MagicMock()))
@patch('hbmqtt.broker.Broker', Mock(return_value=MagicMock()))
@patch('hbmqtt.broker.Broker.start', Mock(return_value=mock_coro()))
@patch('homeassistant.components.mqtt.MQTT')
def test_creating_config_with_pass_and_no_http_pass(self, mock_mqtt):
"""Test if the MQTT server gets started with password.
Since 0.77, MQTT server has to setup its own password.
"""
mock_mqtt().async_connect.return_value = mock_coro(True)
self.hass.bus.listen_once = MagicMock()
password = 'mqtt_secret'
assert setup_component(self.hass, mqtt.DOMAIN, {
mqtt.DOMAIN: {CONF_PASSWORD: password},
})
assert mock_mqtt.called
from pprint import pprint
pprint(mock_mqtt.mock_calls)
@ -51,8 +71,33 @@ class TestMQTT:
@patch('hbmqtt.broker.Broker', Mock(return_value=MagicMock()))
@patch('hbmqtt.broker.Broker.start', Mock(return_value=mock_coro()))
@patch('homeassistant.components.mqtt.MQTT')
def test_creating_config_with_http_no_pass(self, mock_mqtt):
"""Test if the MQTT server gets started and subscribe/publish msg."""
def test_creating_config_with_pass_and_http_pass(self, mock_mqtt):
"""Test if the MQTT server gets started with password.
Since 0.77, MQTT server has to setup its own password.
"""
mock_mqtt().async_connect.return_value = mock_coro(True)
self.hass.bus.listen_once = MagicMock()
password = 'mqtt_secret'
self.hass.config.api = MagicMock(api_password='api_password')
assert setup_component(self.hass, mqtt.DOMAIN, {
'http': {'api_password': 'http_secret'},
mqtt.DOMAIN: {CONF_PASSWORD: password},
})
assert mock_mqtt.called
from pprint import pprint
pprint(mock_mqtt.mock_calls)
assert mock_mqtt.mock_calls[1][1][5] == 'homeassistant'
assert mock_mqtt.mock_calls[1][1][6] == password
@patch('passlib.apps.custom_app_context', Mock(return_value=''))
@patch('tempfile.NamedTemporaryFile', Mock(return_value=MagicMock()))
@patch('hbmqtt.broker.Broker', Mock(return_value=MagicMock()))
@patch('hbmqtt.broker.Broker.start', Mock(return_value=mock_coro()))
@patch('homeassistant.components.mqtt.MQTT')
def test_creating_config_without_pass(self, mock_mqtt):
"""Test if the MQTT server gets started without password."""
mock_mqtt().async_connect.return_value = mock_coro(True)
self.hass.bus.listen_once = MagicMock()