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)
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)
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
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
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)
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]})
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)
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))
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)
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
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")
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)
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
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
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
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)
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)
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)
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
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"})
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
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
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
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
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
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
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.')
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)
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()
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
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)
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)
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({})
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'
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
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)
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)
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) )
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))
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
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) )
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
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
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