Esempio n. 1
0
    def test_requests_have_host_header(self):
        """
        The tado internet bridge will fail if a secure session request
        doesn't have a Host header.

        https://github.com/home-assistant/home-assistant/issues/16971
        https://github.com/jlusiardi/homekit_python/pull/130
        """

        session = mock.Mock()
        session.pairing_data = {
            'AccessoryIP': '192.168.1.2',
            'AccessoryPort': 8080,
        }
        secure_http = SecureHttp(session)

        with mock.patch.object(secure_http, '_handle_request') as handle_req:
            secure_http.get('/characteristics')
            assert '\r\nHost: 192.168.1.2:8080\r\n' in handle_req.call_args[0][
                0].decode()

            secure_http.post('/characteristics', b'')
            assert '\r\nHost: 192.168.1.2:8080\r\n' in handle_req.call_args[0][
                0].decode()

            secure_http.put('/characteristics', b'')
            assert '\r\nHost: 192.168.1.2:8080\r\n' in handle_req.call_args[0][
                0].decode()
Esempio n. 2
0
class IpSession(object):
    def __init__(self, pairing_data):
        """

        :param pairing_data:
        :raises AccessoryNotFoundError: if the device can not be found via zeroconf
        """
        logging.debug('init session')
        connected = False
        if 'AccessoryIP' in pairing_data and 'AccessoryPort' in pairing_data:
            # if it is known, try it
            accessory_ip = pairing_data['AccessoryIP']
            accessory_port = pairing_data['AccessoryPort']
            conn = HomeKitHTTPConnection(accessory_ip, port=accessory_port)
            try:
                conn.connect()
                write_fun = create_ip_pair_verify_write(conn)
                c2a_key, a2c_key = get_session_keys(conn, pairing_data,
                                                    write_fun)
                connected = True
            except Exception:
                connected = False
        if not connected:
            # no connection yet, so ip / port might have changed and we need to fall back to slow zeroconf lookup
            device_id = pairing_data['AccessoryPairingID']
            connection_data = find_device_ip_and_port(device_id)
            if connection_data is None:
                raise AccessoryNotFoundError('Device {id} not found'.format(
                    id=pairing_data['AccessoryPairingID']))
            conn = HomeKitHTTPConnection(connection_data['ip'],
                                         port=connection_data['port'])
            pairing_data['AccessoryIP'] = connection_data['ip']
            pairing_data['AccessoryPort'] = connection_data['port']
            write_fun = create_ip_pair_verify_write(conn)
            c2a_key, a2c_key = get_session_keys(conn, pairing_data, write_fun)

        logging.debug('session established')
        self.sock = conn.sock
        self.c2a_key = c2a_key
        self.a2c_key = a2c_key
        self.pairing_data = pairing_data
        self.sec_http = SecureHttp(self)

    def close(self):
        """
        Close the session. This closes the socket.
        """
        try:
            self.sock.close()
        except OSError:
            # If we get an OSError its probably because the socket is already closed
            pass
        self.sock = None

    def get_from_pairing_data(self, key):
        if key not in self.pairing_data:
            return None
        return self.pairing_data[key]

    def set_in_pairing_data(self, key, value):
        self.pairing_data[key] = value

    def get(self, url):
        """
        Perform HTTP get via the encrypted session.
        :param url: The url to request
        :return: a homekit.http_impl.HttpResponse object
        """
        return self.sec_http.get(url)

    def put(self, url, body, content_type=HttpContentTypes.JSON):
        """
        Perform HTTP put via the encrypted session.
        :param url: The url to request
        :param body: the body of the put request
        :param content_type: the content of the content-type header
        :return: a homekit.http_impl.HttpResponse object
        """
        return self.sec_http.put(url, body, content_type)

    def post(self, url, body, content_type=HttpContentTypes.JSON):
        """
        Perform HTTP post via the encrypted session.
        :param url: The url to request
        :param body: the body of the post request
        :param content_type: the content of the content-type header
        :return: a homekit.http_impl.HttpResponse object
        """
        return self.sec_http.post(url, body, content_type)
Esempio n. 3
0
class IpSession(object):
    def __init__(self, pairing_data):
        """

        :param pairing_data:
        :raises AccessoryNotFoundError: if the device can not be found via zeroconf
        """
        logging.debug('init session')
        connected = False
        self.pairing_data = pairing_data

        if 'AccessoryIP' in pairing_data and 'AccessoryPort' in pairing_data:
            # if it is known, try it
            accessory_ip = pairing_data['AccessoryIP']
            accessory_port = pairing_data['AccessoryPort']
            connected = self._connect(accessory_ip, accessory_port)

        if not connected:
            # no connection yet, so ip / port might have changed and we need to fall back to slow zeroconf lookup
            device_id = pairing_data['AccessoryPairingID']
            connection_data = find_device_ip_and_port(device_id)

            # update pairing data with the IP/port we elaborated above, perhaps next time they are valid
            pairing_data['AccessoryIP'] = connection_data['ip']
            pairing_data['AccessoryPort'] = connection_data['port']

            if connection_data is None:
                raise AccessoryNotFoundError('Device {id} not found'.format(
                    id=pairing_data['AccessoryPairingID']))

            if not self._connect(connection_data['ip'],
                                 connection_data['port']):
                return

        logging.debug('session established')

        self.sec_http = SecureHttp(self)

    def _connect(self, accessory_ip, accessory_port):
        conn = HomeKitHTTPConnection(accessory_ip, port=accessory_port)
        try:
            conn.connect()
            write_fun = create_ip_pair_verify_write(conn)

            state_machine = get_session_keys(self.pairing_data)

            request, expected = state_machine.send(None)
            while True:
                try:
                    response = write_fun(request, expected)
                    request, expected = state_machine.send(response)
                except StopIteration as result:
                    self.c2a_key, self.a2c_key = result.value
                    self.sock = conn.sock
                    return True
        except OSError as e:
            logging.debug("Failed to connect to accessory: %s", e.strerror)
        except Exception:
            logging.exception("Failed to connect to accessory")

        return False

    def close(self):
        """
        Close the session. This closes the socket.
        """
        try:
            self.sock.close()
        except OSError:
            # If we get an OSError its probably because the socket is already closed
            pass
        self.sock = None

    def get_from_pairing_data(self, key):
        if key not in self.pairing_data:
            return None
        return self.pairing_data[key]

    def set_in_pairing_data(self, key, value):
        self.pairing_data[key] = value

    def get(self, url):
        """
        Perform HTTP get via the encrypted session.
        :param url: The url to request
        :return: a homekit.http_impl.HttpResponse object
        """
        return self.sec_http.get(url)

    def put(self, url, body, content_type=HttpContentTypes.JSON):
        """
        Perform HTTP put via the encrypted session.
        :param url: The url to request
        :param body: the body of the put request
        :param content_type: the content of the content-type header
        :return: a homekit.http_impl.HttpResponse object
        """
        return self.sec_http.put(url, body, content_type)

    def post(self, url, body, content_type=HttpContentTypes.JSON):
        """
        Perform HTTP post via the encrypted session.
        :param url: The url to request
        :param body: the body of the post request
        :param content_type: the content of the content-type header
        :return: a homekit.http_impl.HttpResponse object
        """
        return self.sec_http.post(url, body, content_type)