Exemplo n.º 1
0
    def delete_device(req, device_id):
        """
        Deletes a single device.

        :param req: The received HTTP request, as created by Flask.
        :param device_id: The device to be removed.
        :return The removed device.
        :rtype JSON
        :raises HTTPRequestError: If no authorization token was provided (no
        tenant was informed)
        :raises HTTPRequestError: If this device could not be found in
        database.
        """

        tenant = init_tenant_context(req, db)
        orm_device = assert_device_exists(device_id)
        data = serialize_full_device(orm_device, tenant)

        kafka_handler = KafkaHandler()
        kafka_handler.remove(data, meta={"service": tenant})
        if CONFIG.orion:
            subscription_handler = PersistenceHandler(service=tenant)
            subscription_handler.remove(orm_device.persistence)

        db.session.delete(orm_device)
        db.session.commit()

        results = {'result': 'ok', 'removed_device': data}
        return results
Exemplo n.º 2
0
 def notifies_deletion_to_kafka(device, tenant):
     data = serialize_full_device(device, tenant)
     kafka_handler = KafkaHandler()
     kafka_handler.remove(data, meta={"service": tenant})
     if CONFIG.orion:
         subscription_handler = PersistenceHandler(service=tenant)
         subscription_handler.remove(device.persistence)
Exemplo n.º 3
0
    def configure_device(req, device_id):
        """
        Send actuation commands to the device

        :param req: The received HTTP request, as created by Flask.
        :param device_id: The device which should receive the actuation message
        :return A status on whether the message was sent to the device or not.
        Note that this is not a guarantee that the device actually performed
        what was requested.
        :rtype JSON
        """
        # Meta information to be published along with device actuation message
        meta = {'service': ''}
        kafka_handler = KafkaHandler()
        invalid_attrs = []

        meta['service'] = init_tenant_context(req, db)

        meta['timestamp'] = int(time.time() * 1000)

        orm_device = assert_device_exists(device_id)
        full_device = serialize_full_device(orm_device, meta['service'])
        LOGGER.debug(f" Full device: {json.dumps(full_device)}")

        payload = json.loads(req.data)
        LOGGER.debug(f' Parsed request payload: {json.dumps(payload)}')

        payload['id'] = orm_device.id

        for attr in payload['attrs']:
            if find_attribute(full_device, attr, 'actuator') is None:
                invalid_attrs.append(attr)

        if not invalid_attrs:
            LOGGER.debug(f' Sending configuration message through Kafka.')
            kafka_handler.configure(payload, meta)
            LOGGER.debug(f' Configuration sent.')
            result = {f' status': 'configuration sent to device'}
        else:
            LOGGER.warning(
                f' invalid attributes detected in command: {invalid_attrs}')
            result = {
                'status': 'some of the attributes are not configurable',
                'attrs': invalid_attrs
            }
            raise HTTPRequestError(403, result)

        return result
Exemplo n.º 4
0
    def test_configure_event(self, kafka_instance_mock, kafka_flush):

        device = {
            'templates': [369],
            'label': 'test_device',
            'id': 1,
            'created': '2019-08-29T18:18:07.801602+00:00'
        }

        KafkaHandler().configure(device, meta={"service": 'admin'})
        self.assertTrue(kafka_flush.called)
Exemplo n.º 5
0
    def test_notifies_deletion_to_kafka(self, kafka_instance_mock,
                                        kafka_flush):
        with patch('DeviceManager.ImportHandler.serialize_full_device'
                   ) as mock_serialize_device_wrapper:
            mock_serialize_device_wrapper.return_value = {
                'templates': [369],
                'label': 'test_device',
                'id': 1,
                'created': '2019-08-29T18:18:07.801602+00:00'
            }

            with patch.object(KafkaInstanceHandler,
                              "getInstance",
                              return_value=KafkaHandler()):
                ImportHandler().notifies_deletion_to_kafka(
                    'test_device', 'admin')
                self.assertTrue(kafka_flush.called)
Exemplo n.º 6
0
    def update_device(req, device_id):
        """
        Updated the information about a particular device

        :param req: The received HTTP request, as created by Flask.
        :param device_id: The device to be updated.
        :return The updated device.
        :rtype JSON
        :raises HTTPRequestError: If no authorization token was provided (no
        tenant was informed)
        :raises HTTPRequestError: If this device could not be found in
        database.
        """
        device_data, json_payload = parse_payload(req, device_schema)

        tenant = init_tenant_context(req, db)
        old_orm_device = assert_device_exists(device_id)
        db.session.delete(old_orm_device)
        db.session.flush()

        # handled separately by parse_template_list
        device_data.pop('templates')
        updated_orm_device = Device(**device_data)
        parse_template_list(json_payload.get('templates', []),
                            updated_orm_device)
        auto_create_template(json_payload, updated_orm_device)
        updated_orm_device.id = device_id
        updated_orm_device.updated = datetime.now()
        updated_orm_device.created = old_orm_device.created

        db.session.add(updated_orm_device)

        full_device = serialize_full_device(updated_orm_device, tenant)

        if CONFIG.orion:
            # Create subscription pointing to history service
            # (STH, logstash based persister)
            subs_handler = PersistenceHandler(service=tenant)
            subs_handler.remove(old_orm_device.persistence)
            # Generating 'device type' field for subscription request
            type_descr = "template"
            for dev_type in full_device['attrs'].keys():
                type_descr += "_" + str(dev_type)
            updated_orm_device.persistence = subs_handler.create(
                device_id, type_descr)

            ctx_broker_handler = OrionHandler(service=tenant)
            ctx_broker_handler.update(
                serialize_full_device(old_orm_device, tenant), type_descr)

        kafka_handler = KafkaHandler()
        kafka_handler.update(full_device, meta={"service": tenant})

        try:
            db.session.commit()
        except IntegrityError as error:
            handle_consistency_exception(error)

        result = {
            'message': 'device updated',
            'device': serialize_full_device(updated_orm_device, tenant)
        }
        return result
Exemplo n.º 7
0
    def create_device(req):
        """
        Creates and configures the given device.

        :param req: The received HTTP request, as created by Flask.
        :return The created device or a list of device summary. This depends on
        which value the verbose (/?verbose=true) has - if true, only one device
        can be created ("count" argument can't be used or - at least - it must
        be exactly "1") and the full device is returned. If false, "count" can
        be used with higher values and only the devices summary (a structure
        containing all device IDs and their labels) is returned.
        :raises HTTPRequestError: If no authorization token was provided (no
        tenant was informed)
        :raises HTTPRequestError: If verbose is used with multiple device
        creation request.
        :raises HTTPRequestError: If count argument ("?count=X") is provided
        and it is not an integer.

        """

        tenant = init_tenant_context(req, db)
        try:
            count = int(req.args.get('count', '1'))
        except ValueError as e:
            LOGGER.error(e)
            raise HTTPRequestError(400, "If provided, count must be integer")

        c_length = len(str(count))
        verbose = req.args.get('verbose', 'false') in ['true', '1', 'True']
        if verbose and count != 1:
            raise HTTPRequestError(
                400, "Verbose can only be used for single device creation")

        devices = []

        # Handlers
        kafka_handler = KafkaHandler()
        if CONFIG.orion:
            ctx_broker_handler = OrionHandler(service=tenant)
            subs_handler = PersistenceHandler(service=tenant)
        else:
            ctx_broker_handler = None
            subs_handler = None

        full_device = None

        for i in range(0, count):
            device_data, json_payload = parse_payload(req, device_schema)
            device_data['id'] = DeviceHandler.generate_device_id()
            device_data['label'] = DeviceHandler.indexed_label(
                count, c_length, device_data['label'], i)
            # handled separately by parse_template_list
            device_data.pop('templates', None)
            orm_device = Device(**device_data)
            parse_template_list(json_payload.get('templates', []), orm_device)
            auto_create_template(json_payload, orm_device)
            db.session.add(orm_device)

            devices.append({
                'id': device_data['id'],
                'label': device_data['label']
            })

            full_device = serialize_full_device(orm_device, tenant)

            # Updating handlers
            kafka_handler.create(full_device, meta={"service": tenant})
            if CONFIG.orion:
                # Generating 'device type' field for history
                type_descr = "template"
                for dev_type in full_device['attrs'].keys():
                    type_descr += "_" + str(dev_type)
                # TODO remove this in favor of kafka as data broker....
                ctx_broker_handler.create(full_device, type_descr)
                sub_id = subs_handler.create(full_device['id'], type_descr)
                orm_device.persistence = sub_id

        try:
            db.session.commit()
        except IntegrityError as error:
            handle_consistency_exception(error)

        if verbose:
            result = {'message': 'device created', 'device': full_device}
        else:
            result = {'message': 'devices created', 'devices': devices}
        return result
    def update_template(req, template_id):
        """
        Updates a single template.

        :param req: The received HTTP request, as created by Flask.
        :param template_id: The template to be updated.
        :return The old version of this template (previous to the update).
        :rtype JSON
        :raises HTTPRequestError: If no authorization token was provided (no
        tenant was informed)
        :raises HTTPRequestError: If this template could not be found in
        database.
        """
        service = init_tenant_context(req, db)

        # find old version of the template, if any
        old = assert_template_exists(template_id)
        # parse updated version from payload
        updated, json_payload = parse_payload(req, template_schema)

        LOGGER.debug(f" Current json payload: {json_payload}")

        old.label = updated['label']

        new = json_payload['attrs']
        LOGGER.debug(f" Checking old template attributes")

        def attrs_match(attr_from_db, attr_from_request):
            return ((attr_from_db.label == attr_from_request["label"])
                    and (attr_from_db.type == attr_from_request["type"]))

        def update_attr(attrs_from_db, attrs_from_request):
            attrs_from_db.value_type = attrs_from_request.get(
                'value_type', None)
            attrs_from_db.static_value = attrs_from_request.get(
                'static_value', None)

        def validate_attr(attr_from_request, is_meta):
            if is_meta is False:
                attr_schema.load(attr_from_request)
            else:
                metaattr_schema.load(attr_from_request)

        def analyze_attrs(attrs_from_db, attrs_from_request, parentAttr=None):
            for attr_from_db in attrs_from_db:
                found = False
                for idx, attr_from_request in enumerate(attrs_from_request):
                    validate_attr(attr_from_request, parentAttr is not None)
                    if attrs_match(attr_from_db, attr_from_request):
                        update_attr(attr_from_db, attr_from_request)
                        if "metadata" in attr_from_request:
                            analyze_attrs(attr_from_db.children,
                                          attr_from_request["metadata"],
                                          attr_from_db)
                        attrs_from_request.pop(idx)
                        found = True
                        break
                if not found:
                    LOGGER.debug(f" Removing attribute {attr_from_db.label}")
                    db.session.delete(attr_from_db)
            if parentAttr and attrs_from_request is not None:
                for attr_from_request in attrs_from_request:
                    orm_child = DeviceAttr(parent=parentAttr,
                                           **attr_from_request)
                    db.session.add(orm_child)
            return attrs_from_request

        to_be_added = analyze_attrs(old.attrs, new)
        for attr in to_be_added:
            LOGGER.debug(f" Adding new attribute {attr}")
            if "id" in attr:
                del attr["id"]
            child = DeviceAttr(template=old, **attr)
            db.session.add(child)
            if "metadata" in attr and attr["metadata"] is not None:
                for metadata in attr["metadata"]:
                    LOGGER.debug(f" Adding new metadata {metadata}")
                    orm_child = DeviceAttr(parent=child, **metadata)
                    db.session.add(orm_child)
        try:
            LOGGER.debug(f" Commiting new data...")
            db.session.commit()
            LOGGER.debug("... data committed.")
        except IntegrityError as error:
            LOGGER.debug(f"  ConsistencyException was thrown.")
            handle_consistency_exception(error)

        # notify interested parties that a set of devices might have been implicitly updated
        affected = db.session.query(DeviceTemplateMap) \
                             .filter(DeviceTemplateMap.template_id==template_id) \
                             .all()

        affected_devices = []
        kafka_handler = KafkaHandler()
        for device in affected:
            orm_device = assert_device_exists(device.device_id)
            kafka_handler.update(serialize_full_device(orm_device, service),
                                 meta={"service": service})
            affected_devices.append(device.device_id)

        event = {
            "event": DeviceEvent.TEMPLATE,
            "data": {
                "affected": affected_devices,
                "template": template_schema.dump(old)
            },
            "meta": {
                "service": service
            }
        }
        send_raw(event, service)

        results = {'updated': template_schema.dump(old), 'result': 'ok'}
        return results
Exemplo n.º 9
0
    def copy_psk(token, src_device_id, src_attr, dest_device_id, dest_attr):
        """
        Copies a pre shared key from a device attribute to another

        :param token: The authorization token (JWT).
        :param src_device_id: The source device (from).
        :param src_attr: The source attribute (from).
        :param dest_device_id: The destination device (to).
        :param dest_attr: The destination attribute (to).
        :return None.
        :raises HTTPRequestError: If no authorization token was provided (no
        tenant was informed)
        todo list the others exceptions
        """

        tenant = init_tenant_context2(token, db)

        src_device_orm = assert_device_exists(src_device_id, db.session)
        if not src_device_orm:
            raise HTTPRequestError(404, "No such device: {}".format(src_device_id))

        src_device = serialize_full_device(src_device_orm, tenant, True)

        found_attr = False
        src_attr_ref = None
        for template_id in src_device["templates"]:
            for attr in src_device["attrs"][template_id]:
                if attr["label"] == src_attr:
                    if attr["value_type"] == "psk":
                        found_attr = True
                        src_attr_ref = attr
                        break
                    else:
                        raise HTTPRequestError(400,
                                "Attribute {} is not a 'psk' type_value".format(src_attr))

        if not found_attr:
            raise HTTPRequestError(404, "Not found attributes {}".format(src_attr))

        dest_device_orm = assert_device_exists(dest_device_id, db.session)
        if not dest_device_orm:
            raise HTTPRequestError(404, "No such device: {}".format(dest_device_id))

        dest_device = serialize_full_device(dest_device_orm, tenant, True)

        found_attr = False
        dest_attr_ref = None
        for template_id in dest_device["templates"]:
            for attr in dest_device["attrs"][template_id]:
                if attr["label"] == dest_attr:
                    if attr["value_type"] == "psk":
                        found_attr = True
                        dest_attr_ref = attr
                        break
                    else:
                        raise HTTPRequestError(400,
                                "Attribute {} is not a 'psk' type_value".format(dest_attr))

        if not found_attr:
            raise HTTPRequestError(404, "Not found attributes {}".format(dest_attr))

        # copy the pre shared key
        src_psk_entry = DeviceAttrsPsk.query.filter_by(device_id=src_device["id"],
                                                    attr_id=src_attr_ref["id"]).first()
        if not src_psk_entry:
            raise HTTPRequestError(400, "There is not a psk generated to {}".format(src_attr))

        dest_psk_entry = DeviceAttrsPsk.query.filter_by(device_id=dest_device["id"],
            attr_id=dest_attr_ref["id"]).first()
        if not dest_psk_entry:
            db.session.add(DeviceAttrsPsk(device_id=dest_device["id"],
                                            attr_id=dest_attr_ref["id"],
                                            psk=src_psk_entry.psk))
        else:
            dest_psk_entry.psk = src_psk_entry.psk

        dest_device_orm.updated = datetime.now()
        db.session.commit()

        dest_attr_ref['static_value'] = src_attr_ref['static_value']

        # send an update message on kafka
        kafka_handler = KafkaHandler()
        kafka_handler.update(dest_device, meta={"service": tenant})

        return None
Exemplo n.º 10
0
    def gen_psk(token, device_id, key_length, target_attributes=None):
        """
        Generates pre shared keys to a specifics device

        :param token: The authorization token (JWT).
        :param device_id: The target device.
        :param key_length: The key length to be generated.
        :param target_attributes: A list with the target attributes, None means
        all suitable attributes.
        :return The keys generated.
        :rtype JSON
        :raises HTTPRequestError: If no authorization token was provided (no
        tenant was informed)
        :raises HTTPRequestError: If this device could not be found in
        database.
        """

        tenant = init_tenant_context2(token, db)

        device_orm = assert_device_exists(device_id, db.session)
        if not device_orm:
            raise HTTPRequestError(404, "No such device: {}".format(device_id))

        device = serialize_full_device(device_orm, tenant, True)

        # checks if the key length has been specified
        # todo remove this magic number
        if key_length > 1024 or key_length <= 0:
            raise HTTPRequestError(400, "key_length must be greater than 0 and lesser than {}".format(1024))

        is_all_psk_attr_valid = False
        target_attrs_data = []

        # find the target attributes
        # first case: if there are specified attributes
        if target_attributes:
            for template_id in device["templates"]:
                for attr in device["attrs"][template_id]:
                    if attr["value_type"] == "psk" and attr["label"] in target_attributes:
                        target_attrs_data.append(attr)
                        is_all_psk_attr_valid = True

            if not is_all_psk_attr_valid:
                raise HTTPRequestError(400, "Not found some attributes, "
                    "please check them")

            if len(target_attributes) != len(target_attrs_data):
                if not is_all_psk_attr_valid:
                    raise HTTPRequestError(400,
                                    "Some attribute is not a 'psk' type_value")
                else:
                    raise HTTPRequestError(400, "Not found some attributes, "
                        "please check them")
        else: # second case: if there are not specified attributes
            for template_id in device["templates"]:
                for attr in device["attrs"][template_id]:
                    if attr["value_type"] == "psk":
                        target_attrs_data.append(attr)
                        is_all_psk_attr_valid = True

            if not is_all_psk_attr_valid:
                # there is no psk key, do not worry it is not a problem
                raise HTTPRequestError(204, "")

        # generate the pre shared key on selected attributes
        result = []
        for attr in target_attrs_data:
            psk = secrets.token_hex(key_length)
            psk_hex = psk
            attr["static_value"] = psk_hex
            encrypted_psk = encrypt(psk)
            psk_entry = DeviceAttrsPsk.query.filter_by(device_id=device["id"],
                                                       attr_id=attr["id"]).first()
            if not psk_entry:
                db.session.add(DeviceAttrsPsk(device_id=device["id"],
                                              attr_id=attr["id"],
                                              psk=encrypted_psk))
            else:
                psk_entry.psk = encrypted_psk

            result.append( {'attribute': attr["label"], 'psk': psk_hex} )

        device_orm.updated = datetime.now()
        db.session.commit()

        # send an update message on kafka
        kafka_handler = KafkaHandler()
        kafka_handler.update(device, meta={"service": tenant})

        return result
Exemplo n.º 11
0
 def notifies_creation_to_kafka(saved_devices, tenant):
     kafka_handler = KafkaHandler()
     for orm_device in saved_devices:
         full_device = serialize_full_device(orm_device, tenant)
         kafka_handler.create(full_device, meta={"service": tenant})