Ejemplo n.º 1
0
    async def register_webhook(self, event: Event | None = None) -> None:
        """Register a webhook with Toon to get live updates."""
        if CONF_WEBHOOK_ID not in self.entry.data:
            data = {**self.entry.data, CONF_WEBHOOK_ID: secrets.token_hex()}
            self.hass.config_entries.async_update_entry(self.entry, data=data)

        if cloud.async_active_subscription(self.hass):

            if CONF_CLOUDHOOK_URL not in self.entry.data:
                try:
                    webhook_url = await cloud.async_create_cloudhook(
                        self.hass, self.entry.data[CONF_WEBHOOK_ID]
                    )
                except cloud.CloudNotConnected:
                    webhook_url = webhook.async_generate_url(
                        self.hass, self.entry.data[CONF_WEBHOOK_ID]
                    )
                else:
                    data = {**self.entry.data, CONF_CLOUDHOOK_URL: webhook_url}
                    self.hass.config_entries.async_update_entry(self.entry, data=data)
            else:
                webhook_url = self.entry.data[CONF_CLOUDHOOK_URL]
        else:
            webhook_url = webhook.async_generate_url(
                self.hass, self.entry.data[CONF_WEBHOOK_ID]
            )

        # Ensure the webhook is not registered already
        webhook_unregister(self.hass, self.entry.data[CONF_WEBHOOK_ID])

        webhook_register(
            self.hass,
            DOMAIN,
            "Toon",
            self.entry.data[CONF_WEBHOOK_ID],
            self.handle_webhook,
        )

        try:
            await self.toon.subscribe_webhook(
                application_id=self.entry.entry_id, url=webhook_url
            )
            _LOGGER.info("Registered Toon webhook: %s", webhook_url)
        except ToonError as err:
            _LOGGER.error("Error during webhook registration - %s", err)

        self.hass.bus.async_listen_once(
            EVENT_HOMEASSISTANT_STOP, self.unregister_webhook
        )
Ejemplo n.º 2
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
Ejemplo n.º 3
0
def app_fixture(hass, config_file):
    """Fixture for a single app."""
    app = AppEntity(Mock())
    app.apply_data({
        'appName': APP_NAME_PREFIX + str(uuid4()),
        'appId': str(uuid4()),
        'appType': 'WEBHOOK_SMART_APP',
        'classifications': [CLASSIFICATION_AUTOMATION],
        'displayName': 'Home Assistant',
        'description': "Home Assistant at " + hass.config.api.base_url,
        'singleInstance': True,
        'webhookSmartApp': {
            'targetUrl':
            webhook.async_generate_url(hass,
                                       hass.data[DOMAIN][CONF_WEBHOOK_ID]),
            'publicKey':
            ''
        }
    })
    app.refresh = Mock()
    app.refresh.return_value = mock_coro()
    app.save = Mock()
    app.save.return_value = mock_coro()
    settings = AppSettings(app.app_id)
    settings.settings[SETTINGS_INSTANCE_ID] = config_file[CONF_INSTANCE_ID]
    app.settings = Mock()
    app.settings.return_value = mock_coro(return_value=settings)
    return app
Ejemplo n.º 4
0
def app_fixture(hass, config_file):
    """Fixture for a single app."""
    app = AppEntity(Mock())
    app.apply_data({
        'appName': APP_NAME_PREFIX + str(uuid4()),
        'appId': str(uuid4()),
        'appType': 'WEBHOOK_SMART_APP',
        'classifications': [CLASSIFICATION_AUTOMATION],
        'displayName': 'Home Assistant',
        'description': "Home Assistant at " + hass.config.api.base_url,
        'singleInstance': True,
        'webhookSmartApp': {
            'targetUrl': webhook.async_generate_url(
                hass, hass.data[DOMAIN][CONF_WEBHOOK_ID]),
            'publicKey': ''}
    })
    app.refresh = Mock()
    app.refresh.return_value = mock_coro()
    app.save = Mock()
    app.save.return_value = mock_coro()
    settings = AppSettings(app.app_id)
    settings.settings[SETTINGS_INSTANCE_ID] = config_file[CONF_INSTANCE_ID]
    app.settings = Mock()
    app.settings.return_value = mock_coro(return_value=settings)
    return app
Ejemplo n.º 5
0
async def async_get_data_manager(
    hass: HomeAssistant, config_entry: ConfigEntry
) -> DataManager:
    """Get the data manager for a config entry."""
    hass.data.setdefault(const.DOMAIN, {})
    hass.data[const.DOMAIN].setdefault(config_entry.entry_id, {})
    config_entry_data = hass.data[const.DOMAIN][config_entry.entry_id]

    if const.DATA_MANAGER not in config_entry_data:
        profile = config_entry.data.get(const.PROFILE)

        _LOGGER.debug("Creating withings data manager for profile: %s", profile)
        config_entry_data[const.DATA_MANAGER] = DataManager(
            hass,
            profile,
            ConfigEntryWithingsApi(
                hass=hass,
                config_entry=config_entry,
                implementation=await config_entry_oauth2_flow.async_get_config_entry_implementation(
                    hass, config_entry
                ),
            ),
            config_entry.data["token"]["userid"],
            WebhookConfig(
                id=config_entry.data[CONF_WEBHOOK_ID],
                url=webhook.async_generate_url(
                    hass, config_entry.data[CONF_WEBHOOK_ID]
                ),
                enabled=config_entry.data[const.CONF_USE_WEBHOOK],
            ),
        )

    return config_entry_data[const.DATA_MANAGER]
Ejemplo n.º 6
0
async def test_generate_webhook_url(hass):
    """Test we generate a webhook url correctly."""
    await async_process_ha_core_config(
        hass,
        {"external_url": "https://example.com"},
    )
    url = webhook.async_generate_url(hass, "some_id")

    assert url == "https://example.com/api/webhook/some_id"
Ejemplo n.º 7
0
def get_webhook_url(hass: HomeAssistantType) -> str:
    """
    Get the URL of the webhook.

    Return the cloudhook if available, otherwise local webhook.
    """
    cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL]
    if cloud.async_active_subscription(hass) and cloudhook_url is not None:
        return cloudhook_url
    return webhook.async_generate_url(hass, hass.data[DOMAIN][CONF_WEBHOOK_ID])
Ejemplo n.º 8
0
def get_webhook_url(hass: HomeAssistant) -> str:
    """
    Get the URL of the webhook.

    Return the cloudhook if available, otherwise local webhook.
    """
    cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL]
    if cloud.async_active_subscription(hass) and cloudhook_url is not None:
        return cloudhook_url
    return webhook.async_generate_url(hass, hass.data[DOMAIN][CONF_WEBHOOK_ID])
Ejemplo n.º 9
0
    async def _get_webhook_id(self):
        """Generate webhook ID."""
        webhook_id = webhook.async_generate_id()
        if cloud.async_active_subscription(self.hass):
            webhook_url = await cloud.async_create_cloudhook(
                self.hass, webhook_id)
            cloudhook = True
        else:
            webhook_url = webhook.async_generate_url(self.hass, webhook_id)
            cloudhook = False

        return webhook_id, webhook_url, cloudhook
Ejemplo n.º 10
0
    async def async_step_webhook(self, user_input=None):
        """Manage the options for webhook device."""
        if user_input is not None:
            return self.async_create_entry(title="", data=user_input)

        webhook_id = self._config_entry.data.get(CONF_WEBHOOK_ID, None)
        webhook_url = ("" if webhook_id is None else
                       webhook.async_generate_url(self.hass, webhook_id))

        return self.async_show_form(
            step_id="webhook",
            description_placeholders={PLACEHOLDER_WEBHOOK_URL: webhook_url},
        )
Ejemplo n.º 11
0
def _get_app_template(hass: HomeAssistantType):
    from pysmartthings import APP_TYPE_WEBHOOK, CLASSIFICATION_AUTOMATION

    return {
        'app_name': APP_NAME_PREFIX + str(uuid4()),
        'display_name': 'Home Assistant',
        'description': "Home Assistant at " + hass.config.api.base_url,
        'webhook_target_url': webhook.async_generate_url(
            hass, hass.data[DOMAIN][CONF_WEBHOOK_ID]),
        'app_type': APP_TYPE_WEBHOOK,
        'single_instance': True,
        'classifications': [CLASSIFICATION_AUTOMATION]
    }
Ejemplo n.º 12
0
def _get_app_template(hass: HomeAssistantType):
    from pysmartthings import APP_TYPE_WEBHOOK, CLASSIFICATION_AUTOMATION

    return {
        'app_name': APP_NAME_PREFIX + str(uuid4()),
        'display_name': 'Home Assistant',
        'description': "Home Assistant at " + hass.config.api.base_url,
        'webhook_target_url': webhook.async_generate_url(
            hass, hass.data[DOMAIN][CONF_WEBHOOK_ID]),
        'app_type': APP_TYPE_WEBHOOK,
        'single_instance': True,
        'classifications': [CLASSIFICATION_AUTOMATION]
    }
Ejemplo n.º 13
0
async def app_fixture(hass, config_file):
    """Fixture for a single app."""
    app = Mock(AppEntity)
    app.app_name = APP_NAME_PREFIX + str(uuid4())
    app.app_id = str(uuid4())
    app.app_type = "WEBHOOK_SMART_APP"
    app.classifications = [CLASSIFICATION_AUTOMATION]
    app.display_name = "Home Assistant"
    app.description = f"{hass.config.location_name} at https://test.local"
    app.single_instance = True
    app.webhook_target_url = webhook.async_generate_url(
        hass, hass.data[DOMAIN][CONF_WEBHOOK_ID])

    settings = Mock(AppSettings)
    settings.app_id = app.app_id
    settings.settings = {SETTINGS_INSTANCE_ID: config_file[CONF_INSTANCE_ID]}
    app.settings.return_value = settings
    return app
Ejemplo n.º 14
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)
Ejemplo n.º 15
0
    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),
    }
    _LOGGER.debug(
        "Setup endpoint for %s",
        cloudhook_url if cloudhook_url else webhook.async_generate_url(
            hass, config[CONF_WEBHOOK_ID]),
    )


async def unload_smartapp_endpoint(hass: HomeAssistant):
    """Tear down the component configuration."""
    if DOMAIN not in hass.data:
        return
    # Remove the cloudhook if it was created
    cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL]
    if cloudhook_url and hass.components.cloud.async_is_logged_in():
        await hass.components.cloud.async_delete_cloudhook(
            hass.data[DOMAIN][CONF_WEBHOOK_ID])
        # Remove cloudhook from storage
        store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
        await store.async_save({
Ejemplo n.º 16
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]),
    )
Ejemplo n.º 17
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]))
Ejemplo n.º 18
0
                           _async_handle_rachio_webhook)


async def async_get_or_create_registered_webhook_id_and_url(hass, entry):
    """Generate webhook ID."""
    config = entry.data.copy()

    updated_config = False
    webhook_url = None

    if not (webhook_id := config.get(CONF_WEBHOOK_ID)):
        webhook_id = webhook.async_generate_id()
        config[CONF_WEBHOOK_ID] = webhook_id
        updated_config = True

    if cloud.async_active_subscription(hass):
        if not (cloudhook_url := config.get(CONF_CLOUDHOOK_URL)):
            cloudhook_url = await cloud.async_create_cloudhook(
                hass, webhook_id)
            config[CONF_CLOUDHOOK_URL] = cloudhook_url
            updated_config = True
        webhook_url = cloudhook_url

    if not webhook_url:
        webhook_url = webhook.async_generate_url(hass, webhook_id)

    if updated_config:
        hass.config_entries.async_update_entry(entry, data=config)

    return webhook_id, webhook_url