Commit 2f4db75a authored by Karel Hanák's avatar Karel Hanák
Browse files

request jsonschema, request validation

parent 88abae2b
Pipeline #4618 failed with stages
in 21 seconds
......@@ -5,7 +5,7 @@
"port": 80
},
"mqtt": {
"cid": "IQRF-MQTT-REST-Translator",
"cid": "Topic",
"addr": "localhost",
"port": 1883,
"topic": "gateway/+/rest/requests/+/#",
......
......@@ -60,7 +60,11 @@ def main(): # pylint: disable=R1710
if mqtt_client.setup(config, rest_client) != TrRet.OK:
logger.info('Service shutting down.')
return 1
mqtt_client.connect(mqtt_client.addr, mqtt_client.port)
try:
mqtt_client.connect(mqtt_client.addr, mqtt_client.port)
except ConnectionRefusedError as connError:
logger.error(connError.strerror + '[' + str(connError.errno) + ']')
return 1
mqtt_client.loop_forever()
......
......@@ -17,8 +17,11 @@ limitations under the License.
import json
import logging
import paho.mqtt.client as mqtt
import pathlib
from jsonschema import Draft4Validator
from translator.utils.tr_ret import TrRet
path = pathlib.Path(__file__).parent.parent
class CMqtt(mqtt.Client):
"""
......@@ -64,6 +67,16 @@ class CMqtt(mqtt.Client):
topic = ''
last_topic = ''
def _load_schema(self):
"""
Loads request schema and stores validator
@return Validator request json validator
"""
with open(path / 'utils/schema_request.json') as file:
schema_data = json.load(file)
return Draft4Validator(schema_data)
def _validate_request(self, request):
"""
Validates message published on MQTT subscribe topic against request json schema.
......@@ -71,42 +84,19 @@ class CMqtt(mqtt.Client):
If the request is not valid, the method publishes error message.
@param request MQTTMessage object representing the published message
@return True if the request is valid, False otherwise
@return TrRet.OK if valid, TrRet.SchemaErr otherwise
"""
self.logger.info(request)
if request.payload:
try:
payload = json.loads(request.payload.decode('utf-8'))
except json.JSONDecodeError as json_error:
self.logger.error(json_error.msg + ' at line ' + str(json_error.lineno) +
', column ' + str(json_error.colno) + ' on\n' + json_error.doc)
self.last_topic = request.topic.replace('requests', 'responses')
self.publish(self.last_topic, 'Invalid request format: ' +
json_error.msg + ' at line ' + str(json_error.lineno) +
', column ' + str(json_error.colno) + ' on\n' + json_error.doc, 0, False)
return TrRet.JSONErr
if 'headers' not in payload:
self.logger.error('Request payload does not contain the headers element. On:\n%s',
json.dumps(payload, indent=2))
self.last_topic = request.topic.replace('requests', 'responses')
self.publish(self.last_topic, 'The request format is not valid, check log for details.', 0, False)
return TrRet.SchemaErr
if 'Content-Type' in payload['headers']:
if 'body' not in payload:
self.logger.error('Request payload contains the Content-Type header, '
'but does not contain the body element. On:\n%s', json.dumps(payload, indent=2))
self.last_topic = request.topic.replace('requests', 'responses')
self.publish(self.last_topic, 'The request format is not valid, check log for details.', 0, False)
return TrRet.SchemaErr
if 'body' in payload:
if 'Content-Type' not in payload['headers']:
self.logger.error('Request payload contains the body element, but does not contain '
'the Content-Type header. On:\n%s', json.dumps(payload, indent=2))
self.last_topic = request.topic.replace('requests', 'responses')
self.publish(self.last_topic, 'The request format is not valid, check log for details.', 0, False)
return TrRet.SchemaErr
return TrRet.OK
schema_errors = sorted(self.validator.iter_errors(request.payload), key=str)
if schema_errors:
errorString = ''
for error in schema_errors:
self.logger.error(error)
errorString += error.message
return TrRet.SchemaErr, errorString
return TrRet.OK, None
def on_connect(self, client, userdata, flags, rc): # pylint: disable=W0221 W0236 W0613
"""
......@@ -135,7 +125,11 @@ class CMqtt(mqtt.Client):
"""
self.logger.info('MQTT broker published on ' + msg.topic + ':\n' + msg.payload.decode('utf-8'))
if self._validate_request(msg) == TrRet.OK:
pub_topic = msg.topic.replace("requests", "responses")
self.last_topic = pub_topic
ret, errors = self._validate_request(msg)
if ret == TrRet.OK:
topic_tokens = msg.topic.split('/')
method = topic_tokens[4]
path = ''
......@@ -144,8 +138,6 @@ class CMqtt(mqtt.Client):
path += '/' + topic_tokens[i]
payload = msg.payload.decode('utf-8')
pub_topic = msg.topic.replace("requests", "responses")
self.last_topic = pub_topic
try:
if method == 'delete':
......@@ -162,6 +154,8 @@ class CMqtt(mqtt.Client):
self.logger.info(conn_err.strerror)
self.publish(self.last_topic, 'Could not establish connection with the REST API,'
' check logs for more details.', 0, False)
else:
self.publish(self.last_topic, errors, 0, False)
def on_publish(self, client, userdata, mid): # pylint: disable=W0221 W0236 W0613
"""
......@@ -202,4 +196,13 @@ class CMqtt(mqtt.Client):
self.topic = config['mqtt']['topic']
self.username_pw_set(config['mqtt']['user'], config['mqtt']['pw'])
self.enable_logger(logger=self.logger)
return TrRet.OK
try:
self.validator = self._load_schema()
return TrRet.OK
except IOError as io_error:
self.logger.error('%s on file %s', io_error.strerror, io_error.filename)
return TrRet.IOErr
except json.JSONDecodeError as json_error:
self.logger.error(json_error.msg + ' at line ' + str(json_error.lineno) +
', column ' + str(json_error.colno) + ' on\n' + json_error.doc)
return TrRet.JSONErr
{
"$schema": "http://json-schema.org/draft-04/schema",
"$id": "",
"type": "object",
"title": "The root schema",
"description": "Schema of the Translator valid request.",
"default": {},
"examples": [
{
"headers": {
"Content-Type": "application/json"
},
"body": "{}"
}
],
"required": [
"headers",
"body"
],
"properties": {
"headers": {
"$id": "#/properties/headers",
"type": "object",
"title": "Request headers",
"description": "Object containing headers of a request.",
"default": {},
"examples": [
{
"Content-Type": "application/json"
}
],
"properties": {
"Content-Type": {
"$id": "#/properties/headers/properties/Content-Type",
"type": "string",
"title": "Content-Type header",
"description": "Content-Type header requiring body property.",
"default": "",
"examples": [
"application/json"
]
}
},
"additionalProperties": true
},
"body": {
"$id": "#/properties/body",
"anyOf": [
{
"type": "object"
},
{
"type": "string"
}
],
"title": "Body property",
"description": "Body property containing payload for the REST.",
"default": "",
"examples": [
"{}"
]
}
},
"additionalProperties": true
}
\ No newline at end of file
"$schema": "http://json-schema.org/draft-04/schema#",
"$id": "iqrf-gw-translator-request",
"type": "object",
"title": "Request schema",
"oneOf": [
{
"properties": {
"headers": {
"$id": "#/properties/headers",
"type": "object",
"title": "Request headers",
"not": {
"required": [
"Content-Type"
]
},
"additionalProperties": true
}
},
"required": [
"headers"
],
"additionalProperties": false
},
{
"properties": {
"headers": {
"$id": "#/properties/headers",
"type": "object",
"title": "Request headers",
"properties": {
"Content-Type": {
"$id": "#/properties/headers/properties/Content-Type",
"type": "string",
"title": "Content-Type request header",
"example": "application/json"
}
},
"required": [
"Content-Type"
],
"additionalProperties": true
},
"body": {
"$id": "#/properties/body",
"type": "string",
"title": "Request body",
"example": "Example body"
}
},
"required": [
"headers",
"body"
],
"additionalProperties": false
}
]
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment