mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 01:07:10 +00:00
Make more max lengths constants and add additional MaxLengthExceeded checks (#50337)
* Add more MaxLengthExceeded checks * remove some validations to reduce performance impact * check length of generated entity ID * dont check entity ID twice and use single context id length constant * fix test * add missing test
This commit is contained in:
parent
abd6f739e8
commit
58e37435b3
@ -20,7 +20,14 @@ from sqlalchemy.ext.declarative import declarative_base
|
|||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
from homeassistant.const import MAX_LENGTH_EVENT_TYPE
|
from homeassistant.const import (
|
||||||
|
MAX_LENGTH_EVENT_CONTEXT_ID,
|
||||||
|
MAX_LENGTH_EVENT_EVENT_TYPE,
|
||||||
|
MAX_LENGTH_EVENT_ORIGIN,
|
||||||
|
MAX_LENGTH_STATE_DOMAIN,
|
||||||
|
MAX_LENGTH_STATE_ENTITY_ID,
|
||||||
|
MAX_LENGTH_STATE_STATE,
|
||||||
|
)
|
||||||
from homeassistant.core import Context, Event, EventOrigin, State, split_entity_id
|
from homeassistant.core import Context, Event, EventOrigin, State, split_entity_id
|
||||||
from homeassistant.helpers.json import JSONEncoder
|
from homeassistant.helpers.json import JSONEncoder
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
@ -63,14 +70,14 @@ class Events(Base): # type: ignore
|
|||||||
}
|
}
|
||||||
__tablename__ = TABLE_EVENTS
|
__tablename__ = TABLE_EVENTS
|
||||||
event_id = Column(Integer, Identity(), primary_key=True)
|
event_id = Column(Integer, Identity(), primary_key=True)
|
||||||
event_type = Column(String(MAX_LENGTH_EVENT_TYPE))
|
event_type = Column(String(MAX_LENGTH_EVENT_EVENT_TYPE))
|
||||||
event_data = Column(Text().with_variant(mysql.LONGTEXT, "mysql"))
|
event_data = Column(Text().with_variant(mysql.LONGTEXT, "mysql"))
|
||||||
origin = Column(String(32))
|
origin = Column(String(MAX_LENGTH_EVENT_ORIGIN))
|
||||||
time_fired = Column(DATETIME_TYPE, index=True)
|
time_fired = Column(DATETIME_TYPE, index=True)
|
||||||
created = Column(DATETIME_TYPE, default=dt_util.utcnow)
|
created = Column(DATETIME_TYPE, default=dt_util.utcnow)
|
||||||
context_id = Column(String(36), index=True)
|
context_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True)
|
||||||
context_user_id = Column(String(36), index=True)
|
context_user_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True)
|
||||||
context_parent_id = Column(String(36), index=True)
|
context_parent_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True)
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
# Used for fetching events at a specific time
|
# Used for fetching events at a specific time
|
||||||
@ -130,9 +137,9 @@ class States(Base): # type: ignore
|
|||||||
}
|
}
|
||||||
__tablename__ = TABLE_STATES
|
__tablename__ = TABLE_STATES
|
||||||
state_id = Column(Integer, Identity(), primary_key=True)
|
state_id = Column(Integer, Identity(), primary_key=True)
|
||||||
domain = Column(String(64))
|
domain = Column(String(MAX_LENGTH_STATE_DOMAIN))
|
||||||
entity_id = Column(String(255))
|
entity_id = Column(String(MAX_LENGTH_STATE_ENTITY_ID))
|
||||||
state = Column(String(255))
|
state = Column(String(MAX_LENGTH_STATE_STATE))
|
||||||
attributes = Column(Text().with_variant(mysql.LONGTEXT, "mysql"))
|
attributes = Column(Text().with_variant(mysql.LONGTEXT, "mysql"))
|
||||||
event_id = Column(
|
event_id = Column(
|
||||||
Integer, ForeignKey("events.event_id", ondelete="CASCADE"), index=True
|
Integer, ForeignKey("events.event_id", ondelete="CASCADE"), index=True
|
||||||
|
@ -26,9 +26,14 @@ ENTITY_MATCH_ALL: Final = "all"
|
|||||||
# If no name is specified
|
# If no name is specified
|
||||||
DEVICE_DEFAULT_NAME: Final = "Unnamed Device"
|
DEVICE_DEFAULT_NAME: Final = "Unnamed Device"
|
||||||
|
|
||||||
# Max characters for an event_type (changing this requires a recorder
|
# Max characters for data stored in the recorder (changes to these limits would require
|
||||||
# database migration)
|
# a database migration)
|
||||||
MAX_LENGTH_EVENT_TYPE: Final = 64
|
MAX_LENGTH_EVENT_EVENT_TYPE: Final = 64
|
||||||
|
MAX_LENGTH_EVENT_ORIGIN: Final = 32
|
||||||
|
MAX_LENGTH_EVENT_CONTEXT_ID: Final = 36
|
||||||
|
MAX_LENGTH_STATE_DOMAIN: Final = 64
|
||||||
|
MAX_LENGTH_STATE_ENTITY_ID: Final = 255
|
||||||
|
MAX_LENGTH_STATE_STATE: Final = 255
|
||||||
|
|
||||||
# Sun events
|
# Sun events
|
||||||
SUN_EVENT_SUNSET: Final = "sunset"
|
SUN_EVENT_SUNSET: Final = "sunset"
|
||||||
|
@ -47,7 +47,8 @@ from homeassistant.const import (
|
|||||||
EVENT_TIMER_OUT_OF_SYNC,
|
EVENT_TIMER_OUT_OF_SYNC,
|
||||||
LENGTH_METERS,
|
LENGTH_METERS,
|
||||||
MATCH_ALL,
|
MATCH_ALL,
|
||||||
MAX_LENGTH_EVENT_TYPE,
|
MAX_LENGTH_EVENT_EVENT_TYPE,
|
||||||
|
MAX_LENGTH_STATE_STATE,
|
||||||
__version__,
|
__version__,
|
||||||
)
|
)
|
||||||
from homeassistant.exceptions import (
|
from homeassistant.exceptions import (
|
||||||
@ -130,7 +131,7 @@ def valid_entity_id(entity_id: str) -> bool:
|
|||||||
|
|
||||||
def valid_state(state: str) -> bool:
|
def valid_state(state: str) -> bool:
|
||||||
"""Test if a state is valid."""
|
"""Test if a state is valid."""
|
||||||
return len(state) < 256
|
return len(state) <= MAX_LENGTH_STATE_STATE
|
||||||
|
|
||||||
|
|
||||||
def callback(func: CALLABLE_T) -> CALLABLE_T:
|
def callback(func: CALLABLE_T) -> CALLABLE_T:
|
||||||
@ -700,8 +701,10 @@ class EventBus:
|
|||||||
|
|
||||||
This method must be run in the event loop.
|
This method must be run in the event loop.
|
||||||
"""
|
"""
|
||||||
if len(event_type) > MAX_LENGTH_EVENT_TYPE:
|
if len(event_type) > MAX_LENGTH_EVENT_EVENT_TYPE:
|
||||||
raise MaxLengthExceeded(event_type, "event_type", MAX_LENGTH_EVENT_TYPE)
|
raise MaxLengthExceeded(
|
||||||
|
event_type, "event_type", MAX_LENGTH_EVENT_EVENT_TYPE
|
||||||
|
)
|
||||||
|
|
||||||
listeners = self._listeners.get(event_type, [])
|
listeners = self._listeners.get(event_type, [])
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ from homeassistant.const import (
|
|||||||
ATTR_SUPPORTED_FEATURES,
|
ATTR_SUPPORTED_FEATURES,
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
EVENT_HOMEASSISTANT_START,
|
EVENT_HOMEASSISTANT_START,
|
||||||
|
MAX_LENGTH_STATE_DOMAIN,
|
||||||
|
MAX_LENGTH_STATE_ENTITY_ID,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import (
|
from homeassistant.core import (
|
||||||
@ -33,6 +35,7 @@ from homeassistant.core import (
|
|||||||
split_entity_id,
|
split_entity_id,
|
||||||
valid_entity_id,
|
valid_entity_id,
|
||||||
)
|
)
|
||||||
|
from homeassistant.exceptions import MaxLengthExceeded
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
|
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
@ -201,6 +204,10 @@ class EntityRegistry:
|
|||||||
Conflicts checked against registered and currently existing entities.
|
Conflicts checked against registered and currently existing entities.
|
||||||
"""
|
"""
|
||||||
preferred_string = f"{domain}.{slugify(suggested_object_id)}"
|
preferred_string = f"{domain}.{slugify(suggested_object_id)}"
|
||||||
|
|
||||||
|
if len(domain) > MAX_LENGTH_STATE_DOMAIN:
|
||||||
|
raise MaxLengthExceeded(domain, "domain", MAX_LENGTH_STATE_DOMAIN)
|
||||||
|
|
||||||
test_string = preferred_string
|
test_string = preferred_string
|
||||||
if not known_object_ids:
|
if not known_object_ids:
|
||||||
known_object_ids = {}
|
known_object_ids = {}
|
||||||
@ -214,6 +221,11 @@ class EntityRegistry:
|
|||||||
tries += 1
|
tries += 1
|
||||||
test_string = f"{preferred_string}_{tries}"
|
test_string = f"{preferred_string}_{tries}"
|
||||||
|
|
||||||
|
if len(test_string) > MAX_LENGTH_STATE_ENTITY_ID:
|
||||||
|
raise MaxLengthExceeded(
|
||||||
|
test_string, "generated_entity_id", MAX_LENGTH_STATE_ENTITY_ID
|
||||||
|
)
|
||||||
|
|
||||||
return test_string
|
return test_string
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@ -6,6 +6,7 @@ import pytest
|
|||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE
|
from homeassistant.const import EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE
|
||||||
from homeassistant.core import CoreState, callback, valid_entity_id
|
from homeassistant.core import CoreState, callback, valid_entity_id
|
||||||
|
from homeassistant.exceptions import MaxLengthExceeded
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
@ -904,3 +905,47 @@ async def test_disabled_entities_excluded_from_entity_list(hass, registry):
|
|||||||
registry, device_entry.id, include_disabled_entities=True
|
registry, device_entry.id, include_disabled_entities=True
|
||||||
)
|
)
|
||||||
assert entries == [entry1, entry2]
|
assert entries == [entry1, entry2]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_entity_max_length_exceeded(hass, registry):
|
||||||
|
"""Test that an exception is raised when the max character length is exceeded."""
|
||||||
|
|
||||||
|
long_entity_id_name = (
|
||||||
|
"1234567890123456789012345678901234567890123456789012345678901234567890"
|
||||||
|
"1234567890123456789012345678901234567890123456789012345678901234567890"
|
||||||
|
"1234567890123456789012345678901234567890123456789012345678901234567890"
|
||||||
|
"1234567890123456789012345678901234567890123456789012345678901234567890"
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(MaxLengthExceeded) as exc_info:
|
||||||
|
registry.async_generate_entity_id("sensor", long_entity_id_name)
|
||||||
|
|
||||||
|
assert exc_info.value.property_name == "generated_entity_id"
|
||||||
|
assert exc_info.value.max_length == 255
|
||||||
|
assert exc_info.value.value == f"sensor.{long_entity_id_name}"
|
||||||
|
|
||||||
|
# Try again but against the domain
|
||||||
|
long_domain_name = long_entity_id_name
|
||||||
|
with pytest.raises(MaxLengthExceeded) as exc_info:
|
||||||
|
registry.async_generate_entity_id(long_domain_name, "sensor")
|
||||||
|
|
||||||
|
assert exc_info.value.property_name == "domain"
|
||||||
|
assert exc_info.value.max_length == 64
|
||||||
|
assert exc_info.value.value == long_domain_name
|
||||||
|
|
||||||
|
# Try again but force a number to get added to the entity ID
|
||||||
|
long_entity_id_name = (
|
||||||
|
"1234567890123456789012345678901234567890123456789012345678901234567890"
|
||||||
|
"1234567890123456789012345678901234567890123456789012345678901234567890"
|
||||||
|
"1234567890123456789012345678901234567890123456789012345678901234567890"
|
||||||
|
"1234567890123456789012345678901234567"
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(MaxLengthExceeded) as exc_info:
|
||||||
|
registry.async_generate_entity_id(
|
||||||
|
"sensor", long_entity_id_name, [f"sensor.{long_entity_id_name}"]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exc_info.value.property_name == "generated_entity_id"
|
||||||
|
assert exc_info.value.max_length == 255
|
||||||
|
assert exc_info.value.value == f"sensor.{long_entity_id_name}_2"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user