예제 #1
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Withings from a config entry."""
    config_updates = {}

    # Add a unique id if it's an older config entry.
    if entry.unique_id != entry.data["token"]["userid"] or not isinstance(
            entry.unique_id, str):
        config_updates["unique_id"] = str(entry.data["token"]["userid"])

    # Add the webhook configuration.
    if CONF_WEBHOOK_ID not in entry.data:
        webhook_id = webhook.async_generate_id()
        config_updates["data"] = {
            **entry.data,
            **{
                const.CONF_USE_WEBHOOK:
                hass.data[DOMAIN][const.CONFIG][const.CONF_USE_WEBHOOK],
                CONF_WEBHOOK_ID:
                webhook_id,
                const.CONF_WEBHOOK_URL:
                entry.data.get(
                    const.CONF_WEBHOOK_URL,
                    webhook.async_generate_url(hass, webhook_id),
                ),
            },
        }

    if config_updates:
        hass.config_entries.async_update_entry(entry, **config_updates)

    data_manager = await async_get_data_manager(hass, entry)

    _LOGGER.debug("Confirming %s is authenticated to withings",
                  data_manager.profile)
    await data_manager.poll_data_update_coordinator.async_config_entry_first_refresh(
    )

    webhook.async_register(
        hass,
        const.DOMAIN,
        "Withings notify",
        data_manager.webhook_config.id,
        async_webhook_handler,
    )

    # Perform first webhook subscription check.
    if data_manager.webhook_config.enabled:
        data_manager.async_start_polling_webhook_subscriptions()

        @callback
        def async_call_later_callback(now) -> None:
            hass.async_create_task(
                data_manager.subscription_update_coordinator.async_refresh())

        # Start subscription check in the background, outside this component's setup.
        async_call_later(hass, 1, async_call_later_callback)

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    return True
예제 #2
0
async def test_webhook_local_only(hass, mock_client):
    """Test posting a webhook with local only."""
    hooks = []
    webhook_id = webhook.async_generate_id()

    async def handle(*args):
        """Handle webhook."""
        hooks.append((args[0], args[1], await args[2].text()))

    webhook.async_register(hass,
                           "test",
                           "Test hook",
                           webhook_id,
                           handle,
                           local_only=True)

    resp = await mock_client.post(f"/api/webhook/{webhook_id}",
                                  json={"data": True})
    assert resp.status == HTTPStatus.OK
    assert len(hooks) == 1
    assert hooks[0][0] is hass
    assert hooks[0][1] == webhook_id
    assert hooks[0][2] == '{"data": true}'

    # Request from remote IP
    with patch(
            "homeassistant.components.webhook.ip_address",
            return_value=ip_address("123.123.123.123"),
    ):
        resp = await mock_client.post(f"/api/webhook/{webhook_id}",
                                      json={"data": True})
    assert resp.status == HTTPStatus.OK
    # No hook received
    assert len(hooks) == 1
예제 #3
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Configure based on config entry."""
    webhook.async_register(hass, DOMAIN, "Locative",
                           entry.data[CONF_WEBHOOK_ID], handle_webhook)

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)
    return True
예제 #4
0
파일: webhooks.py 프로젝트: jbouwh/core
def async_register_webhook(hass: HomeAssistant, entry: ConfigEntry) -> None:
    """Register a webhook."""
    webhook_id: str = entry.data[CONF_WEBHOOK_ID]

    async def _async_handle_rachio_webhook(
        hass: HomeAssistant, webhook_id: str, request: web.Request
    ) -> web.Response:
        """Handle webhook calls from the server."""
        person: RachioPerson = hass.data[DOMAIN][entry.entry_id]
        data = await request.json()

        try:
            assert (
                data.get(KEY_EXTERNAL_ID, "").split(":")[1]
                == person.rachio.webhook_auth
            )
        except (AssertionError, IndexError):
            return web.Response(status=web.HTTPForbidden.status_code)

        update_type = data[KEY_TYPE]
        if update_type in SIGNAL_MAP:
            async_dispatcher_send(hass, SIGNAL_MAP[update_type], data)

        return web.Response(status=web.HTTPNoContent.status_code)

    webhook.async_register(
        hass, DOMAIN, "Rachio", webhook_id, _async_handle_rachio_webhook
    )
예제 #5
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Configure based on config entry."""
    webhook.async_register(hass, DOMAIN, "Geofency",
                           entry.data[CONF_WEBHOOK_ID], handle_webhook)

    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
    return True
예제 #6
0
    def async_enable_local_sdk(self):
        """Enable the local SDK."""
        setup_successful = True
        setup_webhook_ids = []

        # Don't enable local SDK if ssl is enabled
        if self.hass.config.api and self.hass.config.api.use_ssl:
            self._local_sdk_active = False
            return

        for user_agent_id, _ in self._store.agent_user_ids.items():

            if (webhook_id :=
                    self.get_local_webhook_id(user_agent_id)) is None:
                setup_successful = False
                break

            try:
                webhook.async_register(
                    self.hass,
                    DOMAIN,
                    "Local Support for " + user_agent_id,
                    webhook_id,
                    self._handle_local_webhook,
                    local_only=True,
                )
                setup_webhook_ids.append(webhook_id)
            except ValueError:
                _LOGGER.warning(
                    "Webhook handler %s for agent user id %s is already defined!",
                    webhook_id,
                    user_agent_id,
                )
                setup_successful = False
                break
예제 #7
0
async def test_listing_webhook(hass, hass_ws_client, hass_access_token,
                               enable_custom_integrations):
    """Test unregistering a webhook."""
    assert await async_setup_component(hass, "webhook", {})
    client = await hass_ws_client(hass, hass_access_token)

    webhook.async_register(hass, "test", "Test hook", "my-id", None)
    webhook.async_register(hass,
                           "test",
                           "Test hook",
                           "my-2",
                           None,
                           local_only=True)

    await client.send_json({"id": 5, "type": "webhook/list"})

    msg = await client.receive_json()
    assert msg["id"] == 5
    assert msg["success"]
    assert msg["result"] == [
        {
            "webhook_id": "my-id",
            "domain": "test",
            "name": "Test hook",
            "local_only": False,
        },
        {
            "webhook_id": "my-2",
            "domain": "test",
            "name": "Test hook",
            "local_only": True,
        },
    ]
예제 #8
0
def async_setup_webhook(hass: HomeAssistant, entry: ConfigEntry):
    """Init webhook based on config entry."""
    webhook_id = entry.data[CONF_WEBHOOK_ID]
    device_name = entry.data[CONF_DEVICE_NAME]

    _set_entry_data(entry, hass)

    webhook.async_register(hass, DOMAIN, f"{DOMAIN}.{device_name}", webhook_id,
                           handle_webhook)
예제 #9
0
    def async_enable_local_sdk(self):
        """Enable the local SDK."""
        webhook_id = self.local_sdk_webhook_id

        if webhook_id is None:
            return

        webhook.async_register(self.hass, DOMAIN, "Local Support", webhook_id,
                               self._handle_local_webhook)

        self._local_sdk_active = True
예제 #10
0
async def setup_smartapp_endpoint(hass: HomeAssistantType):
    """
    Configure the SmartApp webhook in hass.

    SmartApps are an extension point within the SmartThings ecosystem and
    is used to receive push updates (i.e. device updates) from the cloud.
    """
    from pysmartapp import Dispatcher, SmartAppManager

    data = hass.data.get(DOMAIN)
    if data:
        # already setup
        return

    # Get/create config to store a unique id for this hass instance.
    store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
    config = await store.async_load()
    if not config:
        # Create config
        config = {
            CONF_INSTANCE_ID: str(uuid4()),
            CONF_WEBHOOK_ID: webhook.generate_secret()
        }
        await store.async_save(config)

    # SmartAppManager uses a dispatcher to invoke callbacks when push events
    # occur. Use hass' implementation instead of the built-in one.
    dispatcher = Dispatcher(signal_prefix=SIGNAL_SMARTAPP_PREFIX,
                            connect=functools.partial(async_dispatcher_connect,
                                                      hass),
                            send=functools.partial(async_dispatcher_send,
                                                   hass))
    manager = SmartAppManager(webhook.async_generate_path(
        config[CONF_WEBHOOK_ID]),
                              dispatcher=dispatcher)
    manager.connect_install(functools.partial(smartapp_install, hass))
    manager.connect_update(functools.partial(smartapp_update, hass))
    manager.connect_uninstall(functools.partial(smartapp_uninstall, hass))

    webhook.async_register(hass, DOMAIN, 'SmartApp', config[CONF_WEBHOOK_ID],
                           smartapp_webhook)

    hass.data[DOMAIN] = {
        DATA_MANAGER: manager,
        CONF_INSTANCE_ID: config[CONF_INSTANCE_ID],
        DATA_BROKERS: {},
        CONF_WEBHOOK_ID: config[CONF_WEBHOOK_ID],
        CONF_INSTALLED_APPS: []
    }
예제 #11
0
async def test_webhook_head(hass, mock_client):
    """Test sending a head request to a webhook."""
    hooks = []
    webhook_id = webhook.async_generate_id()

    async def handle(*args):
        """Handle webhook."""
        hooks.append(args)

    webhook.async_register(hass, "test", "Test hook", webhook_id, handle)

    resp = await mock_client.head(f"/api/webhook/{webhook_id}")
    assert resp.status == HTTPStatus.OK
    assert len(hooks) == 1
    assert hooks[0][0] is hass
    assert hooks[0][1] == webhook_id
    assert hooks[0][2].method == "HEAD"
예제 #12
0
async def setup_smartapp_endpoint(hass: HomeAssistantType):
    """
    Configure the SmartApp webhook in hass.

    SmartApps are an extension point within the SmartThings ecosystem and
    is used to receive push updates (i.e. device updates) from the cloud.
    """
    from pysmartapp import Dispatcher, SmartAppManager

    data = hass.data.get(DOMAIN)
    if data:
        # already setup
        return

    # Get/create config to store a unique id for this hass instance.
    store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
    config = await store.async_load()
    if not config:
        # Create config
        config = {
            CONF_INSTANCE_ID: str(uuid4()),
            CONF_WEBHOOK_ID: webhook.generate_secret()
        }
        await store.async_save(config)

    # SmartAppManager uses a dispatcher to invoke callbacks when push events
    # occur. Use hass' implementation instead of the built-in one.
    dispatcher = Dispatcher(
        signal_prefix=SIGNAL_SMARTAPP_PREFIX,
        connect=functools.partial(async_dispatcher_connect, hass),
        send=functools.partial(async_dispatcher_send, hass))
    manager = SmartAppManager(
        webhook.async_generate_path(config[CONF_WEBHOOK_ID]),
        dispatcher=dispatcher)
    manager.connect_install(functools.partial(smartapp_install, hass))
    manager.connect_uninstall(functools.partial(smartapp_uninstall, hass))

    webhook.async_register(hass, DOMAIN, 'SmartApp',
                           config[CONF_WEBHOOK_ID], smartapp_webhook)

    hass.data[DOMAIN] = {
        DATA_MANAGER: manager,
        CONF_INSTANCE_ID: config[CONF_INSTANCE_ID],
        DATA_BROKERS: {},
        CONF_WEBHOOK_ID: config[CONF_WEBHOOK_ID]
    }
예제 #13
0
async def test_posting_webhook_json(hass, mock_client):
    """Test posting a webhook with JSON data."""
    hooks = []
    webhook_id = webhook.async_generate_id()

    async def handle(*args):
        """Handle webhook."""
        hooks.append((args[0], args[1], await args[2].text()))

    webhook.async_register(hass, "test", "Test hook", webhook_id, handle)

    resp = await mock_client.post(f"/api/webhook/{webhook_id}",
                                  json={"data": True})
    assert resp.status == HTTPStatus.OK
    assert len(hooks) == 1
    assert hooks[0][0] is hass
    assert hooks[0][1] == webhook_id
    assert hooks[0][2] == '{"data": true}'
예제 #14
0
async def test_posting_webhook_no_data(hass, mock_client):
    """Test posting a webhook with no data."""
    hooks = []
    webhook_id = webhook.async_generate_id()

    async def handle(*args):
        """Handle webhook."""
        hooks.append(args)

    webhook.async_register(hass, "test", "Test hook", webhook_id, handle)

    resp = await mock_client.post(f"/api/webhook/{webhook_id}")
    assert resp.status == HTTPStatus.OK
    assert len(hooks) == 1
    assert hooks[0][0] is hass
    assert hooks[0][1] == webhook_id
    assert hooks[0][2].method == "POST"
    assert await hooks[0][2].text() == ""
예제 #15
0
async def test_unregistering_webhook(hass, mock_client):
    """Test unregistering a webhook."""
    hooks = []
    webhook_id = webhook.async_generate_id()

    async def handle(*args):
        """Handle webhook."""
        hooks.append(args)

    webhook.async_register(hass, "test", "Test hook", webhook_id, handle)

    resp = await mock_client.post(f"/api/webhook/{webhook_id}")
    assert resp.status == HTTPStatus.OK
    assert len(hooks) == 1

    webhook.async_unregister(hass, webhook_id)

    resp = await mock_client.post(f"/api/webhook/{webhook_id}")
    assert resp.status == HTTPStatus.OK
    assert len(hooks) == 1
예제 #16
0
파일: webhooks.py 프로젝트: rikroe/core
def async_register_webhook(hass, webhook_id, entry_id):
    """Register a webhook."""
    async def _async_handle_rachio_webhook(hass, webhook_id, request):
        """Handle webhook calls from the server."""
        data = await request.json()

        try:
            auth = data.get(KEY_EXTERNAL_ID, "").split(":")[1]
            assert auth == hass.data[DOMAIN][entry_id].rachio.webhook_auth
        except (AssertionError, IndexError):
            return web.Response(status=web.HTTPForbidden.status_code)

        update_type = data[KEY_TYPE]
        if update_type in SIGNAL_MAP:
            async_dispatcher_send(hass, SIGNAL_MAP[update_type], data)

        return web.Response(status=web.HTTPNoContent.status_code)

    webhook.async_register(hass, DOMAIN, "Rachio", webhook_id,
                           _async_handle_rachio_webhook)
예제 #17
0
    def async_enable_local_sdk(self):
        """Enable the local SDK."""
        webhook_id = self.local_sdk_webhook_id

        if webhook_id is None:
            return

        try:
            webhook.async_register(
                self.hass,
                DOMAIN,
                "Local Support",
                webhook_id,
                self._handle_local_webhook,
            )
        except ValueError:
            _LOGGER.info("Webhook handler is already defined!")
            return

        self._local_sdk_active = True
예제 #18
0
파일: __init__.py 프로젝트: 2Fake/core
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up OwnTracks entry."""
    config = hass.data[DOMAIN]["config"]
    max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
    waypoint_import = config.get(CONF_WAYPOINT_IMPORT)
    waypoint_whitelist = config.get(CONF_WAYPOINT_WHITELIST)
    secret = config.get(CONF_SECRET) or entry.data[CONF_SECRET]
    region_mapping = config.get(CONF_REGION_MAPPING)
    events_only = config.get(CONF_EVENTS_ONLY)
    mqtt_topic = config.get(CONF_MQTT_TOPIC)

    context = OwnTracksContext(
        hass,
        secret,
        max_gps_accuracy,
        waypoint_import,
        waypoint_whitelist,
        region_mapping,
        events_only,
        mqtt_topic,
    )

    webhook_id = config.get(CONF_WEBHOOK_ID) or entry.data[CONF_WEBHOOK_ID]

    hass.data[DOMAIN]["context"] = context

    async_when_setup(hass, "mqtt", async_connect_mqtt)

    webhook.async_register(hass, DOMAIN, "OwnTracks", webhook_id,
                           handle_webhook)

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    hass.data[DOMAIN][
        "unsub"] = hass.helpers.dispatcher.async_dispatcher_connect(
            DOMAIN, async_handle_message)

    return True
예제 #19
0
async def async_setup_webhook(hass: HomeAssistant, entry: ConfigEntry,
                              session):
    """Set up a webhook to handle binary sensor events."""
    if CONF_WEBHOOK_ID not in entry.data:
        webhook_id = webhook.async_generate_id()
        webhook_url = webhook.async_generate_url(hass, webhook_id)
        _LOGGER.info("Registering new webhook at: %s", webhook_url)

        hass.config_entries.async_update_entry(
            entry,
            data={
                **entry.data,
                CONF_WEBHOOK_ID: webhook_id,
                CONF_WEBHOOK_URL: webhook_url,
            },
        )
    await session.update_webhook(
        entry.data[CONF_WEBHOOK_URL],
        entry.data[CONF_WEBHOOK_ID],
        ["*"],
    )

    webhook.async_register(hass, DOMAIN, "Point", entry.data[CONF_WEBHOOK_ID],
                           handle_webhook)
예제 #20
0
class AbstractConfig(ABC):
    """Hold the configuration for Google Assistant."""

    _unsub_report_state = None

    def __init__(self, hass):
        """Initialize abstract config."""
        self.hass = hass
        self._store = None
        self._google_sync_unsub = {}
        self._local_sdk_active = False

    async def async_initialize(self):
        """Perform async initialization of config."""
        self._store = GoogleConfigStore(self.hass)
        await self._store.async_load()

        if not self.enabled:
            return

        async def sync_google(_):
            """Sync entities to Google."""
            await self.async_sync_entities_all()

        start.async_at_start(self.hass, sync_google)

    @property
    def enabled(self):
        """Return if Google is enabled."""
        return False

    @property
    def entity_config(self):
        """Return entity config."""
        return {}

    @property
    def secure_devices_pin(self):
        """Return entity config."""
        return None

    @property
    def is_reporting_state(self):
        """Return if we're actively reporting states."""
        return self._unsub_report_state is not None

    @property
    def is_local_sdk_active(self):
        """Return if we're actively accepting local messages."""
        return self._local_sdk_active

    @property
    def should_report_state(self):
        """Return if states should be proactively reported."""
        return False

    @property
    def local_sdk_webhook_id(self):
        """Return the local SDK webhook ID.

        Return None to disable the local SDK.
        """
        return None

    @property
    def local_sdk_user_id(self):
        """Return the user ID to be used for actions received via the local SDK."""
        raise NotImplementedError

    @abstractmethod
    def get_agent_user_id(self, context):
        """Get agent user ID from context."""

    @abstractmethod
    def should_expose(self, state) -> bool:
        """Return if entity should be exposed."""

    def should_2fa(self, state):
        """If an entity should have 2FA checked."""
        # pylint: disable=no-self-use
        return True

    async def async_report_state(self, message, agent_user_id: str):
        """Send a state report to Google."""
        raise NotImplementedError

    async def async_report_state_all(self, message):
        """Send a state report to Google for all previously synced users."""
        jobs = [
            self.async_report_state(message, agent_user_id)
            for agent_user_id in self._store.agent_user_ids
        ]
        await gather(*jobs)

    @callback
    def async_enable_report_state(self):
        """Enable proactive mode."""
        # Circular dep
        # pylint: disable=import-outside-toplevel
        from .report_state import async_enable_report_state

        if self._unsub_report_state is None:
            self._unsub_report_state = async_enable_report_state(
                self.hass, self)

    @callback
    def async_disable_report_state(self):
        """Disable report state."""
        if self._unsub_report_state is not None:
            self._unsub_report_state()
            self._unsub_report_state = None

    async def async_sync_entities(self, agent_user_id: str):
        """Sync all entities to Google."""
        # Remove any pending sync
        self._google_sync_unsub.pop(agent_user_id, lambda: None)()
        status = await self._async_request_sync_devices(agent_user_id)
        if status == HTTPStatus.NOT_FOUND:
            await self.async_disconnect_agent_user(agent_user_id)
        return status

    async def async_sync_entities_all(self):
        """Sync all entities to Google for all registered agents."""
        res = await gather(*(self.async_sync_entities(agent_user_id)
                             for agent_user_id in self._store.agent_user_ids))
        return max(res, default=204)

    @callback
    def async_schedule_google_sync(self, agent_user_id: str):
        """Schedule a sync."""
        async def _schedule_callback(_now):
            """Handle a scheduled sync callback."""
            self._google_sync_unsub.pop(agent_user_id, None)
            await self.async_sync_entities(agent_user_id)

        self._google_sync_unsub.pop(agent_user_id, lambda: None)()

        self._google_sync_unsub[agent_user_id] = async_call_later(
            self.hass, SYNC_DELAY, _schedule_callback)

    @callback
    def async_schedule_google_sync_all(self):
        """Schedule a sync for all registered agents."""
        for agent_user_id in self._store.agent_user_ids:
            self.async_schedule_google_sync(agent_user_id)

    async def _async_request_sync_devices(self, agent_user_id: str) -> int:
        """Trigger a sync with Google.

        Return value is the HTTP status code of the sync request.
        """
        raise NotImplementedError

    async def async_connect_agent_user(self, agent_user_id: str):
        """Add an synced and known agent_user_id.

        Called when a completed sync response have been sent to Google.
        """
        self._store.add_agent_user_id(agent_user_id)

    async def async_disconnect_agent_user(self, agent_user_id: str):
        """Turn off report state and disable further state reporting.

        Called when the user disconnects their account from Google.
        """
        self._store.pop_agent_user_id(agent_user_id)

    @callback
    def async_enable_local_sdk(self):
        """Enable the local SDK."""
        if (webhook_id := self.local_sdk_webhook_id) is None:
            return

        try:
            webhook.async_register(
                self.hass,
                DOMAIN,
                "Local Support",
                webhook_id,
                self._handle_local_webhook,
            )
        except ValueError:
            _LOGGER.info("Webhook handler is already defined!")
            return

        self._local_sdk_active = True
예제 #21
0
async def setup_smartapp_endpoint(hass: HomeAssistantType):
    """
    Configure the SmartApp webhook in hass.

    SmartApps are an extension point within the SmartThings ecosystem and
    is used to receive push updates (i.e. device updates) from the cloud.
    """
    data = hass.data.get(DOMAIN)
    if data:
        # already setup
        return

    # Get/create config to store a unique id for this hass instance.
    store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
    config = await store.async_load()
    if not config:
        # Create config
        config = {
            CONF_INSTANCE_ID: str(uuid4()),
            CONF_WEBHOOK_ID: webhook.generate_secret(),
            CONF_CLOUDHOOK_URL: None,
        }
        await store.async_save(config)

    # Register webhook
    webhook.async_register(hass, DOMAIN, "SmartApp", config[CONF_WEBHOOK_ID],
                           smartapp_webhook)

    # Create webhook if eligible
    cloudhook_url = config.get(CONF_CLOUDHOOK_URL)
    if (cloudhook_url is None and cloud.async_active_subscription(hass)
            and not hass.config_entries.async_entries(DOMAIN)):
        cloudhook_url = await cloud.async_create_cloudhook(
            hass, config[CONF_WEBHOOK_ID])
        config[CONF_CLOUDHOOK_URL] = cloudhook_url
        await store.async_save(config)
        _LOGGER.debug("Created cloudhook '%s'", cloudhook_url)

    # SmartAppManager uses a dispatcher to invoke callbacks when push events
    # occur. Use hass' implementation instead of the built-in one.
    dispatcher = Dispatcher(
        signal_prefix=SIGNAL_SMARTAPP_PREFIX,
        connect=functools.partial(async_dispatcher_connect, hass),
        send=functools.partial(async_dispatcher_send, hass),
    )
    # Path is used in digital signature validation
    path = (urlparse(cloudhook_url).path if cloudhook_url else
            webhook.async_generate_path(config[CONF_WEBHOOK_ID]))
    manager = SmartAppManager(path, dispatcher=dispatcher)
    manager.connect_install(functools.partial(smartapp_install, hass))
    manager.connect_update(functools.partial(smartapp_update, hass))
    manager.connect_uninstall(functools.partial(smartapp_uninstall, hass))

    hass.data[DOMAIN] = {
        DATA_MANAGER: manager,
        CONF_INSTANCE_ID: config[CONF_INSTANCE_ID],
        DATA_BROKERS: {},
        CONF_WEBHOOK_ID: config[CONF_WEBHOOK_ID],
        # Will not be present if not enabled
        CONF_CLOUDHOOK_URL: config.get(CONF_CLOUDHOOK_URL),
        CONF_INSTALLED_APPS: [],
    }
    _LOGGER.debug(
        "Setup endpoint for %s",
        cloudhook_url if cloudhook_url else webhook.async_generate_url(
            hass, config[CONF_WEBHOOK_ID]),
    )
예제 #22
0
async def test_ws_webhook(hass, caplog, hass_ws_client):
    """Test sending webhook msg via WS API."""
    assert await async_setup_component(hass, "webhook", {})

    received = []

    async def handler(hass, webhook_id, request):
        """Handle a webhook."""
        received.append(request)
        return web.json_response({"from": "handler"})

    webhook.async_register(hass, "test", "Test", "mock-webhook-id", handler)

    client = await hass_ws_client(hass)

    await client.send_json({
        "id": 5,
        "type": "webhook/handle",
        "webhook_id": "mock-webhook-id",
        "method": "POST",
        "headers": {
            "Content-Type": "application/json"
        },
        "body": '{"hello": "world"}',
        "query": "a=2",
    })

    result = await client.receive_json()
    assert result["success"], result
    assert result["result"] == {
        "status": 200,
        "body": '{"from": "handler"}',
        "headers": {
            "Content-Type": "application/json"
        },
    }

    assert len(received) == 1
    assert received[0].headers["content-type"] == "application/json"
    assert received[0].query == {"a": "2"}
    assert await received[0].json() == {"hello": "world"}

    # Non existing webhook
    caplog.clear()

    await client.send_json({
        "id": 6,
        "type": "webhook/handle",
        "webhook_id": "mock-nonexisting-id",
        "method": "POST",
        "body": '{"nonexisting": "payload"}',
    })

    result = await client.receive_json()
    assert result["success"], result
    assert result["result"] == {
        "status": 200,
        "body": None,
        "headers": {
            "Content-Type": "application/octet-stream"
        },
    }

    assert (
        "Received message for unregistered webhook mock-nonexisting-id from webhook/ws"
        in caplog.text)
    assert '{"nonexisting": "payload"}' in caplog.text
예제 #23
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Configure based on config entry."""
    webhook.async_register(
        hass, DOMAIN, "IFTTT", entry.data[CONF_WEBHOOK_ID], handle_webhook
    )
    return True
예제 #24
0
def register_device_webhook(hass: HomeAssistantType, store, device):
    """Register the webhook for a device."""
    device_name = 'Mobile App: {}'.format(device[ATTR_DEVICE_NAME])
    webhook_id = device[CONF_WEBHOOK_ID]
    webhook.async_register(hass, DOMAIN, device_name, webhook_id,
                           partial(handle_webhook, store))
예제 #25
0
async def test_posting_webhook_invalid_json(hass, mock_client):
    """Test posting to a nonexisting webhook."""
    webhook.async_register(hass, "test", "Test hook", "hello", None)
    resp = await mock_client.post("/api/webhook/hello", data="not-json")
    assert resp.status == HTTPStatus.OK
예제 #26
0
async def setup_smartapp_endpoint(hass: HomeAssistantType):
    """
    Configure the SmartApp webhook in hass.

    SmartApps are an extension point within the SmartThings ecosystem and
    is used to receive push updates (i.e. device updates) from the cloud.
    """
    from pysmartapp import Dispatcher, SmartAppManager

    data = hass.data.get(DOMAIN)
    if data:
        # already setup
        return

    # Get/create config to store a unique id for this hass instance.
    store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
    config = await store.async_load()
    if not config:
        # Create config
        config = {
            CONF_INSTANCE_ID: str(uuid4()),
            CONF_WEBHOOK_ID: webhook.generate_secret(),
            CONF_CLOUDHOOK_URL: None
        }
        await store.async_save(config)

    # Register webhook
    webhook.async_register(hass, DOMAIN, 'SmartApp',
                           config[CONF_WEBHOOK_ID], smartapp_webhook)

    # Create webhook if eligible
    cloudhook_url = config.get(CONF_CLOUDHOOK_URL)
    if cloudhook_url is None \
            and cloud.async_active_subscription(hass) \
            and not hass.config_entries.async_entries(DOMAIN):
        cloudhook_url = await cloud.async_create_cloudhook(
            hass, config[CONF_WEBHOOK_ID])
        config[CONF_CLOUDHOOK_URL] = cloudhook_url
        await store.async_save(config)
        _LOGGER.debug("Created cloudhook '%s'", cloudhook_url)

    # SmartAppManager uses a dispatcher to invoke callbacks when push events
    # occur. Use hass' implementation instead of the built-in one.
    dispatcher = Dispatcher(
        signal_prefix=SIGNAL_SMARTAPP_PREFIX,
        connect=functools.partial(async_dispatcher_connect, hass),
        send=functools.partial(async_dispatcher_send, hass))
    # Path is used in digital signature validation
    path = urlparse(cloudhook_url).path if cloudhook_url else \
        webhook.async_generate_path(config[CONF_WEBHOOK_ID])
    manager = SmartAppManager(path, dispatcher=dispatcher)
    manager.connect_install(functools.partial(smartapp_install, hass))
    manager.connect_update(functools.partial(smartapp_update, hass))
    manager.connect_uninstall(functools.partial(smartapp_uninstall, hass))

    hass.data[DOMAIN] = {
        DATA_MANAGER: manager,
        CONF_INSTANCE_ID: config[CONF_INSTANCE_ID],
        DATA_BROKERS: {},
        CONF_WEBHOOK_ID: config[CONF_WEBHOOK_ID],
        # Will not be present if not enabled
        CONF_CLOUDHOOK_URL: config.get(CONF_CLOUDHOOK_URL),
        CONF_INSTALLED_APPS: []
    }
    _LOGGER.debug("Setup endpoint for %s",
                  cloudhook_url if cloudhook_url else
                  webhook.async_generate_url(hass, config[CONF_WEBHOOK_ID]))
예제 #27
0
        # already setup
        return

    # Get/create config to store a unique id for this hass instance.
    store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
    if not (config := await store.async_load()):
        # Create config
        config = {
            CONF_INSTANCE_ID: str(uuid4()),
            CONF_WEBHOOK_ID: secrets.token_hex(),
            CONF_CLOUDHOOK_URL: None,
        }
        await store.async_save(config)

    # Register webhook
    webhook.async_register(hass, DOMAIN, "SmartApp", config[CONF_WEBHOOK_ID],
                           smartapp_webhook)

    # Create webhook if eligible
    cloudhook_url = config.get(CONF_CLOUDHOOK_URL)
    if (cloudhook_url is None
            and hass.components.cloud.async_active_subscription()
            and not hass.config_entries.async_entries(DOMAIN)):
        cloudhook_url = await hass.components.cloud.async_create_cloudhook(
            config[CONF_WEBHOOK_ID])
        config[CONF_CLOUDHOOK_URL] = cloudhook_url
        await store.async_save(config)
        _LOGGER.debug("Created cloudhook '%s'", cloudhook_url)

    # SmartAppManager uses a dispatcher to invoke callbacks when push events
    # occur. Use hass' implementation instead of the built-in one.
    dispatcher = Dispatcher(
예제 #28
0
def register_device_webhook(hass: HomeAssistantType, store, device):
    """Register the webhook for a device."""
    device_name = 'Mobile App: {}'.format(device[ATTR_DEVICE_NAME])
    webhook_id = device[CONF_WEBHOOK_ID]
    webhook.async_register(hass, DOMAIN, device_name, webhook_id,
                           partial(handle_webhook, store))