Add more validation for mysensors (#5493)

* Move isdevice validator under helpers.config_validation.
* Check that all persistence files are set and are unique if any is set
  by user. This is necessary to avoid file name clashes.
* Check that a set persistence file has an existing and writable
  directory.
* Check that a device is either a valid device file, "mqtt", or a valid
  domain name or ip address.
This commit is contained in:
Martin Hjelmare 2017-01-25 07:04:44 +01:00 committed by Paulus Schoutsen
parent b57f5728c5
commit a09a772f43
3 changed files with 61 additions and 20 deletions

View File

@ -5,12 +5,15 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors/ https://home-assistant.io/components/sensor.mysensors/
""" """
import logging import logging
import os
import socket import socket
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.bootstrap import setup_component from homeassistant.bootstrap import setup_component
from homeassistant.components.mqtt import (valid_publish_topic,
valid_subscribe_topic)
from homeassistant.const import (ATTR_BATTERY_LEVEL, CONF_NAME, from homeassistant.const import (ATTR_BATTERY_LEVEL, CONF_NAME,
CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_START, CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON) EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON)
@ -44,22 +47,61 @@ REQUIREMENTS = [
'https://github.com/theolind/pymysensors/archive/' 'https://github.com/theolind/pymysensors/archive/'
'0b705119389be58332f17753c53167f551254b6c.zip#pymysensors==0.8'] '0b705119389be58332f17753c53167f551254b6c.zip#pymysensors==0.8']
def is_socket_address(value):
"""Validate that value is a valid address."""
try:
socket.getaddrinfo(value, None)
return value
except OSError:
raise vol.Invalid('Device is not a valid domain name or ip address')
def has_parent_dir(value):
"""Validate that value is in an existing directory which is writetable."""
parent = os.path.dirname(os.path.realpath(value))
is_dir_writable = os.path.isdir(parent) and os.access(parent, os.W_OK)
if not is_dir_writable:
raise vol.Invalid(
'{} directory does not exist or is not writetable'.format(parent))
return value
def has_all_unique_files(value):
"""Validate that all persistence files are unique and set if any is set."""
persistence_files = [
gateway.get(CONF_PERSISTENCE_FILE) for gateway in value]
if None in persistence_files and any(
name is not None for name in persistence_files):
raise vol.Invalid(
'persistence file name of all devices must be set if any is set')
if not all(name is None for name in persistence_files):
schema = vol.Schema(vol.Unique())
schema(persistence_files)
return value
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({ DOMAIN: vol.Schema({
vol.Required(CONF_GATEWAYS): vol.All(cv.ensure_list, [ vol.Required(CONF_GATEWAYS): vol.All(
{ cv.ensure_list, has_all_unique_files,
vol.Required(CONF_DEVICE): cv.string, [{
vol.Optional(CONF_PERSISTENCE_FILE): cv.string, vol.Required(CONF_DEVICE):
vol.Any(cv.isdevice, MQTT_COMPONENT, is_socket_address),
vol.Optional(CONF_PERSISTENCE_FILE):
vol.All(cv.string, has_parent_dir),
vol.Optional( vol.Optional(
CONF_BAUD_RATE, CONF_BAUD_RATE,
default=DEFAULT_BAUD_RATE): cv.positive_int, default=DEFAULT_BAUD_RATE): cv.positive_int,
vol.Optional( vol.Optional(
CONF_TCP_PORT, CONF_TCP_PORT,
default=DEFAULT_TCP_PORT): cv.port, default=DEFAULT_TCP_PORT): cv.port,
vol.Optional(CONF_TOPIC_IN_PREFIX, default=''): cv.string, vol.Optional(
vol.Optional(CONF_TOPIC_OUT_PREFIX, default=''): cv.string, CONF_TOPIC_IN_PREFIX, default=''): valid_subscribe_topic,
}, vol.Optional(
]), CONF_TOPIC_OUT_PREFIX, default=''): valid_publish_topic,
}]
),
vol.Optional(CONF_DEBUG, default=False): cv.boolean, vol.Optional(CONF_DEBUG, default=False): cv.boolean,
vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
vol.Optional(CONF_PERSISTENCE, default=True): cv.boolean, vol.Optional(CONF_PERSISTENCE, default=True): cv.boolean,
@ -100,7 +142,7 @@ def setup(hass, config):
out_prefix=out_prefix, retain=retain) out_prefix=out_prefix, retain=retain)
else: else:
try: try:
socket.inet_aton(device) socket.getaddrinfo(device, None)
# valid ip address # valid ip address
gateway = mysensors.TCPGateway( gateway = mysensors.TCPGateway(
device, event_callback=None, persistence=persistence, device, event_callback=None, persistence=persistence,

View File

@ -4,7 +4,6 @@ Use serial protocol of Acer projector to obtain state of the projector.
For more details about this component, please refer to the documentation For more details about this component, please refer to the documentation
at https://home-assistant.io/components/switch.acer_projector/ at https://home-assistant.io/components/switch.acer_projector/
""" """
import os
import logging import logging
import re import re
@ -47,17 +46,8 @@ CMD_DICT = {LAMP: '* 0 Lamp ?\r',
STATE_OFF: '* 0 IR 002\r'} STATE_OFF: '* 0 IR 002\r'}
def isdevice(dev):
"""Check if dev a real device."""
try:
os.stat(dev)
return str(dev)
except OSError:
raise vol.Invalid("No device found!")
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_FILENAME): isdevice, vol.Required(CONF_FILENAME): cv.isdevice,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_WRITE_TIMEOUT, default=DEFAULT_WRITE_TIMEOUT): vol.Optional(CONF_WRITE_TIMEOUT, default=DEFAULT_WRITE_TIMEOUT):

View File

@ -70,6 +70,15 @@ def boolean(value: Any) -> bool:
return bool(value) return bool(value)
def isdevice(value):
"""Validate that value is a real device."""
try:
os.stat(value)
return str(value)
except OSError:
raise vol.Invalid('No device at {} found'.format(value))
def isfile(value: Any) -> str: def isfile(value: Any) -> str:
"""Validate that the value is an existing file.""" """Validate that the value is an existing file."""
if value is None: if value is None: