Skip to content

Commit

Permalink
Blow up startup if init auth providers or modules failed
Browse files Browse the repository at this point in the history
  • Loading branch information
awarecan committed Aug 28, 2018
1 parent 67df162 commit add0185
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 50 deletions.
9 changes: 3 additions & 6 deletions homeassistant/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from homeassistant import data_entry_flow
from homeassistant.core import callback, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util import dt as dt_util

from . import auth_store, models
Expand Down Expand Up @@ -35,16 +36,14 @@ async def auth_manager_from_config(
# So returned auth providers are in same order as config
provider_hash = OrderedDict() # type: _ProviderDict
for provider in providers:
if provider is None:
continue

key = (provider.type, provider.id)

if key in provider_hash:
_LOGGER.error(
'Found duplicate provider: %s. Please add unique IDs if you '
'want to have the same provider twice.', key)
continue
raise HomeAssistantError

provider_hash[key] = provider

Expand All @@ -57,14 +56,12 @@ async def auth_manager_from_config(
# So returned auth modules are in same order as config
module_hash = OrderedDict() # type: _MfaModuleDict
for module in modules:
if module is None:
continue

if module.id in module_hash:
_LOGGER.error(
'Found duplicate multi-factor module: %s. Please add unique '
'IDs if you want to have the same module twice.', module.id)
continue
raise HomeAssistantError

module_hash[module.id] = module

Expand Down
17 changes: 9 additions & 8 deletions homeassistant/auth/mfa_modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from homeassistant import requirements, data_entry_flow
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.decorator import Registry

MULTI_FACTOR_AUTH_MODULES = Registry()
Expand Down Expand Up @@ -127,34 +128,32 @@ async def async_step_init(

async def auth_mfa_module_from_config(
hass: HomeAssistant, config: Dict[str, Any]) \
-> Optional[MultiFactorAuthModule]:
-> MultiFactorAuthModule:
"""Initialize an auth module from a config."""
module_name = config[CONF_TYPE]
module = await _load_mfa_module(hass, module_name)

if module is None:
return None

try:
config = module.CONFIG_SCHEMA(config) # type: ignore
except vol.Invalid as err:
_LOGGER.error('Invalid configuration for multi-factor module %s: %s',
module_name, humanize_error(config, err))
return None
raise

return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config) # type: ignore


async def _load_mfa_module(hass: HomeAssistant, module_name: str) \
-> Optional[types.ModuleType]:
-> types.ModuleType:
"""Load an mfa auth module."""
module_path = 'homeassistant.auth.mfa_modules.{}'.format(module_name)

try:
module = importlib.import_module(module_path)
except ImportError as err:
_LOGGER.error('Unable to load mfa module %s: %s', module_name, err)
return None
raise HomeAssistantError('Unable to load mfa module {}: {}'.format(
module_name, err))

if hass.config.skip_pip or not hasattr(module, 'REQUIREMENTS'):
return module
Expand All @@ -170,7 +169,9 @@ async def _load_mfa_module(hass: HomeAssistant, module_name: str) \
hass, module_path, module.REQUIREMENTS) # type: ignore

if not req_success:
return None
raise HomeAssistantError(
'Unable to process requirements of mfa module {}'.format(
module_name))

processed.add(module_name)
return module
17 changes: 9 additions & 8 deletions homeassistant/auth/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from homeassistant import data_entry_flow, requirements
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util import dt as dt_util
from homeassistant.util.decorator import Registry

Expand Down Expand Up @@ -110,33 +111,31 @@ async def async_user_meta_for_credentials(

async def auth_provider_from_config(
hass: HomeAssistant, store: AuthStore,
config: Dict[str, Any]) -> Optional[AuthProvider]:
config: Dict[str, Any]) -> AuthProvider:
"""Initialize an auth provider from a config."""
provider_name = config[CONF_TYPE]
module = await load_auth_provider_module(hass, provider_name)

if module is None:
return None

try:
config = module.CONFIG_SCHEMA(config) # type: ignore
except vol.Invalid as err:
_LOGGER.error('Invalid configuration for auth provider %s: %s',
provider_name, humanize_error(config, err))
return None
raise

return AUTH_PROVIDERS[provider_name](hass, store, config) # type: ignore


async def load_auth_provider_module(
hass: HomeAssistant, provider: str) -> Optional[types.ModuleType]:
hass: HomeAssistant, provider: str) -> types.ModuleType:
"""Load an auth provider."""
try:
module = importlib.import_module(
'homeassistant.auth.providers.{}'.format(provider))
except ImportError as err:
_LOGGER.error('Unable to load auth provider %s: %s', provider, err)
return None
raise HomeAssistantError('Unable to load auth provider {}: {}'.format(
provider, err))

if hass.config.skip_pip or not hasattr(module, 'REQUIREMENTS'):
return module
Expand All @@ -154,7 +153,9 @@ async def load_auth_provider_module(
hass, 'auth provider {}'.format(provider), reqs)

if not req_success:
return None
raise HomeAssistantError(
'Unable to process requirements of auth provider {}'.format(
provider))

processed.add(provider)
return module
Expand Down
12 changes: 8 additions & 4 deletions homeassistant/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ def from_config_dict(config: Dict[str, Any],
config, hass, config_dir, enable_log, verbose, skip_pip,
log_rotate_days, log_file, log_no_color)
)

return hass


Expand Down Expand Up @@ -94,8 +93,13 @@ async def async_from_config_dict(config: Dict[str, Any],
try:
await conf_util.async_process_ha_core_config(
hass, core_config, has_api_password, has_trusted_networks)
except vol.Invalid as ex:
conf_util.async_log_exception(ex, 'homeassistant', core_config, hass)
except vol.Invalid as config_err:
conf_util.async_log_exception(
config_err, 'homeassistant', core_config, hass)
return None
except HomeAssistantError:
_LOGGER.error("Home Assistant core failed to initialize. "
"Further initialization aborted")
return None

await hass.async_add_executor_job(
Expand Down Expand Up @@ -130,7 +134,7 @@ async def async_from_config_dict(config: Dict[str, Any],
res = await core_components.async_setup(hass, config)
if not res:
_LOGGER.error("Home Assistant core failed to initialize. "
"further initialization aborted")
"Further initialization aborted")
return hass

await persistent_notification.async_setup(hass, config)
Expand Down
11 changes: 6 additions & 5 deletions tests/auth/providers/test_homeassistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import base64
import pytest
import voluptuous as vol

from homeassistant import data_entry_flow
from homeassistant.auth import auth_manager_from_config, auth_store
Expand Down Expand Up @@ -111,11 +112,11 @@ async def test_saving_loading(data, hass):
async def test_not_allow_set_id():
"""Test we are not allowed to set an ID in config."""
hass = Mock()
provider = await auth_provider_from_config(hass, None, {
'type': 'homeassistant',
'id': 'invalid',
})
assert provider is None
with pytest.raises(vol.Invalid):
await auth_provider_from_config(hass, None, {
'type': 'homeassistant',
'id': 'invalid',
})


async def test_new_users_populate_values(hass, data):
Expand Down
90 changes: 71 additions & 19 deletions tests/auth/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
from unittest.mock import Mock, patch

import pytest
import voluptuous as vol

from homeassistant import auth, data_entry_flow
from homeassistant.auth import (
models as auth_models, auth_store, const as auth_const)
from homeassistant.auth.mfa_modules import SESSION_EXPIRATION
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util import dt as dt_util
from tests.common import (
MockUser, ensure_auth_manager_loaded, flush_store, CLIENT_ID)
Expand All @@ -23,31 +25,45 @@ def mock_hass(loop):

async def test_auth_manager_from_config_validates_config_and_id(mock_hass):
"""Test get auth providers."""
with pytest.raises(vol.Invalid):
manager = await auth.auth_manager_from_config(mock_hass, [{
'name': 'Test Name',
'type': 'insecure_example',
'users': [],
}, {
'name': 'Invalid config because no users',
'type': 'insecure_example',
'id': 'invalid_config',
}], [])

with pytest.raises(HomeAssistantError):
manager = await auth.auth_manager_from_config(mock_hass, [{
'name': 'Test Name',
'type': 'insecure_example',
'users': [],
}, {
'name': 'Test Name 2',
'type': 'insecure_example',
'users': [],
}], [])

manager = await auth.auth_manager_from_config(mock_hass, [{
'name': 'Test Name',
'type': 'insecure_example',
'users': [],
}, {
'name': 'Invalid config because no users',
'type': 'insecure_example',
'id': 'invalid_config',
}, {
'name': 'Test Name 2',
'type': 'insecure_example',
'id': 'another',
'users': [],
}, {
'name': 'Wrong because duplicate ID',
'type': 'insecure_example',
'id': 'another',
'users': [],
}], [])

providers = [{
'name': provider.name,
'id': provider.id,
'type': provider.type,
} for provider in manager.auth_providers]
'name': provider.name,
'id': provider.id,
'type': provider.type,
} for provider in manager.auth_providers]

assert providers == [{
'name': 'Test Name',
'type': 'insecure_example',
Expand All @@ -61,6 +77,48 @@ async def test_auth_manager_from_config_validates_config_and_id(mock_hass):

async def test_auth_manager_from_config_auth_modules(mock_hass):
"""Test get auth modules."""
with pytest.raises(vol.Invalid):
manager = await auth.auth_manager_from_config(mock_hass, [{
'name': 'Test Name',
'type': 'insecure_example',
'users': [],
}, {
'name': 'Test Name 2',
'type': 'insecure_example',
'id': 'another',
'users': [],
}], [{
'name': 'Module 1',
'type': 'insecure_example',
'data': [],
}, {
'name': 'Module 2',
'type': 'insecure_example',
'id': 'another',
}])

with pytest.raises(HomeAssistantError):
manager = await auth.auth_manager_from_config(mock_hass, [{
'name': 'Test Name',
'type': 'insecure_example',
'users': [],
}, {
'name': 'Test Name 2',
'type': 'insecure_example',
'id': 'another',
'users': [],
}], [{
'name': 'Module 2',
'type': 'insecure_example',
'id': 'another',
'data': [],
}, {
'name': 'Duplicate ID',
'type': 'insecure_example',
'id': 'another',
'data': [],
}])

manager = await auth.auth_manager_from_config(mock_hass, [{
'name': 'Test Name',
'type': 'insecure_example',
Expand All @@ -79,13 +137,7 @@ async def test_auth_manager_from_config_auth_modules(mock_hass):
'type': 'insecure_example',
'id': 'another',
'data': [],
}, {
'name': 'Duplicate ID',
'type': 'insecure_example',
'id': 'another',
'data': [],
}])

providers = [{
'name': provider.name,
'type': provider.type,
Expand Down
16 changes: 16 additions & 0 deletions tests/testing_config/.storage/core.entity_registry
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"data": {
"entities": [
{
"config_entry_id": null,
"device_id": null,
"entity_id": "mock_component.registry_id",
"name": null,
"platform": "zwave",
"unique_id": "567-1000"
}
]
},
"key": "core.entity_registry",
"version": 1
}

0 comments on commit add0185

Please sign in to comment.