Example #1
0
    async def post(self, request):
        """Accept the POST request for push registrations from a browser."""
        try:
            data = await request.json()
        except ValueError:
            return self.json_message('Invalid JSON', HTTP_BAD_REQUEST)

        try:
            data = REGISTER_SCHEMA(data)
        except vol.Invalid as ex:
            return self.json_message(
                humanize_error(data, ex), HTTP_BAD_REQUEST)

        name = self.find_registration_name(data)
        previous_registration = self.registrations.get(name)

        self.registrations[name] = data

        try:
            hass = request.app['hass']

            await hass.async_add_job(save_json, self.json_path,
                                     self.registrations)
            return self.json_message(
                'Push notification subscriber registered.')
        except HomeAssistantError:
            if previous_registration is not None:
                self.registrations[name] = previous_registration
            else:
                self.registrations.pop(name)

            return self.json_message(
                'Error saving registration.', HTTP_INTERNAL_SERVER_ERROR)
Example #2
0
def async_log_exception(ex, domain, config, hass):
    """Generate log exception for configuration validation.

    This method must be run in the event loop.
    """
    message = "Invalid config for [{}]: ".format(domain)
    if hass is not None:
        async_notify_setup_error(hass, domain, True)

    if 'extra keys not allowed' in ex.error_message:
        message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\
                   .format(ex.path[-1], domain, domain,
                           '->'.join(str(m) for m in ex.path))
    else:
        message += '{}.'.format(humanize_error(config, ex))

    domain_config = config.get(domain, config)
    message += " (See {}, line {}). ".format(
        getattr(domain_config, '__config_file__', '?'),
        getattr(domain_config, '__line__', '?'))

    if domain != 'homeassistant':
        message += ('Please check the docs at '
                    'https://home-assistant.io/components/{}/'.format(domain))

    _LOGGER.error(message)
Example #3
0
def _format_config_error(ex: vol.Invalid, domain: str, config: Dict) -> str:
    """Generate log exception for configuration validation.

    This method must be run in the event loop.
    """
    message = "Invalid config for [{}]: ".format(domain)
    if 'extra keys not allowed' in ex.error_message:
        message += '[{option}] is an invalid option for [{domain}]. ' \
            'Check: {domain}->{path}.'.format(
                option=ex.path[-1], domain=domain,
                path='->'.join(str(m) for m in ex.path))
    else:
        message += '{}.'.format(humanize_error(config, ex))

    try:
        domain_config = config.get(domain, config)
    except AttributeError:
        domain_config = config

    message += " (See {}, line {}). ".format(
        getattr(domain_config, '__config_file__', '?'),
        getattr(domain_config, '__line__', '?'))

    if domain != CONF_CORE:
        message += ('Please check the docs at '
                    'https://home-assistant.io/components/{}/'.format(domain))

    return message
Example #4
0
    async def async_handle(self, msg):
        """Handle authentication."""
        try:
            msg = AUTH_MESSAGE_SCHEMA(msg)
        except vol.Invalid as err:
            error_msg = 'Auth message incorrectly formatted: {}'.format(
                humanize_error(msg, err))
            self._logger.warning(error_msg)
            self._send_message(auth_invalid_message(error_msg))
            raise Disconnect

        if self._hass.auth.active and 'access_token' in msg:
            self._logger.debug("Received access_token")
            refresh_token = \
                await self._hass.auth.async_validate_access_token(
                    msg['access_token'])
            if refresh_token is not None:
                return await self._async_finish_auth(
                    refresh_token.user, refresh_token)

        elif ((not self._hass.auth.active or self._hass.auth.support_legacy)
              and 'api_password' in msg):
            self._logger.debug("Received api_password")
            if validate_password(self._request, msg['api_password']):
                return await self._async_finish_auth(None, None)

        self._send_message(auth_invalid_message(
            'Invalid access token or password'))
        await process_wrong_login(self._request)
        raise Disconnect
Example #5
0
def log_exception(ex, domain, config, hass=None):
    """Generate log exception for config validation."""
    message = "Invalid config for [{}]: ".format(domain)
    if hass is not None:
        _PERSISTENT_VALIDATION.add(domain)
        message = (
            "The following platforms contain invalid configuration:  "
            + ", ".join(list(_PERSISTENT_VALIDATION))
            + "  (please check your configuration)"
        )
        persistent_notification.create(hass, message, "Invalid config", "invalid_config")

    if "extra keys not allowed" in ex.error_message:
        message += "[{}] is an invalid option for [{}]. Check: {}->{}.".format(
            ex.path[-1], domain, domain, "->".join("%s" % m for m in ex.path)
        )
    else:
        message += "{}.".format(humanize_error(config, ex))

    if hasattr(config, "__line__"):
        message += " (See {}:{})".format(config.__config_file__, config.__line__ or "?")

    if domain != "homeassistant":
        message += " Please check the docs at " "https://home-assistant.io/components/{}/".format(domain)

    _LOGGER.error(message)
Example #6
0
    def post(self, request):
        """Accept the POST request for push registrations event callback."""
        auth_check = self.check_authorization_header(request)
        if not isinstance(auth_check, dict):
            return auth_check

        try:
            data = yield from request.json()
        except ValueError:
            return self.json_message('Invalid JSON', HTTP_BAD_REQUEST)

        event_payload = {
            ATTR_TAG: data.get(ATTR_TAG),
            ATTR_TYPE: data[ATTR_TYPE],
            ATTR_TARGET: auth_check[ATTR_TARGET],
        }

        if data.get(ATTR_ACTION) is not None:
            event_payload[ATTR_ACTION] = data.get(ATTR_ACTION)

        if data.get(ATTR_DATA) is not None:
            event_payload[ATTR_DATA] = data.get(ATTR_DATA)

        try:
            event_payload = CALLBACK_EVENT_PAYLOAD_SCHEMA(event_payload)
        except vol.Invalid as ex:
            _LOGGER.warning('Callback event payload is not valid! %s',
                            humanize_error(event_payload, ex))

        event_name = '{}.{}'.format(NOTIFY_CALLBACK_EVENT,
                                    event_payload[ATTR_TYPE])
        self.hass.bus.fire(event_name, event_payload)
        return self.json({'status': 'ok',
                          'event': event_payload[ATTR_TYPE]})
Example #7
0
    async def _event_to_service_call(self, event: Event) -> None:
        """Handle the SERVICE_CALLED events from the EventBus."""
        service_data = event.data.get(ATTR_SERVICE_DATA) or {}
        domain = event.data.get(ATTR_DOMAIN).lower()  # type: ignore
        service = event.data.get(ATTR_SERVICE).lower()  # type: ignore
        call_id = event.data.get(ATTR_SERVICE_CALL_ID)

        if not self.has_service(domain, service):
            if event.origin == EventOrigin.local:
                _LOGGER.warning("Unable to find service %s/%s",
                                domain, service)
            return

        service_handler = self._services[domain][service]

        def fire_service_executed() -> None:
            """Fire service executed event."""
            if not call_id:
                return

            data = {ATTR_SERVICE_CALL_ID: call_id}

            if (service_handler.is_coroutinefunction or
                    service_handler.is_callback):
                self._hass.bus.async_fire(EVENT_SERVICE_EXECUTED, data,
                                          EventOrigin.local, event.context)
            else:
                self._hass.bus.fire(EVENT_SERVICE_EXECUTED, data,
                                    EventOrigin.local, event.context)

        try:
            if service_handler.schema:
                service_data = service_handler.schema(service_data)
        except vol.Invalid as ex:
            _LOGGER.error("Invalid service data for %s.%s: %s",
                          domain, service, humanize_error(service_data, ex))
            fire_service_executed()
            return

        service_call = ServiceCall(
            domain, service, service_data, event.context)

        try:
            if service_handler.is_callback:
                service_handler.func(service_call)
                fire_service_executed()
            elif service_handler.is_coroutinefunction:
                await service_handler.func(service_call)
                fire_service_executed()
            else:
                def execute_service() -> None:
                    """Execute a service and fires a SERVICE_EXECUTED event."""
                    service_handler.func(service_call)
                    fire_service_executed()

                await self._hass.async_add_executor_job(execute_service)
        except Exception:  # pylint: disable=broad-except
            _LOGGER.exception('Error executing service %s', service_call)
Example #8
0
    def __call__(self, call):
        """Execute the service."""
        try:
            if self.schema:
                call.data = self.schema(call.data)

            self.func(call)
        except vol.MultipleInvalid as ex:
            _LOGGER.error('Invalid service data for %s.%s: %s',
                          call.domain, call.service,
                          humanize_error(call.data, ex))
Example #9
0
    def _event_to_service_call(self, event):
        """Callback for SERVICE_CALLED events from the event bus."""
        service_data = event.data.get(ATTR_SERVICE_DATA) or {}
        domain = event.data.get(ATTR_DOMAIN).lower()
        service = event.data.get(ATTR_SERVICE).lower()
        call_id = event.data.get(ATTR_SERVICE_CALL_ID)

        if not self.has_service(domain, service):
            if event.origin == EventOrigin.local:
                _LOGGER.warning("Unable to find service %s/%s",
                                domain, service)
            return

        service_handler = self._services[domain][service]

        def fire_service_executed():
            """Fire service executed event."""
            if not call_id:
                return

            data = {ATTR_SERVICE_CALL_ID: call_id}

            if (service_handler.is_coroutinefunction or
                    service_handler.is_callback):
                self._hass.bus.async_fire(EVENT_SERVICE_EXECUTED, data)
            else:
                self._hass.bus.fire(EVENT_SERVICE_EXECUTED, data)

        try:
            if service_handler.schema:
                service_data = service_handler.schema(service_data)
        except vol.Invalid as ex:
            _LOGGER.error("Invalid service data for %s.%s: %s",
                          domain, service, humanize_error(service_data, ex))
            fire_service_executed()
            return

        service_call = ServiceCall(domain, service, service_data, call_id)

        if service_handler.is_callback:
            service_handler.func(service_call)
            fire_service_executed()
        elif service_handler.is_coroutinefunction:
            yield from service_handler.func(service_call)
            fire_service_executed()
        else:
            def execute_service():
                """Execute a service and fires a SERVICE_EXECUTED event."""
                service_handler.func(service_call)
                fire_service_executed()

            self._hass.async_add_job(execute_service)
Example #10
0
async def auth_provider_from_config(
        hass: HomeAssistant, store: AuthStore,
        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)

    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))
        raise

    return AUTH_PROVIDERS[provider_name](hass, store, config)  # type: ignore
Example #11
0
def validate_manifest(integration: Integration):
    """Validate manifest."""
    try:
        MANIFEST_SCHEMA(integration.manifest)
    except vol.Invalid as err:
        integration.add_error(
            "manifest",
            "Invalid manifest: {}".format(
                humanize_error(integration.manifest, err)),
        )
        integration.manifest = None
        return

    if integration.manifest["domain"] != integration.path.name:
        integration.add_error("manifest", "Domain does not match dir name")
Example #12
0
def _log_exception(ex, domain, config):
    """Generate log exception for config validation."""
    message = 'Invalid config for [{}]: '.format(domain)
    if 'extra keys not allowed' in ex.error_message:
        message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\
                   .format(ex.path[-1], domain, domain,
                           '->'.join('%s' % m for m in ex.path))
    else:
        message += humanize_error(config, ex)

    if hasattr(config, '__line__'):
        message += " (See {}:{})".format(config.__config_file__,
                                         config.__line__ or '?')

    _LOGGER.error(message)
Example #13
0
async def auth_provider_from_config(
        hass: HomeAssistant, store: AuthStore,
        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)

    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))
        raise

    return AUTH_PROVIDERS[provider_name](hass, store, config)  # type: ignore
Example #14
0
async def auth_mfa_module_from_config(
        hass: HomeAssistant, config: Dict[str, Any]) \
        -> MultiFactorAuthModule:
    """Initialize an auth module from a config."""
    module_name = config[CONF_TYPE]
    module = await _load_mfa_module(hass, module_name)

    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))
        raise

    return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config)  # type: ignore
Example #15
0
    def save(self):
        """Store data to config file."""
        # validate
        try:
            self._data = self._schema(self._data)
        except vol.Invalid as ex:
            _LOGGER.error("Can't parse data -> %s",
                          humanize_error(self._data, ex))
            return False

        # write
        if not write_json_file(self._file, self._data):
            _LOGGER.error("Can't store config in %s", self._file)
            return False
        return True
Example #16
0
async def auth_mfa_module_from_config(
        hass: HomeAssistant, config: Dict[str, Any]) \
        -> MultiFactorAuthModule:
    """Initialize an auth module from a config."""
    module_name = config[CONF_TYPE]
    module = await _load_mfa_module(hass, module_name)

    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))
        raise

    return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config)  # type: ignore
Example #17
0
    async def async_inputs_from_config(
            self, config_with_blueprint: dict) -> BlueprintInputs:
        """Process a blueprint config."""
        try:
            config_with_blueprint = BLUEPRINT_INSTANCE_FIELDS(
                config_with_blueprint)
        except vol.Invalid as err:
            raise InvalidBlueprintInputs(
                self.domain, humanize_error(config_with_blueprint,
                                            err)) from err

        bp_conf = config_with_blueprint[CONF_USE_BLUEPRINT]
        blueprint = await self.async_get_blueprint(bp_conf[CONF_PATH])
        inputs = BlueprintInputs(blueprint, config_with_blueprint)
        inputs.validate()
        return inputs
Example #18
0
    async def start(self, request):
        """Start addon."""
        addon = self._extract_addon(request)

        if await self.addons.state(addon) == STATE_STARTED:
            raise RuntimeError("Addon is already running")

        # validate options
        try:
            schema = self.addons.get_schema(addon)
            options = self.addons.get_options(addon)
            schema(options)
        except vol.Invalid as ex:
            raise RuntimeError(humanize_error(options, ex)) from None

        return await asyncio.shield(self.addons.start(addon), loop=self.loop)
Example #19
0
async def _auth_provider_from_config(hass, store, config):
    """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)
    except vol.Invalid as err:
        _LOGGER.error('Invalid configuration for auth provider %s: %s',
                      provider_name, humanize_error(config, err))
        return None

    return AUTH_PROVIDERS[provider_name](hass, store, config)
Example #20
0
async def auth_provider_from_config(hass, store, config):
    """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)
    except vol.Invalid as err:
        _LOGGER.error('Invalid configuration for auth provider %s: %s',
                      provider_name, humanize_error(config, err))
        return None

    return AUTH_PROVIDERS[provider_name](hass, store, config)
Example #21
0
async def auth_mfa_module_from_config(
        opp: OpenPeerPower, config: dict[str, Any]) -> MultiFactorAuthModule:
    """Initialize an auth module from a config."""
    module_name = config[CONF_TYPE]
    module = await _load_mfa_module(opp, module_name)

    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),
        )
        raise

    return MULTI_FACTOR_AUTH_MODULES[module_name](opp, config)  # type: ignore
Example #22
0
    def post(self, request):
        """Handle the POST request for device identification."""
        try:
            data = IDENTIFY_SCHEMA(request.json)
        except vol.Invalid as ex:
            return self.json_message(humanize_error(request.json, ex),
                                     HTTP_BAD_REQUEST)

        name = data.get(ATTR_DEVICE_ID)

        CONFIG_FILE[ATTR_DEVICES][name] = data

        if not _save_config(CONFIG_FILE_PATH, CONFIG_FILE):
            return self.json_message("Error saving device.",
                                     HTTP_INTERNAL_SERVER_ERROR)

        return self.json({"status": "registered"})
Example #23
0
    def __init__(
        self,
        domain: str,
        blueprint_name: str,
        blueprint_data: Any,
        msg_or_exc: vol.Invalid,
    ):
        """Initialize an invalid blueprint error."""
        if isinstance(msg_or_exc, vol.Invalid):
            msg_or_exc = humanize_error(blueprint_data, msg_or_exc)

        super().__init__(
            domain,
            blueprint_name,
            f"Invalid blueprint: {msg_or_exc}",
        )
        self.blueprint_data = blueprint_data
Example #24
0
    def write_options(self):
        """Return True if add-on options is written to data."""
        schema = self.schema
        options = self.options

        try:
            schema(options)
            write_json_file(self.path_options, options)
        except vol.Invalid as ex:
            _LOGGER.error("Add-on %s have wrong options: %s", self._id,
                          humanize_error(options, ex))
        except (OSError, json.JSONDecodeError) as err:
            _LOGGER.error("Add-on %s can't write options: %s", self._id, err)
        else:
            return True

        return False
Example #25
0
    def _read_addons_folder(self, path: Path, repository: Dict) -> None:
        """Read data from add-ons folder."""
        try:
            # Generate a list without artefact, safe for corruptions
            addon_list = [
                addon for addon in path.glob("**/config.json")
                if ".git" not in addon.parts
            ]
        except OSError as err:
            suggestion = None
            if path.stem != StoreType.LOCAL:
                suggestion = [SuggestionType.EXECUTE_RESET]
            self.sys_resolution.create_issue(
                IssueType.CORRUPT_REPOSITORY,
                ContextType.STORE,
                reference=path.stem,
                suggestions=suggestion,
            )
            _LOGGER.critical(
                "Can't process %s because of Filesystem issues: %s",
                repository, err)
            return

        for addon in addon_list:
            try:
                addon_config = read_json_file(addon)
            except JsonFileError:
                _LOGGER.warning("Can't read %s from repository %s", addon,
                                repository)
                continue

            # validate
            try:
                addon_config = SCHEMA_ADDON_CONFIG(addon_config)
            except vol.Invalid as ex:
                _LOGGER.warning("Can't read %s: %s", addon,
                                humanize_error(addon_config, ex))
                continue

            # Generate slug
            addon_slug = f"{repository}_{addon_config[ATTR_SLUG]}"

            # store
            addon_config[ATTR_REPOSITORY] = repository
            addon_config[ATTR_LOCATON] = str(addon.parent)
            self.addons[addon_slug] = addon_config
Example #26
0
    def post(self, request):
        """Handle the POST request for device identification."""
        try:
            data = IDENTIFY_SCHEMA(request.json)
        except vol.Invalid as ex:
            return self.json_message(humanize_error(request.json, ex),
                                     HTTP_BAD_REQUEST)

        name = data.get(ATTR_DEVICE_ID)

        CONFIG_FILE[ATTR_DEVICES][name] = data

        if not _save_config(CONFIG_FILE_PATH, CONFIG_FILE):
            return self.json_message("Error saving device.",
                                     HTTP_INTERNAL_SERVER_ERROR)

        return self.json({"status": "registered"})
Example #27
0
async def auth_provider_from_config(opp: OpenPeerPower, store: AuthStore,
                                    config: dict[str, Any]) -> AuthProvider:
    """Initialize an auth provider from a config."""
    provider_name = config[CONF_TYPE]
    module = await load_auth_provider_module(opp, provider_name)

    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),
        )
        raise

    return AUTH_PROVIDERS[provider_name](opp, store, config)  # type: ignore
Example #28
0
    def save_data(self) -> None:
        """Store data to configuration file."""
        # Validate
        try:
            self._data = self._schema(self._data)
        except vol.Invalid as ex:
            _LOGGER.error("Can't parse data: %s", humanize_error(self._data, ex))

            # Load last valid data
            _LOGGER.warning("Reset %s to last version", self._file)
            self.read_data()
        else:
            # write
            try:
                write_json_file(self._file, self._data)
            except JsonFileError:
                pass
Example #29
0
    async def load(self):
        """Read backup.json from tar file."""
        if not self.tarfile.is_file():
            _LOGGER.error("No tarfile located at %s", self.tarfile)
            return False

        def _load_file():
            """Read backup.json."""
            with tarfile.open(self.tarfile, "r:") as backup:
                if "./snapshot.json" in [
                        entry.name for entry in backup.getmembers()
                ]:
                    # Old backups stil uses "snapshot.json", we need to support that forever
                    json_file = backup.extractfile("./snapshot.json")
                else:
                    json_file = backup.extractfile("./backup.json")
                return json_file.read()

        # read backup.json
        try:
            raw = await self.sys_run_in_executor(_load_file)
        except (tarfile.TarError, KeyError) as err:
            _LOGGER.error("Can't read backup tarfile %s: %s", self.tarfile,
                          err)
            return False

        # parse data
        try:
            raw_dict = json.loads(raw)
        except json.JSONDecodeError as err:
            _LOGGER.error("Can't read data for %s: %s", self.tarfile, err)
            return False

        # validate
        try:
            self._data = SCHEMA_BACKUP(raw_dict)
        except vol.Invalid as err:
            _LOGGER.error(
                "Can't validate data for %s: %s",
                self.tarfile,
                humanize_error(raw_dict, err),
            )
            return False

        return True
Example #30
0
    def post(self, request):
        """Accept the POST request for push registrations from a browser."""
        try:
            data = REGISTER_SCHEMA(request.json)
        except vol.Invalid as ex:
            return self.json_message(humanize_error(request.json, ex),
                                     HTTP_BAD_REQUEST)

        name = ensure_unique_string('unnamed device',
                                    self.registrations.keys())

        self.registrations[name] = data

        if not _save_config(self.json_path, self.registrations):
            return self.json_message('Error saving registration.',
                                     HTTP_INTERNAL_SERVER_ERROR)

        return self.json_message('Push notification subscriber registered.')
Example #31
0
    def read_data(self) -> None:
        """Read JSON file & validate."""
        if self._file.is_file():
            try:
                self._data = read_json_file(self._file)
            except JsonFileError:
                self._data = {}

        # Validate
        try:
            self._data = self._schema(self._data)
        except vol.Invalid as ex:
            _LOGGER.error("Can't parse %s: %s", self._file,
                          humanize_error(self._data, ex))

            # Reset data to default
            _LOGGER.warning("Reset %s to default", self._file)
            self._data = self._schema(_DEFAULT)
Example #32
0
    def write_options(self):
        """Return True if add-on options is written to data."""
        schema = self.schema
        options = self.options

        try:
            schema(options)
            write_json_file(self.path_options, options)
        except vol.Invalid as ex:
            _LOGGER.error("Add-on %s have wrong options: %s", self.slug,
                          humanize_error(options, ex))
        except JsonFileError:
            _LOGGER.error("Add-on %s can't write options", self.slug)
        else:
            _LOGGER.debug("Add-on %s write options: %s", self.slug, options)
            return

        raise AddonsError()
Example #33
0
def _format_config_error(ex, domain, config):
    message = u"Invalid config for [{}]: ".format(domain)
    if u'extra keys not allowed' in ex.error_message:
        message += u'[{}] is an invalid option for [{}]. Check: {}->{}.' \
            .format(ex.path[-1], domain, domain,
                    u'->'.join(str(m) for m in ex.path))
    else:
        message += u'{}.'.format(humanize_error(config, ex))

    if isinstance(config, list):
        return message

    domain_config = config.get(domain, config)
    message += u" (See {}, line {}). ".format(
        getattr(domain_config, '__config_file__', '?'),
        getattr(domain_config, '__line__', '?'))

    return message
Example #34
0
    def post(self, request):
        """Accept the POST request for push registrations from a browser."""
        try:
            data = REGISTER_SCHEMA(request.json)
        except vol.Invalid as ex:
            return self.json_message(humanize_error(request.json, ex),
                                     HTTP_BAD_REQUEST)

        name = ensure_unique_string('unnamed device',
                                    self.registrations.keys())

        self.registrations[name] = data

        if not _save_config(self.json_path, self.registrations):
            return self.json_message('Error saving registration.',
                                     HTTP_INTERNAL_SERVER_ERROR)

        return self.json_message('Push notification subscriber registered.')
Example #35
0
    def read_data(self) -> None:
        """Read configuration file."""
        if self._file.is_file():
            try:
                self._data = read_json_or_yaml_file(self._file)
            except ConfigurationFileError:
                self._data = _DEFAULT

        # Validate
        try:
            self._data = self._schema(self._data)
        except vol.Invalid as ex:
            _LOGGER.critical("Can't parse %s: %s", self._file,
                             humanize_error(self._data, ex))

            # Reset data to default
            _LOGGER.warning("Resetting %s to default", self._file)
            self._data = self._schema(_DEFAULT)
Example #36
0
    def save_data(self):
        """Store data to config file."""
        # Validate
        try:
            self._data = self._schema(self._data)
        except vol.Invalid as ex:
            _LOGGER.error("Can't parse data: %s",
                          humanize_error(self._data, ex))

            # Load last valid data
            _LOGGER.warning("Reset %s to last version", self._file)
            self.read_data()
            return

        # write
        try:
            write_json_file(self._file, self._data)
        except (OSError, json.JSONDecodeError) as err:
            _LOGGER.error("Can't store config in %s: %s", self._file, err)
Example #37
0
    def read_data(self):
        """Read json file & validate."""
        if self._file.is_file():
            try:
                self._data = read_json_file(self._file)
            except (OSError, json.JSONDecodeError):
                _LOGGER.warning("Can't read %s", self._file)
                self._data = {}

        # Validate
        try:
            self._data = self._schema(self._data)
        except vol.Invalid as ex:
            _LOGGER.error("Can't parse %s: %s", self._file,
                          humanize_error(self._data, ex))

            # Reset data to default
            _LOGGER.warning("Reset %s to default", self._file)
            self._data = self._schema({})
Example #38
0
def test_humanize_error():
    data = {
        'a': 'not an int',
        'b': [123]
    }
    schema = Schema({
        'a': int,
        'b': [str]
    })
    try:
        schema(data)
    except MultipleInvalid as e:
        assert_equal(
            humanize_error(data, e),
            "expected int for dictionary value @ data['a']. Got 'not an int'\n"
            "expected str @ data['b'][0]. Got 123"
        )
    else:
        assert False, 'Did not raise MultipleInvalid'
Example #39
0
async def api_validate(schema: vol.Schema,
                       request: web.Request,
                       origin: Optional[List[str]] = None) -> Dict[str, Any]:
    """Validate request data with schema."""
    data: Dict[str, Any] = await request.json(loads=json_loads)
    try:
        data_validated = schema(data)
    except vol.Invalid as ex:
        raise APIError(humanize_error(data, ex)) from None

    if not origin:
        return data_validated

    for origin_value in origin:
        if origin_value not in data_validated:
            continue
        data_validated[origin_value] = data[origin_value]

    return data_validated
Example #40
0
def test_humanize_error():
    data = {
        'a': 'not an int',
        'b': [123]
    }
    schema = Schema({
        'a': int,
        'b': [str]
    })
    try:
        schema(data)
    except MultipleInvalid as e:
        assert_equal(
            humanize_error(data, e),
            "expected int for dictionary value @ data['a']. Got 'not an int'\n"
            "expected str @ data['b'][0]. Got 123"
        )
    else:
        assert False, 'Did not raise MultipleInvalid'
Example #41
0
def run_args(args) -> pd.DataFrame:
    with open(args.config) as f:
        config = yaml.load(f)
    for k, v in vars(args).items():
        if v is not None and "." in k:
            config = toolz.assoc_in(config, k.split("."), v)
            print(k, v)
    if args.logdir is not None:
        config['train']['logdir'] = args.logdir
    try:
        cfg = voluptuous.Schema({
            'train': TrainConfig.schema,
            'version': str,
        },
                                extra=voluptuous.REMOVE_EXTRA,
                                required=True)(config)
    except voluptuous.error.Error as e:
        logger.error(humanize_error(config, e))
        sys.exit(1)

    logger.info(f"Parsed config\n{pformat(cfg)}")
    formatter = logging.Formatter(
        "%(asctime)s [%(levelname)5s]:%(name)20s: %(message)s")
    train_cfg: TrainConfig = cfg['train']
    os.makedirs(train_cfg.logdir, exist_ok=True)
    fn = os.path.join(train_cfg.logdir,
                      f"{getattr(args, 'name', 'mincall')}.log")
    h = (logging.FileHandler(fn))
    h.setLevel(logging.INFO)
    h.setFormatter(formatter)
    name_filter = ExtraFieldsFilter({"run_name": args.name})
    root_logger = logging.getLogger()

    root_logger.addHandler(h)
    root_logger.addFilter(name_filter)
    logging.info(f"Added handler to {fn}")
    try:
        with tf.Graph().as_default():
            return run(cfg['train'])
    finally:
        root_logger.removeHandler(h)
        root_logger.removeFilter(name_filter)
Example #42
0
def log_exception(ex, domain, config):
    """Generate log exception for config validation."""
    message = 'Invalid config for [{}]: '.format(domain)

    if 'extra keys not allowed' in ex.error_message:
        message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\
                   .format(ex.path[-1], domain, domain,
                           '->'.join('%s' % m for m in ex.path))
    else:
        message += '{}.'.format(humanize_error(config, ex))

    if hasattr(config, '__line__'):
        message += " (See {}:{})".format(config.__config_file__,
                                         config.__line__ or '?')

    if domain != 'homeassistant':
        message += (' Please check the docs at '
                    'https://home-assistant.io/components/{}/'.format(domain))

    _LOGGER.error(message)
Example #43
0
 async def ws_create_item(
     self, hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
 ) -> None:
     """Create a item."""
     try:
         data = dict(msg)
         data.pop("id")
         data.pop("type")
         item = await self.storage_collection.async_create_item(data)
         connection.send_result(msg["id"], item)
     except vol.Invalid as err:
         connection.send_error(
             msg["id"],
             websocket_api.const.ERR_INVALID_FORMAT,
             humanize_error(data, err),
         )
     except ValueError as err:
         connection.send_error(
             msg["id"], websocket_api.const.ERR_INVALID_FORMAT, str(err)
         )
Example #44
0
    def __init__(self, json_file, schema):
        """Initialize hass object."""
        self._file = json_file
        self._schema = schema
        self._data = {}

        # init or load data
        if self._file.is_file():
            try:
                self._data = read_json_file(self._file)
            except (OSError, json.JSONDecodeError):
                _LOGGER.warning("Can't read %s", self._file)
                self._data = {}

        # validate
        try:
            self._data = self._schema(self._data)
        except vol.Invalid as ex:
            _LOGGER.error("Can't parse %s -> %s", self._file,
                          humanize_error(self._data, ex))
Example #45
0
 def set_child_value(self, child_id, value_type, value, **kwargs):
     """Set a child sensor's value."""
     children = kwargs.get("children", self.children)
     if not isinstance(children, dict) or child_id not in children:
         return None
     msg_type = kwargs.get("msg_type", 1)
     ack = kwargs.get("ack", 0)
     msg = Message().modify(
         node_id=self.sensor_id,
         child_id=child_id,
         type=msg_type,
         ack=ack,
         sub_type=value_type,
         payload=value,
     )
     msg_string = msg.encode()
     if msg_string is None:
         _LOGGER.error(
             "Not a valid message: node %s, child %s, type %s, ack %s, "
             "sub_type %s, payload %s",
             self.sensor_id,
             child_id,
             msg_type,
             ack,
             value_type,
             value,
         )
         return None
     msg = Message(msg_string)
     try:
         msg.validate(self.protocol_version)
     except AttributeError as exc:
         _LOGGER.error("Invalid %s: %s", msg, exc)
         return None
     except vol.Invalid as exc:
         _LOGGER.error("Invalid %s: %s", msg,
                       humanize_error(msg.__dict__, exc))
         return None
     child = children[msg.child_id]
     child.values[msg.sub_type] = msg.payload
     return msg_string
Example #46
0
    def _validate_docs_schema(self, doc, schema, name, error_code):
        # TODO: Add line/col
        errors = []
        try:
            schema(doc)
        except Exception as e:
            for error in e.errors:
                error.data = doc
            errors.extend(e.errors)

        for error in errors:
            path = [str(p) for p in error.path]

            if isinstance(error.data, dict):
                error_message = humanize_error(error.data, error)
            else:
                error_message = error

            self.reporter.error(
                path=self.object_path,
                code=error_code,
                msg='%s.%s: %s' % (name, '.'.join(path), error_message)
            )
Example #47
0
    async def async_handle(self, msg):
        """Handle authentication."""
        try:
            msg = AUTH_MESSAGE_SCHEMA(msg)
        except vol.Invalid as err:
            error_msg = 'Auth message incorrectly formatted: {}'.format(
                humanize_error(msg, err))
            self._logger.warning(error_msg)
            self._send_message(auth_invalid_message(error_msg))
            raise Disconnect

        if 'access_token' in msg:
            self._logger.debug("Received access_token")
            refresh_token = \
                await self._hass.auth.async_validate_access_token(
                    msg['access_token'])
            if refresh_token is not None:
                return await self._async_finish_auth(
                    refresh_token.user, refresh_token)

        elif self._hass.auth.support_legacy and 'api_password' in msg:
            self._logger.info(
                "Received api_password, it is going to deprecate, please use"
                " access_token instead. For instructions, see https://"
                "developers.home-assistant.io/docs/en/external_api_websocket"
                ".html#authentication-phase"
            )
            user = await legacy_api_password.async_validate_password(
                self._hass, msg['api_password'])
            if user is not None:
                return await self._async_finish_auth(user, None)

        self._send_message(auth_invalid_message(
            'Invalid access token or password'))
        await process_wrong_login(self._request)
        raise Disconnect
Example #48
0
    def handle(self):
        """Handle the websocket connection."""
        wsock = self.wsock = web.WebSocketResponse()
        yield from wsock.prepare(self.request)

        # Set up to cancel this connection when Home Assistant shuts down
        socket_task = asyncio.Task.current_task(loop=self.hass.loop)

        @callback
        def cancel_connection(event):
            """Cancel this connection."""
            socket_task.cancel()

        unsub_stop = self.hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP,
                                                cancel_connection)

        self.debug('Connected')

        msg = None
        authenticated = False

        try:
            if self.request[KEY_AUTHENTICATED]:
                authenticated = True

            else:
                self.send_message(auth_required_message())
                msg = yield from wsock.receive_json()
                msg = AUTH_MESSAGE_SCHEMA(msg)

                if validate_password(self.request, msg['api_password']):
                    authenticated = True

                else:
                    self.debug('Invalid password')
                    self.send_message(auth_invalid_message('Invalid password'))

            if not authenticated:
                yield from process_wrong_login(self.request)
                return wsock

            self.send_message(auth_ok_message())

            msg = yield from wsock.receive_json()

            last_id = 0

            while msg:
                self.debug('Received', msg)
                msg = BASE_COMMAND_MESSAGE_SCHEMA(msg)
                cur_id = msg['id']

                if cur_id <= last_id:
                    self.send_message(error_message(
                        cur_id, ERR_ID_REUSE,
                        'Identifier values have to increase.'))

                else:
                    handler_name = 'handle_{}'.format(msg['type'])
                    getattr(self, handler_name)(msg)

                last_id = cur_id
                msg = yield from wsock.receive_json()

        except vol.Invalid as err:
            error_msg = 'Message incorrectly formatted: '
            if msg:
                error_msg += humanize_error(msg, err)
            else:
                error_msg += str(err)

            self.log_error(error_msg)

            if not authenticated:
                self.send_message(auth_invalid_message(error_msg))

            else:
                if isinstance(msg, dict):
                    iden = msg.get('id')
                else:
                    iden = None

                self.send_message(error_message(iden, ERR_INVALID_FORMAT,
                                                error_msg))

        except TypeError as err:
            if wsock.closed:
                self.debug('Connection closed by client')
            else:
                self.log_error('Unexpected TypeError', msg)

        except ValueError as err:
            msg = 'Received invalid JSON'
            value = getattr(err, 'doc', None)  # Py3.5+ only
            if value:
                msg += ': {}'.format(value)
            self.log_error(msg)

        except asyncio.CancelledError:
            self.debug('Connection cancelled by server')

        except Exception:  # pylint: disable=broad-except
            error = 'Unexpected error inside websocket API. '
            if msg is not None:
                error += str(msg)
            _LOGGER.exception(error)

        finally:
            unsub_stop()

            for unsub in self.event_listeners.values():
                unsub()

            yield from wsock.close()
            self.debug('Closed connection')

        return wsock
Example #49
0
    def handle(self, request):
        """Handle the websocket connection."""
        wsock = self.wsock = web.WebSocketResponse()
        yield from wsock.prepare(request)
        self.debug("Connected")

        # Get a reference to current task so we can cancel our connection
        self._handle_task = asyncio.Task.current_task(loop=self.hass.loop)

        @callback
        def handle_hass_stop(event):
            """Cancel this connection."""
            self.cancel()

        unsub_stop = self.hass.bus.async_listen(
            EVENT_HOMEASSISTANT_STOP, handle_hass_stop)
        self._writer_task = self.hass.async_add_job(self._writer())
        final_message = None
        msg = None
        authenticated = False

        try:
            if request[KEY_AUTHENTICATED]:
                authenticated = True

            else:
                yield from self.wsock.send_json(auth_required_message())
                msg = yield from wsock.receive_json()
                msg = AUTH_MESSAGE_SCHEMA(msg)

                if validate_password(request, msg['api_password']):
                    authenticated = True

                else:
                    self.debug("Invalid password")
                    yield from self.wsock.send_json(
                        auth_invalid_message('Invalid password'))

            if not authenticated:
                yield from process_wrong_login(request)
                return wsock

            yield from self.wsock.send_json(auth_ok_message())

            # ---------- AUTH PHASE OVER ----------

            msg = yield from wsock.receive_json()
            last_id = 0

            while msg:
                self.debug("Received", msg)
                msg = BASE_COMMAND_MESSAGE_SCHEMA(msg)
                cur_id = msg['id']

                if cur_id <= last_id:
                    self.to_write.put_nowait(error_message(
                        cur_id, ERR_ID_REUSE,
                        'Identifier values have to increase.'))

                else:
                    handler_name = 'handle_{}'.format(msg['type'])
                    getattr(self, handler_name)(msg)

                last_id = cur_id
                msg = yield from wsock.receive_json()

        except vol.Invalid as err:
            error_msg = "Message incorrectly formatted: "
            if msg:
                error_msg += humanize_error(msg, err)
            else:
                error_msg += str(err)

            self.log_error(error_msg)

            if not authenticated:
                final_message = auth_invalid_message(error_msg)

            else:
                if isinstance(msg, dict):
                    iden = msg.get('id')
                else:
                    iden = None

                final_message = error_message(
                    iden, ERR_INVALID_FORMAT, error_msg)

        except TypeError as err:
            if wsock.closed:
                self.debug("Connection closed by client")
            else:
                self.log_error("Unexpected TypeError", msg)

        except ValueError as err:
            msg = "Received invalid JSON"
            value = getattr(err, 'doc', None)  # Py3.5+ only
            if value:
                msg += ': {}'.format(value)
            self.log_error(msg)
            self._writer_task.cancel()

        except asyncio.CancelledError:
            self.debug("Connection cancelled by server")

        except asyncio.QueueFull:
            self.log_error("Client exceeded max pending messages [1]:",
                           MAX_PENDING_MSG)
            self._writer_task.cancel()

        except Exception:  # pylint: disable=broad-except
            error = "Unexpected error inside websocket API. "
            if msg is not None:
                error += str(msg)
            _LOGGER.exception(error)

        finally:
            unsub_stop()

            for unsub in self.event_listeners.values():
                unsub()

            try:
                if final_message is not None:
                    self.to_write.put_nowait(final_message)
                self.to_write.put_nowait(None)
                # Make sure all error messages are written before closing
                yield from self._writer_task
            except asyncio.QueueFull:
                self._writer_task.cancel()

            yield from wsock.close()
            self.debug("Closed connection")

        return wsock
    async def handle(self):
        """Handle the websocket connection."""
        request = self.request
        wsock = self.wsock = web.WebSocketResponse(heartbeat=55)
        await wsock.prepare(request)
        self.debug("Connected")

        self._handle_task = asyncio.Task.current_task(loop=self.hass.loop)

        @callback
        def handle_hass_stop(event):
            """Cancel this connection."""
            self.cancel()

        unsub_stop = self.hass.bus.async_listen(
            EVENT_HOMEASSISTANT_STOP, handle_hass_stop)
        self._writer_task = self.hass.async_add_job(self._writer())
        final_message = None
        msg = None
        authenticated = False

        try:
            if request[KEY_AUTHENTICATED]:
                authenticated = True

            # always request auth when auth is active
            #   even request passed pre-authentication (trusted networks)
            # or when using legacy api_password
            if self.hass.auth.active or not authenticated:
                self.debug("Request auth")
                await self.wsock.send_json(auth_required_message())
                msg = await wsock.receive_json()
                msg = AUTH_MESSAGE_SCHEMA(msg)

                if self.hass.auth.active and 'access_token' in msg:
                    self.debug("Received access_token")
                    refresh_token = \
                        await self.hass.auth.async_validate_access_token(
                            msg['access_token'])
                    authenticated = refresh_token is not None
                    if authenticated:
                        request['hass_user'] = refresh_token.user
                        request['refresh_token_id'] = refresh_token.id

                elif ((not self.hass.auth.active or
                       self.hass.auth.support_legacy) and
                      'api_password' in msg):
                    self.debug("Received api_password")
                    authenticated = validate_password(
                        request, msg['api_password'])

            if not authenticated:
                self.debug("Authorization failed")
                await self.wsock.send_json(
                    auth_invalid_message('Invalid access token or password'))
                await process_wrong_login(request)
                return wsock

            self.debug("Auth OK")
            await process_success_login(request)
            await self.wsock.send_json(auth_ok_message())

            # ---------- AUTH PHASE OVER ----------

            msg = await wsock.receive_json()
            last_id = 0
            handlers = self.hass.data[DOMAIN]

            while msg:
                self.debug("Received", msg)
                msg = MINIMAL_MESSAGE_SCHEMA(msg)
                cur_id = msg['id']

                if cur_id <= last_id:
                    self.to_write.put_nowait(error_message(
                        cur_id, ERR_ID_REUSE,
                        'Identifier values have to increase.'))

                elif msg['type'] not in handlers:
                    self.log_error(
                        'Received invalid command: {}'.format(msg['type']))
                    self.to_write.put_nowait(error_message(
                        cur_id, ERR_UNKNOWN_COMMAND,
                        'Unknown command.'))

                else:
                    handler, schema = handlers[msg['type']]
                    try:
                        handler(self.hass, self, schema(msg))
                    except Exception:  # pylint: disable=broad-except
                        _LOGGER.exception('Error handling message: %s', msg)
                        self.to_write.put_nowait(error_message(
                            cur_id, ERR_UNKNOWN_ERROR,
                            'Unknown error.'))

                last_id = cur_id
                msg = await wsock.receive_json()

        except vol.Invalid as err:
            error_msg = "Message incorrectly formatted: "
            if msg:
                error_msg += humanize_error(msg, err)
            else:
                error_msg += str(err)

            self.log_error(error_msg)

            if not authenticated:
                final_message = auth_invalid_message(error_msg)

            else:
                if isinstance(msg, dict):
                    iden = msg.get('id')
                else:
                    iden = None

                final_message = error_message(
                    iden, ERR_INVALID_FORMAT, error_msg)

        except TypeError as err:
            if wsock.closed:
                self.debug("Connection closed by client")
            else:
                _LOGGER.exception("Unexpected TypeError: %s", err)

        except ValueError as err:
            msg = "Received invalid JSON"
            value = getattr(err, 'doc', None)  # Py3.5+ only
            if value:
                msg += ': {}'.format(value)
            self.log_error(msg)
            self._writer_task.cancel()

        except CANCELLATION_ERRORS:
            self.debug("Connection cancelled")

        except asyncio.QueueFull:
            self.log_error("Client exceeded max pending messages [1]:",
                           MAX_PENDING_MSG)
            self._writer_task.cancel()

        except Exception:  # pylint: disable=broad-except
            error = "Unexpected error inside websocket API. "
            if msg is not None:
                error += str(msg)
            _LOGGER.exception(error)

        finally:
            unsub_stop()

            for unsub in self.event_listeners.values():
                unsub()

            try:
                if final_message is not None:
                    self.to_write.put_nowait(final_message)
                self.to_write.put_nowait(None)
                # Make sure all error messages are written before closing
                await self._writer_task
            except asyncio.QueueFull:
                self._writer_task.cancel()

            await wsock.close()
            self.debug("Closed connection")

        return wsock