Пример #1
0
    async def async_handle(self, msg):
        """Handle authentication."""
        try:
            msg = AUTH_MESSAGE_SCHEMA(msg)
        except vol.Invalid as err:
            error_msg = 'Auth message incorrectly formatted: {}'.format(
                humanize_error(msg, err))
            self._logger.warning(error_msg)
            self._send_message(auth_invalid_message(error_msg))
            raise Disconnect

        if self._hass.auth.active and 'access_token' in msg:
            self._logger.debug("Received access_token")
            refresh_token = \
                await self._hass.auth.async_validate_access_token(
                    msg['access_token'])
            if refresh_token is not None:
                return await self._async_finish_auth(
                    refresh_token.user, refresh_token)

        elif ((not self._hass.auth.active or self._hass.auth.support_legacy)
              and 'api_password' in msg):
            self._logger.debug("Received api_password")
            if validate_password(self._request, msg['api_password']):
                return await self._async_finish_auth(None, None)

        self._send_message(auth_invalid_message(
            'Invalid access token or password'))
        await process_wrong_login(self._request)
        raise Disconnect
Пример #2
0
    async def handle(self):
        """Handle the websocket connection."""
        request = self.request
        wsock = self.wsock = web.WebSocketResponse(heartbeat=55)
        await wsock.prepare(request)
        self.debug("Connected")

        self._handle_task = asyncio.Task.current_task(loop=self.hass.loop)

        @callback
        def handle_hass_stop(event):
            """Cancel this connection."""
            self.cancel()

        unsub_stop = self.hass.bus.async_listen(
            EVENT_HOMEASSISTANT_STOP, handle_hass_stop)
        self._writer_task = self.hass.async_add_job(self._writer())
        final_message = None
        msg = None
        authenticated = False

        try:
            if request[KEY_AUTHENTICATED]:
                authenticated = True

            # always request auth when auth is active
            #   even request passed pre-authentication (trusted networks)
            # or when using legacy api_password
            if self.hass.auth.active or not authenticated:
                self.debug("Request auth")
                await self.wsock.send_json(auth_required_message())
                msg = await wsock.receive_json()
                msg = AUTH_MESSAGE_SCHEMA(msg)

                if self.hass.auth.active and 'access_token' in msg:
                    self.debug("Received access_token")
                    refresh_token = \
                        await self.hass.auth.async_validate_access_token(
                            msg['access_token'])
                    authenticated = refresh_token is not None
                    if authenticated:
                        request['hass_user'] = refresh_token.user

                elif ((not self.hass.auth.active or
                       self.hass.auth.support_legacy) and
                      'api_password' in msg):
                    self.debug("Received api_password")
                    authenticated = validate_password(
                        request, msg['api_password'])

            if not authenticated:
                self.debug("Authorization failed")
                await self.wsock.send_json(
                    auth_invalid_message('Invalid access token or password'))
                await process_wrong_login(request)
                return wsock

            self.debug("Auth OK")
            await process_success_login(request)
            await self.wsock.send_json(auth_ok_message())

            # ---------- AUTH PHASE OVER ----------

            msg = await wsock.receive_json()
            last_id = 0
            handlers = self.hass.data[DOMAIN]

            while msg:
                self.debug("Received", msg)
                msg = MINIMAL_MESSAGE_SCHEMA(msg)
                cur_id = msg['id']

                if cur_id <= last_id:
                    self.to_write.put_nowait(error_message(
                        cur_id, ERR_ID_REUSE,
                        'Identifier values have to increase.'))

                elif msg['type'] not in handlers:
                    self.log_error(
                        'Received invalid command: {}'.format(msg['type']))
                    self.to_write.put_nowait(error_message(
                        cur_id, ERR_UNKNOWN_COMMAND,
                        'Unknown command.'))

                else:
                    handler, schema = handlers[msg['type']]
                    handler(self.hass, self, schema(msg))

                last_id = cur_id
                msg = await wsock.receive_json()

        except vol.Invalid as err:
            error_msg = "Message incorrectly formatted: "
            if msg:
                error_msg += humanize_error(msg, err)
            else:
                error_msg += str(err)

            self.log_error(error_msg)

            if not authenticated:
                final_message = auth_invalid_message(error_msg)

            else:
                if isinstance(msg, dict):
                    iden = msg.get('id')
                else:
                    iden = None

                final_message = error_message(
                    iden, ERR_INVALID_FORMAT, error_msg)

        except TypeError as err:
            if wsock.closed:
                self.debug("Connection closed by client")
            else:
                _LOGGER.exception("Unexpected TypeError: %s", err)

        except ValueError as err:
            msg = "Received invalid JSON"
            value = getattr(err, 'doc', None)  # Py3.5+ only
            if value:
                msg += ': {}'.format(value)
            self.log_error(msg)
            self._writer_task.cancel()

        except CANCELLATION_ERRORS:
            self.debug("Connection cancelled")

        except asyncio.QueueFull:
            self.log_error("Client exceeded max pending messages [1]:",
                           MAX_PENDING_MSG)
            self._writer_task.cancel()

        except Exception:  # pylint: disable=broad-except
            error = "Unexpected error inside websocket API. "
            if msg is not None:
                error += str(msg)
            _LOGGER.exception(error)

        finally:
            unsub_stop()

            for unsub in self.event_listeners.values():
                unsub()

            try:
                if final_message is not None:
                    self.to_write.put_nowait(final_message)
                self.to_write.put_nowait(None)
                # Make sure all error messages are written before closing
                await self._writer_task
            except asyncio.QueueFull:
                self._writer_task.cancel()

            await wsock.close()
            self.debug("Closed connection")

        return wsock
Пример #3
0
    def handle(self, request):
        """Handle the websocket connection."""
        wsock = self.wsock = web.WebSocketResponse()
        yield from wsock.prepare(request)
        self.debug("Connected")

        # Get a reference to current task so we can cancel our connection
        self._handle_task = asyncio.Task.current_task(loop=self.hass.loop)

        @callback
        def handle_hass_stop(event):
            """Cancel this connection."""
            self.cancel()

        unsub_stop = self.hass.bus.async_listen(
            EVENT_HOMEASSISTANT_STOP, handle_hass_stop)
        self._writer_task = self.hass.async_add_job(self._writer())
        final_message = None
        msg = None
        authenticated = False

        try:
            if request[KEY_AUTHENTICATED]:
                authenticated = True

            else:
                yield from self.wsock.send_json(auth_required_message())
                msg = yield from wsock.receive_json()
                msg = AUTH_MESSAGE_SCHEMA(msg)

                if validate_password(request, msg['api_password']):
                    authenticated = True

                else:
                    self.debug("Invalid password")
                    yield from self.wsock.send_json(
                        auth_invalid_message('Invalid password'))

            if not authenticated:
                yield from process_wrong_login(request)
                return wsock

            yield from self.wsock.send_json(auth_ok_message())

            # ---------- AUTH PHASE OVER ----------

            msg = yield from wsock.receive_json()
            last_id = 0

            while msg:
                self.debug("Received", msg)
                msg = BASE_COMMAND_MESSAGE_SCHEMA(msg)
                cur_id = msg['id']

                if cur_id <= last_id:
                    self.to_write.put_nowait(error_message(
                        cur_id, ERR_ID_REUSE,
                        'Identifier values have to increase.'))

                else:
                    handler_name = 'handle_{}'.format(msg['type'])
                    getattr(self, handler_name)(msg)

                last_id = cur_id
                msg = yield from wsock.receive_json()

        except vol.Invalid as err:
            error_msg = "Message incorrectly formatted: "
            if msg:
                error_msg += humanize_error(msg, err)
            else:
                error_msg += str(err)

            self.log_error(error_msg)

            if not authenticated:
                final_message = auth_invalid_message(error_msg)

            else:
                if isinstance(msg, dict):
                    iden = msg.get('id')
                else:
                    iden = None

                final_message = error_message(
                    iden, ERR_INVALID_FORMAT, error_msg)

        except TypeError as err:
            if wsock.closed:
                self.debug("Connection closed by client")
            else:
                self.log_error("Unexpected TypeError", msg)

        except ValueError as err:
            msg = "Received invalid JSON"
            value = getattr(err, 'doc', None)  # Py3.5+ only
            if value:
                msg += ': {}'.format(value)
            self.log_error(msg)
            self._writer_task.cancel()

        except asyncio.CancelledError:
            self.debug("Connection cancelled by server")

        except asyncio.QueueFull:
            self.log_error("Client exceeded max pending messages [1]:",
                           MAX_PENDING_MSG)
            self._writer_task.cancel()

        except Exception:  # pylint: disable=broad-except
            error = "Unexpected error inside websocket API. "
            if msg is not None:
                error += str(msg)
            _LOGGER.exception(error)

        finally:
            unsub_stop()

            for unsub in self.event_listeners.values():
                unsub()

            try:
                if final_message is not None:
                    self.to_write.put_nowait(final_message)
                self.to_write.put_nowait(None)
                # Make sure all error messages are written before closing
                yield from self._writer_task
            except asyncio.QueueFull:
                self._writer_task.cancel()

            yield from wsock.close()
            self.debug("Closed connection")

        return wsock
Пример #4
0
    def handle(self):
        """Handle the websocket connection."""
        wsock = self.wsock = web.WebSocketResponse()
        yield from wsock.prepare(self.request)

        # Set up to cancel this connection when Home Assistant shuts down
        socket_task = asyncio.Task.current_task(loop=self.hass.loop)

        @callback
        def cancel_connection(event):
            """Cancel this connection."""
            socket_task.cancel()

        unsub_stop = self.hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP,
                                                cancel_connection)

        self.debug('Connected')

        msg = None
        authenticated = False

        try:
            if self.request[KEY_AUTHENTICATED]:
                authenticated = True

            else:
                self.send_message(auth_required_message())
                msg = yield from wsock.receive_json()
                msg = AUTH_MESSAGE_SCHEMA(msg)

                if validate_password(self.request, msg['api_password']):
                    authenticated = True

                else:
                    self.debug('Invalid password')
                    self.send_message(auth_invalid_message('Invalid password'))

            if not authenticated:
                yield from process_wrong_login(self.request)
                return wsock

            self.send_message(auth_ok_message())

            msg = yield from wsock.receive_json()

            last_id = 0

            while msg:
                self.debug('Received', msg)
                msg = BASE_COMMAND_MESSAGE_SCHEMA(msg)
                cur_id = msg['id']

                if cur_id <= last_id:
                    self.send_message(error_message(
                        cur_id, ERR_ID_REUSE,
                        'Identifier values have to increase.'))

                else:
                    handler_name = 'handle_{}'.format(msg['type'])
                    getattr(self, handler_name)(msg)

                last_id = cur_id
                msg = yield from wsock.receive_json()

        except vol.Invalid as err:
            error_msg = 'Message incorrectly formatted: '
            if msg:
                error_msg += humanize_error(msg, err)
            else:
                error_msg += str(err)

            self.log_error(error_msg)

            if not authenticated:
                self.send_message(auth_invalid_message(error_msg))

            else:
                if isinstance(msg, dict):
                    iden = msg.get('id')
                else:
                    iden = None

                self.send_message(error_message(iden, ERR_INVALID_FORMAT,
                                                error_msg))

        except TypeError as err:
            if wsock.closed:
                self.debug('Connection closed by client')
            else:
                self.log_error('Unexpected TypeError', msg)

        except ValueError as err:
            msg = 'Received invalid JSON'
            value = getattr(err, 'doc', None)  # Py3.5+ only
            if value:
                msg += ': {}'.format(value)
            self.log_error(msg)

        except asyncio.CancelledError:
            self.debug('Connection cancelled by server')

        except Exception:  # pylint: disable=broad-except
            error = 'Unexpected error inside websocket API. '
            if msg is not None:
                error += str(msg)
            _LOGGER.exception(error)

        finally:
            unsub_stop()

            for unsub in self.event_listeners.values():
                unsub()

            yield from wsock.close()
            self.debug('Closed connection')

        return wsock
Пример #5
0
    def handle(self):
        """Handle the websocket connection."""
        request = self.request
        wsock = self.wsock = web.WebSocketResponse(heartbeat=55)
        yield from wsock.prepare(request)
        self.debug("Connected")

        # Get a reference to current task so we can cancel our connection
        self._handle_task = asyncio.Task.current_task(loop=self.hass.loop)

        @callback
        def handle_hass_stop(event):
            """Cancel this connection."""
            self.cancel()

        unsub_stop = self.hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP,
                                                handle_hass_stop)
        self._writer_task = self.hass.async_add_job(self._writer())
        final_message = None
        msg = None
        authenticated = False

        try:
            if request[KEY_AUTHENTICATED]:
                authenticated = True

            else:
                yield from self.wsock.send_json(auth_required_message())
                msg = yield from wsock.receive_json()
                msg = AUTH_MESSAGE_SCHEMA(msg)

                if validate_password(request, msg['api_password']):
                    authenticated = True

                else:
                    self.debug("Invalid password")
                    yield from self.wsock.send_json(
                        auth_invalid_message('Invalid password'))

            if not authenticated:
                yield from process_wrong_login(request)
                return wsock

            yield from self.wsock.send_json(auth_ok_message())

            # ---------- AUTH PHASE OVER ----------

            msg = yield from wsock.receive_json()
            last_id = 0

            while msg:
                self.debug("Received", msg)
                msg = BASE_COMMAND_MESSAGE_SCHEMA(msg)
                cur_id = msg['id']

                if cur_id <= last_id:
                    self.to_write.put_nowait(
                        error_message(cur_id, ERR_ID_REUSE,
                                      'Identifier values have to increase.'))

                else:
                    handler_name = 'handle_{}'.format(msg['type'])
                    getattr(self, handler_name)(msg)

                last_id = cur_id
                msg = yield from wsock.receive_json()

        except vol.Invalid as err:
            error_msg = "Message incorrectly formatted: "
            if msg:
                error_msg += humanize_error(msg, err)
            else:
                error_msg += str(err)

            self.log_error(error_msg)

            if not authenticated:
                final_message = auth_invalid_message(error_msg)

            else:
                if isinstance(msg, dict):
                    iden = msg.get('id')
                else:
                    iden = None

                final_message = error_message(iden, ERR_INVALID_FORMAT,
                                              error_msg)

        except TypeError as err:
            if wsock.closed:
                self.debug("Connection closed by client")
            else:
                _LOGGER.exception("Unexpected TypeError: %s", msg)

        except ValueError as err:
            msg = "Received invalid JSON"
            value = getattr(err, 'doc', None)  # Py3.5+ only
            if value:
                msg += ': {}'.format(value)
            self.log_error(msg)
            self._writer_task.cancel()

        except CANCELLATION_ERRORS:
            self.debug("Connection cancelled by server")

        except asyncio.QueueFull:
            self.log_error("Client exceeded max pending messages [1]:",
                           MAX_PENDING_MSG)
            self._writer_task.cancel()

        except Exception:  # pylint: disable=broad-except
            error = "Unexpected error inside websocket API. "
            if msg is not None:
                error += str(msg)
            _LOGGER.exception(error)

        finally:
            unsub_stop()

            for unsub in self.event_listeners.values():
                unsub()

            try:
                if final_message is not None:
                    self.to_write.put_nowait(final_message)
                self.to_write.put_nowait(None)
                # Make sure all error messages are written before closing
                yield from self._writer_task
            except asyncio.QueueFull:
                self._writer_task.cancel()

            yield from wsock.close()
            self.debug("Closed connection")

        return wsock
Пример #6
0
    async def handle(self):
        """Handle the websocket connection."""
        request = self.request
        wsock = self.wsock = web.WebSocketResponse(heartbeat=55)
        await wsock.prepare(request)
        self.debug("Connected")

        self._handle_task = asyncio.Task.current_task(loop=self.hass.loop)

        @callback
        def handle_hass_stop(event):
            """Cancel this connection."""
            self.cancel()

        unsub_stop = self.hass.bus.async_listen(
            EVENT_HOMEASSISTANT_STOP, handle_hass_stop)
        self._writer_task = self.hass.async_add_job(self._writer())
        final_message = None
        msg = None
        authenticated = False

        try:
            if request[KEY_AUTHENTICATED]:
                authenticated = True

            # always request auth when auth is active
            #   even request passed pre-authentication (trusted networks)
            # or when using legacy api_password
            if self.hass.auth.active or not authenticated:
                self.debug("Request auth")
                await self.wsock.send_json(auth_required_message())
                msg = await wsock.receive_json()
                msg = AUTH_MESSAGE_SCHEMA(msg)

                if self.hass.auth.active and 'access_token' in msg:
                    self.debug("Received access_token")
                    refresh_token = \
                        await self.hass.auth.async_validate_access_token(
                            msg['access_token'])
                    authenticated = refresh_token is not None
                    if authenticated:
                        request['hass_user'] = refresh_token.user
                        request['refresh_token_id'] = refresh_token.id

                elif ((not self.hass.auth.active or
                       self.hass.auth.support_legacy) and
                      'api_password' in msg):
                    self.debug("Received api_password")
                    authenticated = validate_password(
                        request, msg['api_password'])

            if not authenticated:
                self.debug("Authorization failed")
                await self.wsock.send_json(
                    auth_invalid_message('Invalid access token or password'))
                await process_wrong_login(request)
                return wsock

            self.debug("Auth OK")
            await process_success_login(request)
            await self.wsock.send_json(auth_ok_message())

            # ---------- AUTH PHASE OVER ----------

            msg = await wsock.receive_json()
            last_id = 0
            handlers = self.hass.data[DOMAIN]

            while msg:
                self.debug("Received", msg)
                msg = MINIMAL_MESSAGE_SCHEMA(msg)
                cur_id = msg['id']

                if cur_id <= last_id:
                    self.to_write.put_nowait(error_message(
                        cur_id, ERR_ID_REUSE,
                        'Identifier values have to increase.'))

                elif msg['type'] not in handlers:
                    self.log_error(
                        'Received invalid command: {}'.format(msg['type']))
                    self.to_write.put_nowait(error_message(
                        cur_id, ERR_UNKNOWN_COMMAND,
                        'Unknown command.'))

                else:
                    handler, schema = handlers[msg['type']]
                    try:
                        handler(self.hass, self, schema(msg))
                    except Exception:  # pylint: disable=broad-except
                        _LOGGER.exception('Error handling message: %s', msg)
                        self.to_write.put_nowait(error_message(
                            cur_id, ERR_UNKNOWN_ERROR,
                            'Unknown error.'))

                last_id = cur_id
                msg = await wsock.receive_json()

        except vol.Invalid as err:
            error_msg = "Message incorrectly formatted: "
            if msg:
                error_msg += humanize_error(msg, err)
            else:
                error_msg += str(err)

            self.log_error(error_msg)

            if not authenticated:
                final_message = auth_invalid_message(error_msg)

            else:
                if isinstance(msg, dict):
                    iden = msg.get('id')
                else:
                    iden = None

                final_message = error_message(
                    iden, ERR_INVALID_FORMAT, error_msg)

        except TypeError as err:
            if wsock.closed:
                self.debug("Connection closed by client")
            else:
                _LOGGER.exception("Unexpected TypeError: %s", err)

        except ValueError as err:
            msg = "Received invalid JSON"
            value = getattr(err, 'doc', None)  # Py3.5+ only
            if value:
                msg += ': {}'.format(value)
            self.log_error(msg)
            self._writer_task.cancel()

        except CANCELLATION_ERRORS:
            self.debug("Connection cancelled")

        except asyncio.QueueFull:
            self.log_error("Client exceeded max pending messages [1]:",
                           MAX_PENDING_MSG)
            self._writer_task.cancel()

        except Exception:  # pylint: disable=broad-except
            error = "Unexpected error inside websocket API. "
            if msg is not None:
                error += str(msg)
            _LOGGER.exception(error)

        finally:
            unsub_stop()

            for unsub in self.event_listeners.values():
                unsub()

            try:
                if final_message is not None:
                    self.to_write.put_nowait(final_message)
                self.to_write.put_nowait(None)
                # Make sure all error messages are written before closing
                await self._writer_task
            except asyncio.QueueFull:
                self._writer_task.cancel()

            await wsock.close()
            self.debug("Closed connection")

        return wsock
Пример #7
0
    def handle(self):
        """Handle the websocket connection."""
        wsock = self.wsock = web.WebSocketResponse()
        yield from wsock.prepare(self.request)

        # Set up to cancel this connection when Home Assistant shuts down
        socket_task = asyncio.Task.current_task(loop=self.hass.loop)

        @callback
        def cancel_connection(event):
            """Cancel this connection."""
            socket_task.cancel()

        unsub_stop = self.hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP,
                                                cancel_connection)

        self.debug('Connected')

        msg = None
        authenticated = False

        try:
            if self.request[KEY_AUTHENTICATED]:
                authenticated = True

            else:
                self.send_message(auth_required_message())
                msg = yield from wsock.receive_json()
                msg = AUTH_MESSAGE_SCHEMA(msg)

                if validate_password(self.request, msg['api_password']):
                    authenticated = True

                else:
                    self.debug('Invalid password')
                    self.send_message(auth_invalid_message('Invalid password'))
                    return wsock

            if not authenticated:
                return wsock

            self.send_message(auth_ok_message())

            msg = yield from wsock.receive_json()

            last_id = 0

            while msg:
                self.debug('Received', msg)
                msg = BASE_COMMAND_MESSAGE_SCHEMA(msg)
                cur_id = msg['id']

                if cur_id <= last_id:
                    self.send_message(error_message(
                        cur_id, ERR_ID_REUSE,
                        'Identifier values have to increase.'))

                else:
                    handler_name = 'handle_{}'.format(msg['type'])
                    getattr(self, handler_name)(msg)

                last_id = cur_id
                msg = yield from wsock.receive_json()

        except vol.Invalid as err:
            error_msg = 'Message incorrectly formatted: '
            if msg:
                error_msg += humanize_error(msg, err)
            else:
                error_msg += str(err)

            self.log_error(error_msg)

            if not authenticated:
                self.send_message(auth_invalid_message(error_msg))

            else:
                if isinstance(msg, dict):
                    iden = msg.get('id')
                else:
                    iden = None

                self.send_message(error_message(iden, ERR_INVALID_FORMAT,
                                                error_msg))

        except TypeError as err:
            if wsock.closed:
                self.debug('Connection closed by client')
            else:
                self.log_error('Unexpected TypeError', msg)

        except ValueError as err:
            msg = 'Received invalid JSON'
            value = getattr(err, 'doc', None)  # Py3.5+ only
            if value:
                msg += ': {}'.format(value)
            self.log_error(msg)

        except asyncio.CancelledError:
            self.debug('Connection cancelled by server')

        except Exception:  # pylint: disable=broad-except
            error = 'Unexpected error inside websocket API. '
            if msg is not None:
                error += str(msg)
            _LOGGER.exception(error)

        finally:
            unsub_stop()

            for unsub in list(self.event_listeners.values()):
                unsub()

            yield from wsock.close()
            self.debug('Closed connection')

        return wsock