Beispiel #1
0
    async def _websocket_client(self):
        """Initialize a WebSocket API connection."""
        url = f"{self.sys_homeassistant.api_url}/api/websocket"

        try:
            client = await self.sys_websession_ssl.ws_connect(url,
                                                              heartbeat=60,
                                                              verify_ssl=False)

            # Handle authentication
            data = await client.receive_json()

            if data.get('type') == 'auth_ok':
                return client

            if data.get('type') != 'auth_required':
                # Invalid protocol
                _LOGGER.error("Got unexpected response from HA WebSocket: %s",
                              data)
                raise HTTPBadGateway()

            if self.sys_homeassistant.refresh_token:
                await self.sys_homeassistant.ensure_access_token()
                await client.send_json({
                    'type':
                    'auth',
                    'access_token':
                    self.sys_homeassistant.access_token,
                })
            else:
                await client.send_json({
                    'type':
                    'auth',
                    'api_password':
                    self.sys_homeassistant.api_password,
                })

            data = await client.receive_json()

            if data.get('type') == 'auth_ok':
                return client

            # Renew the Token is invalid
            if (data.get('type') == 'invalid_auth'
                    and self.sys_homeassistant.refresh_token):
                self.sys_homeassistant.access_token = None
                return await self._websocket_client()

            raise HomeAssistantAuthError()

        except (RuntimeError, ValueError) as err:
            _LOGGER.error("Client error on WebSocket API %s.", err)
        except HomeAssistantAuthError as err:
            _LOGGER.error("Failed authentication to Home Assistant WebSocket")

        raise HTTPBadGateway()
def async_aiohttp_proxy_web(hass,
                            request,
                            web_coro,
                            buffer_size=102400,
                            timeout=10):
    """Stream websession request to aiohttp web response."""
    try:
        with async_timeout.timeout(timeout, loop=hass.loop):
            req = yield from web_coro

    except asyncio.CancelledError:
        # The user cancelled the request
        return

    except asyncio.TimeoutError as err:
        # Timeout trying to start the web request
        raise HTTPGatewayTimeout() from err

    except aiohttp.ClientError as err:
        # Something went wrong with the connection
        raise HTTPBadGateway() from err

    try:
        yield from async_aiohttp_proxy_stream(hass, request, req.content,
                                              req.headers.get(CONTENT_TYPE))
    finally:
        req.close()
    async def _api_client(self,
                          request: web.Request,
                          path: str,
                          timeout: int = 300):
        """Return a client request with proxy origin for Home Assistant."""
        try:
            async with self.sys_homeassistant.make_request(
                    request.method.lower(),
                    f"api/{path}",
                    headers={
                        name: value
                        for name, value in request.headers.items()
                        if name in FORWARD_HEADERS
                    },
                    content_type=request.content_type,
                    data=request.content,
                    timeout=timeout,
                    params=request.query,
            ) as resp:
                yield resp
                return

        except HomeAssistantAuthError:
            _LOGGER.error("Authenticate error on API for request %s", path)
        except HomeAssistantAPIError:
            _LOGGER.error("Error on API for request %s", path)
        except aiohttp.ClientError as err:
            _LOGGER.error("Client error on API %s request %s", path, err)
        except asyncio.TimeoutError:
            _LOGGER.error("Client timeout error on API request %s", path)

        raise HTTPBadGateway()
Beispiel #4
0
    async def _websocket_client(self):
        """Initialize a websocket api connection."""
        url = f"{self.sys_homeassistant.api_url}/api/websocket"

        try:
            client = await self.sys_websession_ssl.ws_connect(url,
                                                              heartbeat=60,
                                                              verify_ssl=False)

            # handle authentication
            for _ in range(2):
                data = await client.receive_json()
                if data.get('type') == 'auth_ok':
                    return client
                elif data.get('type') == 'auth_required':
                    await client.send_json({
                        'type':
                        'auth',
                        'api_password':
                        self.sys_homeassistant.api_password,
                    })

            _LOGGER.error("Authentication to Home-Assistant websocket")

        except (aiohttp.ClientError, RuntimeError) as err:
            _LOGGER.error("Client error on websocket API %s.", err)

        raise HTTPBadGateway()
Beispiel #5
0
    async def handler(
        self, request: web.Request
    ) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]:
        """Route data to Supervisor ingress service."""

        # Check Ingress Session
        session = request.cookies.get(COOKIE_INGRESS)
        if not self.sys_ingress.validate_session(session):
            _LOGGER.warning("No valid ingress session %s", session)
            raise HTTPUnauthorized()

        # Process requests
        addon = self._extract_addon(request)
        path = request.match_info.get("path")
        try:
            # Websocket
            if _is_websocket(request):
                return await self._handle_websocket(request, addon, path)

            # Request
            return await self._handle_request(request, addon, path)

        except aiohttp.ClientError as err:
            _LOGGER.error("Ingress error: %s", err)

        raise HTTPBadGateway()
Beispiel #6
0
    def _command_proxy(self, path, request):
        """Return a client request with proxy origin for Hass.io supervisor.

        This method is a coroutine.
        """
        read_timeout = _get_timeout(path)
        hass = request.app['hass']

        try:
            data = None
            headers = {X_HASSIO: os.environ.get('HASSIO_TOKEN', "")}
            with async_timeout.timeout(10, loop=hass.loop):
                data = yield from request.read()
                if data:
                    headers[CONTENT_TYPE] = request.content_type
                else:
                    data = None

            method = getattr(self._websession, request.method.lower())
            client = yield from method(
                "http://{}/{}".format(self._host, path), data=data,
                headers=headers, timeout=read_timeout
            )

            return client

        except aiohttp.ClientError as err:
            _LOGGER.error("Client error on api %s request %s", path, err)

        except asyncio.TimeoutError:
            _LOGGER.error("Client timeout error on API request %s", path)

        raise HTTPBadGateway()
Beispiel #7
0
 def get(self, request, addon):
     """Get addon data."""
     data = yield from self.hassio.send_command(
         "/addons/{}/info".format(addon))
     if not data:
         raise HTTPBadGateway()
     return web.json_response(data)
Beispiel #8
0
    async def _handle(
        self, request: web.Request, token: str, path: str
    ) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]:
        """Route data to Hass.io ingress service."""
        # validate token
        if token != self._valid_token:
            try:
                auth = self._hass.auth
                refresh_token = await auth.async_validate_access_token(token)
                if refresh_token is None:
                    raise HTTPUnauthorized() from None
                # remember the token as valid
                self._valid_token = token
            except Exception:
                raise HTTPUnauthorized() from None

        try:
            # Websockettoken
            if _is_websocket(request):
                return await self._handle_websocket(request, token, path)

            # Request
            return await self._handle_request(request, token, path)

        except aiohttp.ClientError as err:
            _LOGGER.debug("Ingress error with %s / %s: %s", token, path, err)

        raise HTTPBadGateway() from None
Beispiel #9
0
    async def _api_client(self, request, path, timeout=300):
        """Return a client request with proxy origin for Home Assistant."""
        try:
            # read data
            with async_timeout.timeout(30):
                data = await request.read()

            if data:
                content_type = request.content_type
            else:
                content_type = None

            async with self.sys_homeassistant.make_request(
                    request.method.lower(), f'api/{path}',
                    content_type=content_type,
                    data=data,
                    timeout=timeout,
            ) as resp:
                yield resp
                return

        except HomeAssistantAuthError:
            _LOGGER.error("Authenticate error on API for request %s", path)
        except HomeAssistantAPIError:
            _LOGGER.error("Error on API for request %s", path)
        except aiohttp.ClientError as err:
            _LOGGER.error("Client error on API %s request %s", path, err)
        except asyncio.TimeoutError:
            _LOGGER.error("Client timeout error on API request %s", path)

        raise HTTPBadGateway()
Beispiel #10
0
    def command_proxy(self, path, request):
        """Return a client request with proxy origin for HassIO supervisor.

        This method is a coroutine.
        """
        try:
            data = None
            headers = None
            with async_timeout.timeout(TIMEOUT, loop=self.loop):
                data = yield from request.read()
                if data:
                    headers = {CONTENT_TYPE: request.content_type}
                else:
                    data = None

            method = getattr(self.websession, request.method.lower())
            client = yield from method("http://{}/{}".format(self._ip, path),
                                       data=data,
                                       headers=headers)

            return client

        except aiohttp.ClientError as err:
            _LOGGER.error("Client error on api %s request %s.", path, err)

        except asyncio.TimeoutError:
            _LOGGER.error("Client timeout error on api request %s.", path)

        raise HTTPBadGateway()
Beispiel #11
0
async def async_aiohttp_proxy_web(
        hass: HomeAssistantType,
        request: web.BaseRequest,
        web_coro: Awaitable[aiohttp.ClientResponse],
        buffer_size: int = 102400,
        timeout: int = 10) -> Optional[web.StreamResponse]:
    """Stream websession request to aiohttp web response."""
    try:
        with async_timeout.timeout(timeout, loop=hass.loop):
            req = await web_coro

    except asyncio.CancelledError:
        # The user cancelled the request
        return None

    except asyncio.TimeoutError as err:
        # Timeout trying to start the web request
        raise HTTPGatewayTimeout() from err

    except aiohttp.ClientError as err:
        # Something went wrong with the connection
        raise HTTPBadGateway() from err

    try:
        return await async_aiohttp_proxy_stream(hass, request, req.content,
                                                req.headers.get(CONTENT_TYPE))
    finally:
        req.close()
Beispiel #12
0
    async def _command_proxy(
            self, path: str,
            request: web.Request) -> web.Response | web.StreamResponse:
        """Return a client request with proxy origin for Hass.io supervisor.

        This method is a coroutine.
        """
        read_timeout = _get_timeout(path)
        client_timeout = 10
        data = None
        headers = _init_header(request)
        if path == "snapshots/new/upload":
            # We need to reuse the full content type that includes the boundary
            headers["Content-Type"] = request._stored_content_type  # pylint: disable=protected-access

            # Snapshots are big, so we need to adjust the allowed size
            request._client_max_size = (  # pylint: disable=protected-access
                MAX_UPLOAD_SIZE)
            client_timeout = 300

        try:
            with async_timeout.timeout(client_timeout):
                data = await request.read()

            method = getattr(self._websession, request.method.lower())
            client = await method(
                f"http://{self._host}/{path}",
                data=data,
                headers=headers,
                timeout=read_timeout,
            )

            # Simple request
            if int(client.headers.get(CONTENT_LENGTH, 0)) < 4194000:
                # Return Response
                body = await client.read()
                return web.Response(content_type=client.content_type,
                                    status=client.status,
                                    body=body)

            # Stream response
            response = web.StreamResponse(status=client.status,
                                          headers=client.headers)
            response.content_type = client.content_type

            await response.prepare(request)
            async for data in client.content.iter_chunked(4096):
                await response.write(data)

            return response

        except aiohttp.ClientError as err:
            _LOGGER.error("Client error on api %s request %s", path, err)

        except asyncio.TimeoutError:
            _LOGGER.error("Client timeout error on API request %s", path)

        raise HTTPBadGateway()
Beispiel #13
0
    def post(self, request, addon):
        """Set options on host."""
        data = yield from request.json()

        response = yield from self.hassio.send_raw(
            "/addons/{}/options".format(addon), payload=data)
        if not response:
            raise HTTPBadGateway()
        return web.json_response(response)
Beispiel #14
0
    def post(self, request):
        """Set options on host."""
        data = yield from request.json()

        response = yield from self.hassio.send_raw(
            self._url_options, payload=data)
        if not response:
            raise HTTPBadGateway()
        return web.json_response(response)
Beispiel #15
0
async def test_add_new_message_does_not_update_rounds_on_error():
    round_mock = AsyncMock()
    round_mock.get_key = Mock(side_effect=HTTPBadGateway())
    with patch("stummtaube.data.rounds.rounds", {567: Mock()}) as mocked_rounds:
        try:
            await add_new_message(round_mock, Mock())
            assert False
        except HTTPBadGateway:
            assert mocked_rounds[567] is not None
Beispiel #16
0
    async def _handle(
        self, request: web.Request, path: str
    ) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]:
        """Route data to service."""
        try:
            return await self._handle_request(request, path)

        except aiohttp.ClientError as err:
            _LOGGER.debug("Reverse proxy error with %s: %s", path, err)

        raise HTTPBadGateway() from None
Beispiel #17
0
    async def get(
        self,
        request: web.Request,
        **kwargs: Any,
    ) -> web.Response | web.StreamResponse | web.WebSocketResponse:
        """Route data to service."""
        try:
            return await self._handle_request(request, **kwargs)

        except aiohttp.ClientError as err:
            _LOGGER.debug("Reverse proxy error for %s: %s", request.rel_url, err)

        raise HTTPBadGateway() from None
    async def api(self, request: web.Request):
        """Proxy Home Assistant API Requests."""
        self._check_access(request)
        if not await self.sys_homeassistant.check_api_state():
            raise HTTPBadGateway()

        # Normal request
        path = request.match_info.get("path", "")
        async with self._api_client(request, path) as client:
            data = await client.read()
            return web.Response(body=data,
                                status=client.status,
                                content_type=client.content_type)
Beispiel #19
0
    async def _command_proxy(
            self, path: str,
            request: web.Request) -> Union[web.Response, web.StreamResponse]:
        """Return a client request with proxy origin for Hass.io supervisor.

        This method is a coroutine.
        """
        read_timeout = _get_timeout(path)
        hass = request.app['hass']

        data = None
        headers = _init_header(request)

        try:
            with async_timeout.timeout(10, loop=hass.loop):
                data = await request.read()

            method = getattr(self._websession, request.method.lower())
            client = await method("http://{}/{}".format(self._host, path),
                                  data=data,
                                  headers=headers,
                                  timeout=read_timeout)
            print(client.headers)

            # Simple request
            if int(client.headers.get(CONTENT_LENGTH, 0)) < 4194000:
                # Return Response
                body = await client.read()
                return web.Response(
                    content_type=client.content_type,
                    status=client.status,
                    body=body,
                )

            # Stream response
            response = web.StreamResponse(status=client.status)
            response.content_type = client.content_type

            await response.prepare(request)
            async for data in client.content.iter_chunked(4096):
                await response.write(data)

            return response

        except aiohttp.ClientError as err:
            _LOGGER.error("Client error on api %s request %s", path, err)

        except asyncio.TimeoutError:
            _LOGGER.error("Client timeout error on API request %s", path)

        raise HTTPBadGateway()
Beispiel #20
0
    async def _handle(
            self, request: web.Request, addon: str, path: str
    ) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]:
        """Route data to Hass.io ingress service."""
        try:
            # Websocket
            if _is_websocket(request):
                return await self._handle_websocket(request, addon, path)

            # Request
            return await self._handle_request(request, addon, path)

        except aiohttp.ClientError:
            pass

        raise HTTPBadGateway() from None
Beispiel #21
0
    async def _handle(
        self, request: web.Request, token: str, path: str
    ) -> web.Response | web.StreamResponse | web.WebSocketResponse:
        """Route data to Hass.io ingress service."""
        try:
            # Websocket
            if _is_websocket(request):
                return await self._handle_websocket(request, token, path)

            # Request
            return await self._handle_request(request, token, path)

        except aiohttp.ClientError as err:
            _LOGGER.debug("Ingress error with %s / %s: %s", token, path, err)

        raise HTTPBadGateway() from None
Beispiel #22
0
    async def _command_proxy(
        self, path: str, request: web.Request
    ) -> web.StreamResponse:
        """Return a client request with proxy origin for Hass.io supervisor.

        This method is a coroutine.
        """
        headers = _init_header(request)
        if path == "backups/new/upload":
            # We need to reuse the full content type that includes the boundary
            headers[
                "Content-Type"
            ] = request._stored_content_type  # pylint: disable=protected-access

        try:
            client = await self._websession.request(
                method=request.method,
                url=f"http://{self._host}/{path}",
                params=request.query,
                data=request.content,
                headers=headers,
                timeout=_get_timeout(path),
            )

            # Stream response
            response = web.StreamResponse(
                status=client.status, headers=_response_header(client, path)
            )
            response.content_type = client.content_type

            await response.prepare(request)
            async for data in client.content.iter_chunked(4096):
                await response.write(data)

            return response

        except aiohttp.ClientError as err:
            _LOGGER.error("Client error on api %s request %s", path, err)

        except asyncio.TimeoutError:
            _LOGGER.error("Client timeout error on API request %s", path)

        raise HTTPBadGateway()
Beispiel #23
0
    async def _api_client(self, request, path, timeout=300):
        """Return a client request with proxy origin for Home-Assistant."""
        url = f"{self.sys_homeassistant.api_url}/api/{path}"

        try:
            data = None
            headers = {}
            method = getattr(self._websession_ssl, request.method.lower())
            params = request.query or None

            # read data
            with async_timeout.timeout(30):
                data = await request.read()

            if data:
                headers.update({CONTENT_TYPE: request.content_type})

            # need api password?
            if self.sys_homeassistant.api_password:
                headers = {
                    HEADER_HA_ACCESS: self.sys_homeassistant.api_password,
                }

            # reset headers
            if not headers:
                headers = None

            client = await method(url,
                                  data=data,
                                  headers=headers,
                                  timeout=timeout,
                                  params=params)

            return client

        except aiohttp.ClientError as err:
            _LOGGER.error("Client error on API %s request %s.", path, err)

        except asyncio.TimeoutError:
            _LOGGER.error("Client timeout error on API request %s.", path)

        raise HTTPBadGateway()
    async def stream(self, request: web.Request):
        """Proxy HomeAssistant EventStream Requests."""
        self._check_access(request)
        if not await self.sys_homeassistant.check_api_state():
            raise HTTPBadGateway()

        _LOGGER.info("Home Assistant EventStream start")
        async with self._api_client(request, "stream", timeout=None) as client:
            response = web.StreamResponse()
            response.content_type = request.headers.get(CONTENT_TYPE)
            try:
                await response.prepare(request)
                async for data in client.content:
                    await response.write(data)

            except (aiohttp.ClientError, aiohttp.ClientPayloadError):
                pass

            _LOGGER.info("Home Assistant EventStream close")
            return response
Beispiel #25
0
    async def _handle(
        self, request: web.Request, path: str
    ) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]:
        """Check the authentication"""
        # authenticated = request.get(KEY_AUTHENTICATED, False)
        # _LOGGER.warning("request " + str(request))
        # if not authenticated:
        #     return web.Response(status=401)
        """Route data to ingress service."""
        try:
            # Websocket
            if _is_websocket(request):
                return await self._handle_websocket(request, path)

            # Request
            # if not request["ha_authenticated"]:
            #     return web.Response(status=401)
            return await self._handle_request(request, path)

        except aiohttp.ClientError as err:
            _LOGGER.debug("Ingress error with %s: %s", path, err)

        raise HTTPBadGateway() from None
Beispiel #26
0
    async def homeassistant_proxy(self, path, request):
        """Return a client request with proxy origin for Home-Assistant."""
        url = "{}/api/{}".format(self.homeassistant.api_url, path)

        try:
            data = None
            headers = {}
            method = getattr(self.homeassistant.websession,
                             request.method.lower())

            # read data
            with async_timeout.timeout(10, loop=self.loop):
                data = await request.read()

            if data:
                headers.update({CONTENT_TYPE: request.content_type})

            # need api password?
            if self.homeassistant.api_password:
                headers = {HEADER_HA_ACCESS: self.homeassistant.api_password}

            # reset headers
            if not headers:
                headers = None

            client = await method(url, data=data, headers=headers, timeout=300)

            return client

        except aiohttp.ClientError as err:
            _LOGGER.error("Client error on api %s request %s.", path, err)

        except asyncio.TimeoutError:
            _LOGGER.error("Client timeout error on api request %s.", path)

        raise HTTPBadGateway()
Beispiel #27
0
 def get(self, request):
     """Get base data."""
     data = yield from self.hassio.send_command(self._url_info)
     if not data:
         raise HTTPBadGateway()
     return web.json_response(data)
    async def websocket(self, request: web.Request):
        """Initialize a WebSocket API connection."""
        if not await self.sys_homeassistant.check_api_state():
            raise HTTPBadGateway()
        _LOGGER.info("Home Assistant WebSocket API request initialize")

        # init server
        server = web.WebSocketResponse(heartbeat=30)
        await server.prepare(request)

        # handle authentication
        try:
            await server.send_json({
                "type": "auth_required",
                "ha_version": self.sys_homeassistant.version
            })

            # Check API access
            response = await server.receive_json()
            supervisor_token = response.get("api_password") or response.get(
                "access_token")
            addon = self.sys_addons.from_token(supervisor_token)

            if not addon or not addon.access_homeassistant_api:
                _LOGGER.warning("Unauthorized WebSocket access!")
                await server.send_json({
                    "type": "auth_invalid",
                    "message": "Invalid access"
                })
                return server

            _LOGGER.info("WebSocket access from %s", addon.slug)

            await server.send_json({
                "type": "auth_ok",
                "ha_version": self.sys_homeassistant.version
            })
        except (RuntimeError, ValueError) as err:
            _LOGGER.error("Can't initialize handshake: %s", err)
            return server

        # init connection to hass
        try:
            client = await self._websocket_client()
        except APIError:
            return server

        _LOGGER.info("Home Assistant WebSocket API request running")
        try:
            client_read = None
            server_read = None
            while not server.closed and not client.closed:
                if not client_read:
                    client_read = self.sys_create_task(client.receive_str())
                if not server_read:
                    server_read = self.sys_create_task(server.receive_str())

                # wait until data need to be processed
                await asyncio.wait([client_read, server_read],
                                   return_when=asyncio.FIRST_COMPLETED)

                # server
                if server_read.done() and not client.closed:
                    server_read.exception()
                    await client.send_str(server_read.result())
                    server_read = None

                # client
                if client_read.done() and not server.closed:
                    client_read.exception()
                    await server.send_str(client_read.result())
                    client_read = None

        except asyncio.CancelledError:
            pass

        except (RuntimeError, ConnectionError, TypeError) as err:
            _LOGGER.info("Home Assistant WebSocket API error: %s", err)

        finally:
            if client_read:
                client_read.cancel()
            if server_read:
                server_read.cancel()

            # close connections
            if not client.closed:
                await client.close()
            if not server.closed:
                await server.close()

        _LOGGER.info("Home Assistant WebSocket API connection is closed")
        return server