Пример #1
0
    async def get(self, request):
        """Get discovery information."""
        opp = request.app["opp"]
        uuid = await opp.helpers.instance_id.async_get()
        system_info = await async_get_system_info(opp)

        data = {
            ATTR_UUID: uuid,
            ATTR_BASE_URL: None,
            ATTR_EXTERNAL_URL: None,
            ATTR_INTERNAL_URL: None,
            ATTR_LOCATION_NAME: opp.config.location_name,
            ATTR_INSTALLATION_TYPE: system_info[ATTR_INSTALLATION_TYPE],
            # always needs authentication
            ATTR_REQUIRES_API_PASSWORD: True,
            ATTR_VERSION: __version__,
        }

        with suppress(NoURLAvailableError):
            data["external_url"] = get_url(opp, allow_internal=False)

        with suppress(NoURLAvailableError):
            data["internal_url"] = get_url(opp, allow_external=False)

        # Set old base URL based on external or internal
        data["base_url"] = data["external_url"] or data["internal_url"]

        return self.json(data)
Пример #2
0
async def _async_register_opp_zc_service(opp: OpenPeerPower,
                                         aio_zc: HaAsyncZeroconf,
                                         uuid: str) -> None:
    # Get instance UUID
    valid_location_name = _truncate_location_name_to_valid(
        opp.config.location_name)

    params = {
        "location_name": valid_location_name,
        "uuid": uuid,
        "version": __version__,
        "external_url": "",
        "internal_url": "",
        # Old base URL, for backward compatibility
        "base_url": "",
        # Always needs authentication
        "requires_api_password": True,
    }

    # Get instance URL's
    with suppress(NoURLAvailableError):
        params["external_url"] = get_url(opp, allow_internal=False)

    with suppress(NoURLAvailableError):
        params["internal_url"] = get_url(opp, allow_external=False)

    # Set old base URL based on external or internal
    params["base_url"] = params["external_url"] or params["internal_url"]

    host_ip = util.get_local_ip()

    try:
        host_ip_pton = socket.inet_pton(socket.AF_INET, host_ip)
    except OSError:
        host_ip_pton = socket.inet_pton(socket.AF_INET6, host_ip)

    _suppress_invalid_properties(params)

    info = ServiceInfo(
        ZEROCONF_TYPE,
        name=f"{valid_location_name}.{ZEROCONF_TYPE}",
        server=f"{uuid}.local.",
        addresses=[host_ip_pton],
        port=opp.http.server_port,
        properties=params,
    )

    _LOGGER.info("Starting Zeroconf broadcast")
    try:
        await aio_zc.async_register_service(info)
    except NonUniqueNameException:
        _LOGGER.error(
            "Open Peer Power instance with identical name present in the local network"
        )
Пример #3
0
    async def async_play_media(self, media_type, media_id, **kwargs):
        """Play a piece of media."""
        # Handle media_source
        if media_source.is_media_source_id(media_id):
            sourced_media = await media_source.async_resolve_media(
                self.opp, media_id)
            media_type = sourced_media.mime_type
            media_id = sourced_media.url

        # If media ID is a relative URL, we serve it from HA.
        # Create a signed path.
        if media_id[0] == "/":
            # Sign URL with Open Peer Power Cast User
            config_entry_id = self.registry_entry.config_entry_id
            config_entry = self.opp.config_entries.async_get_entry(
                config_entry_id)
            user_id = config_entry.data["user_id"]
            user = await self.opp.auth.async_get_user(user_id)
            if user.refresh_tokens:
                refresh_token: RefreshToken = list(
                    user.refresh_tokens.values())[0]

                media_id = async_sign_path(
                    self.opp,
                    refresh_token.id,
                    quote(media_id),
                    timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME),
                )

            # prepend external URL
            hass_url = get_url(self.opp, prefer_external=True)
            media_id = f"{hass_url}{media_id}"

        await self.opp.async_add_executor_job(
            ft.partial(self.play_media, media_type, media_id, **kwargs))
Пример #4
0
    async def post(self, request: web.Request) -> web.Response:
        """Generate speech and provide url."""
        try:
            data = await request.json()
        except ValueError:
            return self.json_message("Invalid JSON specified",
                                     HTTP_BAD_REQUEST)
        if not data.get(ATTR_PLATFORM) and data.get(ATTR_MESSAGE):
            return self.json_message("Must specify platform and message",
                                     HTTP_BAD_REQUEST)

        p_type = data[ATTR_PLATFORM]
        message = data[ATTR_MESSAGE]
        cache = data.get(ATTR_CACHE)
        language = data.get(ATTR_LANGUAGE)
        options = data.get(ATTR_OPTIONS)

        try:
            path = await self.tts.async_get_url_path(p_type,
                                                     message,
                                                     cache=cache,
                                                     language=language,
                                                     options=options)
        except OpenPeerPowerError as err:
            _LOGGER.error("Error on init tts: %s", err)
            return self.json({"error": err}, HTTP_BAD_REQUEST)

        base = self.tts.base_url or get_url(self.tts.opp)
        url = base + path

        return self.json({"url": url, "path": path})
Пример #5
0
        async def async_say_handle(service):
            """Service handle for say."""
            entity_ids = service.data[ATTR_ENTITY_ID]
            message = service.data.get(ATTR_MESSAGE)
            cache = service.data.get(ATTR_CACHE)
            language = service.data.get(ATTR_LANGUAGE)
            options = service.data.get(ATTR_OPTIONS)

            try:
                url = await tts.async_get_url_path(p_type,
                                                   message,
                                                   cache=cache,
                                                   language=language,
                                                   options=options)
            except OpenPeerPowerError as err:
                _LOGGER.error("Error on init TTS: %s", err)
                return

            base = tts.base_url or get_url(opp)
            url = base + url

            data = {
                ATTR_MEDIA_CONTENT_ID: url,
                ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC,
                ATTR_ENTITY_ID: entity_ids,
            }

            await opp.services.async_call(
                DOMAIN_MP,
                SERVICE_PLAY_MEDIA,
                data,
                blocking=True,
                context=service.context,
            )
Пример #6
0
    def new_media_status(self, media_status):
        """Handle updates of the media status."""
        if (media_status and media_status.player_is_idle
                and media_status.idle_reason == "ERROR"):
            external_url = None
            internal_url = None
            tts_base_url = None
            url_description = ""
            if "tts" in self.opp.config.components:
                with suppress(KeyError):  # base_url not configured
                    tts_base_url = self.opp.components.tts.get_base_url(
                        self.opp)

            with suppress(NoURLAvailableError):  # external_url not configured
                external_url = get_url(self.opp, allow_internal=False)

            with suppress(NoURLAvailableError):  # internal_url not configured
                internal_url = get_url(self.opp, allow_external=False)

            if media_status.content_id:
                if tts_base_url and media_status.content_id.startswith(
                        tts_base_url):
                    url_description = f" from tts.base_url ({tts_base_url})"
                if external_url and media_status.content_id.startswith(
                        external_url):
                    url_description = f" from external_url ({external_url})"
                if internal_url and media_status.content_id.startswith(
                        internal_url):
                    url_description = f" from internal_url ({internal_url})"

            _LOGGER.error(
                "Failed to cast media %s%s. Please make sure the URL is: "
                "Reachable from the cast device and either a publicly resolvable "
                "hostname or an IP address",
                media_status.content_id,
                url_description,
            )

        self.media_status = media_status
        self.media_status_received = dt_util.utcnow()
        self.schedule_update_op_state()
Пример #7
0
    def register_events(self, opp):
        """Register events on device."""
        # Get the URL of this server
        opp_url = get_url(opp)

        # Override url if another is specified in the configuration
        if self.custom_url is not None:
            opp_url = self.custom_url

        for event in self.doorstation_events:
            self._register_event(opp_url, event)

            _LOGGER.info("Successfully registered URL for %s on %s", event,
                         self.name)
Пример #8
0
async def async_setup_platform(opp, config):
    """Set up the Telegram webhooks platform."""

    bot = initialize_bot(config)

    current_status = await opp.async_add_executor_job(bot.getWebhookInfo)
    base_url = config.get(CONF_URL,
                          get_url(opp, require_ssl=True, allow_internal=False))

    # Some logging of Bot current status:
    last_error_date = getattr(current_status, "last_error_date", None)
    if (last_error_date is not None) and (isinstance(last_error_date, int)):
        last_error_date = dt.datetime.fromtimestamp(last_error_date)
        _LOGGER.info(
            "Telegram webhook last_error_date: %s. Status: %s",
            last_error_date,
            current_status,
        )
    else:
        _LOGGER.debug("telegram webhook Status: %s", current_status)

    handler_url = f"{base_url}{TELEGRAM_HANDLER_URL}"
    if not handler_url.startswith("https"):
        _LOGGER.error("Invalid telegram webhook %s must be https", handler_url)
        return False

    def _try_to_set_webhook():
        retry_num = 0
        while retry_num < 3:
            try:
                return bot.setWebhook(handler_url, timeout=5)
            except TimedOut:
                retry_num += 1
                _LOGGER.warning("Timeout trying to set webhook (retry #%d)",
                                retry_num)

    if current_status and current_status["url"] != handler_url:
        result = await opp.async_add_executor_job(_try_to_set_webhook)
        if result:
            _LOGGER.info("Set new telegram webhook %s", handler_url)
        else:
            _LOGGER.error("Set telegram webhook failed %s", handler_url)
            return False

    opp.bus.async_listen_once(EVENT_OPENPEERPOWER_STOP,
                              lambda event: bot.setWebhook(REMOVE_HANDLER_URL))
    opp.http.register_view(
        BotPushReceiver(opp, config[CONF_ALLOWED_CHAT_IDS],
                        config[CONF_TRUSTED_NETWORKS]))
    return True
Пример #9
0
    def async_desired_settings_payload(self):
        """Return a dict representing the desired device configuration."""
        # keeping self.opp.data check for backwards compatibility
        # newly configured integrations store this in the config entry
        desired_api_host = self.options.get(CONF_API_HOST) or (
            self.opp.data[DOMAIN].get(CONF_API_HOST) or get_url(self.opp)
        )
        desired_api_endpoint = desired_api_host + ENDPOINT_ROOT

        return {
            "sensors": self.async_binary_sensor_configuration(),
            "actuators": self.async_actuator_configuration(),
            "dht_sensors": self.async_dht_sensor_configuration(),
            "ds18b20_sensors": self.async_ds18b20_sensor_configuration(),
            "auth_token": self.config.get(CONF_ACCESS_TOKEN),
            "endpoint": desired_api_endpoint,
            "blink": self.options.get(CONF_BLINK, True),
            "discovery": self.options.get(CONF_DISCOVERY, True),
        }
Пример #10
0
    async def handle_show_view(call: core.ServiceCall):
        """Handle a Show View service call."""
        hass_url = get_url(opp, require_ssl=True, prefer_external=True)

        controller = HomeAssistantController(
            # If you are developing Open Peer Power Cast, uncomment and set to your dev app id.
            # app_id="5FE44367",
            hass_url=hass_url,
            client_id=None,
            refresh_token=refresh_token.token,
        )

        dispatcher.async_dispatcher_send(
            opp,
            SIGNAL_OPP_CAST_SHOW_VIEW,
            controller,
            call.data[ATTR_ENTITY_ID],
            call.data[ATTR_VIEW_PATH],
            call.data.get(ATTR_URL_PATH),
        )
Пример #11
0
async def _configure_almond_for_ha(opp: OpenPeerPower,
                                   entry: config_entries.ConfigEntry,
                                   api: WebAlmondAPI):
    """Configure Almond to connect to HA."""
    try:
        if entry.data["type"] == TYPE_OAUTH2:
            # If we're connecting over OAuth2, we will only set up connection
            # with Open Peer Power if we're remotely accessible.
            opp_url = network.get_url(opp,
                                      allow_internal=False,
                                      prefer_cloud=True)
        else:
            opp_url = network.get_url(opp)
    except network.NoURLAvailableError:
        # If no URL is available, we're not going to configure Almond to connect to HA.
        return

    _LOGGER.debug("Configuring Almond to connect to Open Peer Power at %s",
                  opp_url)
    store = storage.Store(opp, STORAGE_VERSION, STORAGE_KEY)
    data = await store.async_load()

    if data is None:
        data = {}

    user = None
    if "almond_user" in data:
        user = await opp.auth.async_get_user(data["almond_user"])

    if user is None:
        user = await opp.auth.async_create_system_user("Almond",
                                                       [GROUP_ID_ADMIN])
        data["almond_user"] = user.id
        await store.async_save(data)

    refresh_token = await opp.auth.async_create_refresh_token(
        user,
        # Almond will be fine as long as we restart once every 5 years
        access_token_expiration=timedelta(days=365 * 5),
    )

    # Create long lived access token
    access_token = opp.auth.async_create_access_token(refresh_token)

    # Store token in Almond
    try:
        with async_timeout.timeout(30):
            await api.async_create_device({
                "kind":
                "io.openpeerpower",
                "oppUrl":
                opp_url,
                "accessToken":
                access_token,
                "refreshToken":
                "",
                # 5 years from now in ms.
                "accessTokenExpires":
                (time.time() + 60 * 60 * 24 * 365 * 5) * 1000,
            })
    except (asyncio.TimeoutError, ClientError) as err:
        if isinstance(err, asyncio.TimeoutError):
            msg = "Request timeout"
        else:
            msg = err
        _LOGGER.warning("Unable to configure Almond: %s", msg)
        await opp.auth.async_remove_refresh_token(refresh_token)
        raise ConfigEntryNotReady from err

    # Clear all other refresh tokens
    for token in list(user.refresh_tokens.values()):
        if token.id != refresh_token.id:
            await opp.auth.async_remove_refresh_token(token)
Пример #12
0
 def redirect_uri(self) -> str:
     """Return the redirect uri."""
     url = get_url(self.opp, allow_internal=False, prefer_cloud=True)
     return f"{url}{AUTH_CALLBACK_PATH}"
Пример #13
0
def get_base_url(opp):
    """Get base URL."""
    return opp.data[BASE_URL_KEY] or get_url(opp)
Пример #14
0
def async_generate_url(opp, webhook_id):
    """Generate the full URL for a webhook_id."""
    return "{}{}".format(
        get_url(opp, prefer_external=True, allow_cloud=False),
        async_generate_path(webhook_id),
    )
Пример #15
0
async def test_get_current_request_url_with_known_host(opp: OpenPeerPower,
                                                       current_request):
    """Test getting current request URL with known hosts addresses."""
    opp.config.api = Mock(use_ssl=False, port=8123, local_ip="127.0.0.1")
    assert opp.config.internal_url is None

    with pytest.raises(NoURLAvailableError):
        get_url(opp, require_current_request=True)

    # Ensure we accept localhost
    with patch("openpeerpower.helpers.network._get_request_host",
               return_value="localhost"):
        assert get_url(opp,
                       require_current_request=True) == "http://localhost:8123"
        with pytest.raises(NoURLAvailableError):
            get_url(opp, require_current_request=True, require_ssl=True)
        with pytest.raises(NoURLAvailableError):
            get_url(opp,
                    require_current_request=True,
                    require_standard_port=True)

    # Ensure we accept local loopback ip (e.g., 127.0.0.1)
    with patch("openpeerpower.helpers.network._get_request_host",
               return_value="127.0.0.8"):
        assert get_url(opp,
                       require_current_request=True) == "http://127.0.0.8:8123"
        with pytest.raises(NoURLAvailableError):
            get_url(opp, require_current_request=True, allow_ip=False)

    # Ensure hostname from Supervisor is accepted transparently
    mock_component(opp, "oppio")
    opp.components.oppio.is_oppio = Mock(return_value=True)
    opp.components.oppio.get_host_info = Mock(
        return_value={"hostname": "openpeerpower"})

    with patch(
            "openpeerpower.helpers.network._get_request_host",
            return_value="openpeerpower.local",
    ):
        assert (get_url(
            opp,
            require_current_request=True) == "http://openpeerpower.local:8123")

    with patch(
            "openpeerpower.helpers.network._get_request_host",
            return_value="openpeerpower",
    ):
        assert get_url(
            opp, require_current_request=True) == "http://openpeerpower:8123"

    with patch(
            "openpeerpower.helpers.network._get_request_host",
            return_value="unknown.local"), pytest.raises(NoURLAvailableError):
        get_url(opp, require_current_request=True)
Пример #16
0
async def test_get_url(opp: OpenPeerPower):
    """Test getting an instance URL."""
    assert opp.config.external_url is None
    assert opp.config.internal_url is None

    with pytest.raises(NoURLAvailableError):
        get_url(opp)

    opp.config.api = Mock(use_ssl=False, port=8123, local_ip="192.168.123.123")
    assert get_url(opp) == "http://192.168.123.123:8123"
    assert get_url(opp, prefer_external=True) == "http://192.168.123.123:8123"

    with pytest.raises(NoURLAvailableError):
        get_url(opp, allow_internal=False)

    # Test only external
    opp.config.api = None
    await async_process_op_core_config(
        opp,
        {"external_url": "https://example.com"},
    )
    assert opp.config.external_url == "https://example.com"
    assert opp.config.internal_url is None
    assert get_url(opp) == "https://example.com"

    # Test preference or allowance
    await async_process_op_core_config(
        opp,
        {
            "internal_url": "http://example.local",
            "external_url": "https://example.com"
        },
    )
    assert opp.config.external_url == "https://example.com"
    assert opp.config.internal_url == "http://example.local"
    assert get_url(opp) == "http://example.local"
    assert get_url(opp, prefer_external=True) == "https://example.com"
    assert get_url(opp, allow_internal=False) == "https://example.com"
    assert (get_url(opp, prefer_external=True,
                    allow_external=False) == "http://example.local")

    with pytest.raises(NoURLAvailableError):
        get_url(opp, allow_external=False, require_ssl=True)

    with pytest.raises(NoURLAvailableError):
        get_url(opp, allow_external=False, allow_internal=False)

    with pytest.raises(NoURLAvailableError):
        get_url(opp, require_current_request=True)

    with patch("openpeerpower.helpers.network._get_request_host",
               return_value="example.com"), patch(
                   "openpeerpower.components.http.current_request"):
        assert get_url(opp,
                       require_current_request=True) == "https://example.com"
        assert (get_url(opp, require_current_request=True,
                        require_ssl=True) == "https://example.com")

        with pytest.raises(NoURLAvailableError):
            get_url(opp, require_current_request=True, allow_external=False)

    with patch("openpeerpower.helpers.network._get_request_host",
               return_value="example.local"), patch(
                   "openpeerpower.components.http.current_request"):
        assert get_url(opp,
                       require_current_request=True) == "http://example.local"

        with pytest.raises(NoURLAvailableError):
            get_url(opp, require_current_request=True, allow_internal=False)

        with pytest.raises(NoURLAvailableError):
            get_url(opp, require_current_request=True, require_ssl=True)

    with patch(
            "openpeerpower.helpers.network._get_request_host",
            return_value="no_match.example.com",
    ), pytest.raises(NoURLAvailableError):
        _get_internal_url(opp, require_current_request=True)
Пример #17
0
    async def sync_serialize(self, agent_user_id):
        """Serialize entity for a SYNC response.

        https://developers.google.com/actions/smarthome/create-app#actiondevicessync
        """
        state = self.state

        entity_config = self.config.entity_config.get(state.entity_id, {})
        name = (entity_config.get(CONF_NAME) or state.name).strip()
        domain = state.domain
        device_class = state.attributes.get(ATTR_DEVICE_CLASS)
        entity_entry, device_entry = await _get_entity_and_device(
            self.opp, state.entity_id)

        traits = self.traits()

        device_type = get_google_type(domain, device_class)

        device = {
            "id": state.entity_id,
            "name": {
                "name": name
            },
            "attributes": {},
            "traits": [trait.name for trait in traits],
            "willReportState": self.config.should_report_state,
            "type": device_type,
        }

        # use aliases
        aliases = entity_config.get(CONF_ALIASES)
        if aliases:
            device["name"]["nicknames"] = [name] + aliases

        if self.config.is_local_sdk_active and self.should_expose_local():
            device["otherDeviceIds"] = [{"deviceId": self.entity_id}]
            device["customData"] = {
                "webhookId": self.config.local_sdk_webhook_id,
                "httpPort": self.opp.http.server_port,
                "httpSSL": self.opp.config.api.use_ssl,
                "uuid": await self.opp.helpers.instance_id.async_get(),
                "baseUrl": get_url(self.opp, prefer_external=True),
                "proxyDeviceId": agent_user_id,
            }

        for trt in traits:
            device["attributes"].update(trt.sync_attributes())

        room = entity_config.get(CONF_ROOM_HINT)
        if room:
            device["roomHint"] = room
        else:
            area = await _get_area(self.opp, entity_entry, device_entry)
            if area and area.name:
                device["roomHint"] = area.name

        device_info = await _get_device_info(device_entry)
        if device_info:
            device["deviceInfo"] = device_info

        return device