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
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
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
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
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
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
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
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)
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
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