Beispiel #1
0
    def add_template_to_device(req, device_id, template_id):
        """
        Associates given template with device

        :param req: The received HTTP request, as created by Flask.
        :param device_id: The device which should be updated
        :param template_id: The template to be added to this device.
        :raises HTTPRequestError: If no authorization token was provided (no
        tenant was informed)
        :raises HTTPRequestError: If this device or template could not be found
        in database.
        :return A status on whether the device was updated, and the new
        structure for that device.
        :rtype JSON
        """
        tenant = init_tenant_context(req, db)
        orm_device = assert_device_exists(device_id)
        orm_template = assert_template_exists(template_id)

        orm_device.templates.append(orm_template)

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

        result = {
            'message': 'device updated',
            'device': serialize_full_device(orm_device, tenant)
        }

        return result
Beispiel #2
0
    def remove_template_from_device(req, device_id, template_id):
        """
        Disassociates given template with device

        :param req: The received HTTP request, as created by Flask.
        :param device_id: The device which should be updated
        :param template_id: The template to be removed from this device.
        :raises HTTPRequestError: If no authorization token was provided (no
        tenant was informed)
        :raises HTTPRequestError: If this device or template could not be found
        in database.
        :return A status on whether the device was updated, and the new
        structure for that device.
        :rtype JSON
        """
        tenant = init_tenant_context(req, db)
        updated_device = assert_device_exists(device_id)
        relation = assert_device_relation_exists(device_id, template_id)

        # Here (for now) there are no more validations to perform, as template
        # removal cannot violate attribute constraints

        db.session.remove(relation)
        db.session.commit()
        result = {
            'message': 'device updated',
            'device': serialize_full_device(updated_device, tenant)
        }

        return result
Beispiel #3
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
Beispiel #4
0
    def delete_device(cls, device_id, token):
        """
        Deletes a single device.

        :param device_id: The device to be removed.
        :param token: The authorization token (JWT).
        :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(token, db)
        orm_device = assert_device_exists(device_id)
        data = serialize_full_device(orm_device, tenant)

        kafka_handler_instance = cls.kafka.getInstance(cls.kafka.kafkaNotifier)
        kafka_handler_instance.remove(data, meta={"service": tenant})

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

        results = {'result': 'ok', 'removed_device': data}
        return results
Beispiel #5
0
    def update_device(cls, params, device_id, token):
        """
        Updated the information about a particular device

        :param params: Parameters received from request (content_type, data)
        as created by Flask
        :param device_id: The device to be updated.
        :param token: The authorization token (JWT).
        :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.
        """
        try:
            content_type = params.get('content_type')
            data_request = params.get('data')

            device_data, json_payload = parse_payload(content_type,
                                                      data_request,
                                                      device_schema)
            validate_repeated_attrs(json_payload)

            tenant = init_tenant_context(token, 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)

            db.session.commit()
        except IntegrityError as error:
            handle_consistency_exception(error)
        except ValidationError as error:
            raise HTTPRequestError(400, error.messages)

        full_device = serialize_full_device(updated_orm_device, tenant)

        kafka_handler_instance = cls.kafka.getInstance(cls.kafka.kafkaNotifier)
        kafka_handler_instance.update(full_device, meta={"service": tenant})

        result = {
            'message': 'device updated',
            'device': serialize_full_device(updated_orm_device, tenant)
        }
        return result
Beispiel #6
0
    def configure_device(cls, params, device_id, token):
        """
        Send actuation commands to the device

        :param params: Parameters received from request (data)
        as created by Flask
        :param device_id: The device which should receive the actuation message
        :param token: The authorization token (JWT).
        :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': ''}
        invalid_attrs = []

        meta['service'] = init_tenant_context(token, 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)}")

        data = params.get('data')
        payload = json.loads(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_instance = cls.kafka.getInstance(
                cls.kafka.kafkaNotifier)
            kafka_handler_instance.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
Beispiel #7
0
    def get_device(req, device_id):
        """
        Fetches a single device.

        :param req: The received HTTP request, as created by Flask.
        :param device_id: The requested device.
        :return A Device
        :rtype Device, as described in DatabaseModels package
        :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)
        return serialize_full_device(orm_device, tenant)
    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)

        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
            }

        return result
Beispiel #9
0
    def get_device(token, device_id, sensitive_data=False):
        """
        Fetches a single device.

        :param token: The authorization token (JWT).
        :param device_id: The requested device.
        :param sensitive_data: Informs if sensitive data like keys should be
        returned
        :return A Device
        :rtype Device, as described in DatabaseModels package
        :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(token, db)
        orm_device = assert_device_exists(device_id)
        return serialize_full_device(orm_device, tenant, sensitive_data)
Beispiel #10
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
Beispiel #11
0
    def update_template(cls, params, template_id, token):
        """
        Updates a single template.

        :param params: Parameters received from request (content_type, data)
        as created by Flask
        :param template_id: The template to be updated.
        :param token: The authorization token (JWT).
        :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(token, db)

        content_type = params.get('content_type')
        data_request = params.get('data')

        # find old version of the template, if any
        old = assert_template_exists(template_id)
        # parse updated version from payload
        updated, json_payload = parse_payload(content_type, data_request, 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...")
            refresh_template_update_column(db, old)
            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_instance = cls.kafka.getInstance(cls.kafka.kafkaNotifier)
        for device in affected:
            orm_device = assert_device_exists(device.device_id)
            kafka_handler_instance.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}
        }
        kafka_handler_instance.kafkaNotifier.send_raw(event, service)

        results = {
            'updated': template_schema.dump(old),
            'result': 'ok'
        }
        return results
    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
    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