Add readonly feature to JSONRPC

This commit is contained in:
Michael Zanetti 2019-11-05 23:49:01 +01:00
parent ad32ee86ce
commit 25152c5e27
6 changed files with 136 additions and 162 deletions

View File

@ -75,7 +75,7 @@ bool JsonValidator::checkRefs(const QVariantMap &map, const QVariantMap &api)
JsonValidator::Result JsonValidator::validateParams(const QVariantMap &params, const QString &method, const QVariantMap &api)
{
QVariantMap paramDefinition = api.value("methods").toMap().value(method).toMap().value("params").toMap();
m_result = validateMap(params, paramDefinition, api);
m_result = validateMap(params, paramDefinition, api, QIODevice::WriteOnly);
m_result.setWhere(method + ", param " + m_result.where());
return m_result;
}
@ -83,7 +83,7 @@ JsonValidator::Result JsonValidator::validateParams(const QVariantMap &params, c
JsonValidator::Result JsonValidator::validateReturns(const QVariantMap &returns, const QString &method, const QVariantMap &api)
{
QVariantMap returnsDefinition = api.value("methods").toMap().value(method).toMap().value("returns").toMap();
m_result = validateMap(returns, returnsDefinition, api);
m_result = validateMap(returns, returnsDefinition, api, QIODevice::ReadOnly);
m_result.setWhere(method + ", returns " + m_result.where());
return m_result;
}
@ -91,7 +91,7 @@ JsonValidator::Result JsonValidator::validateReturns(const QVariantMap &returns,
JsonValidator::Result JsonValidator::validateNotificationParams(const QVariantMap &params, const QString &notification, const QVariantMap &api)
{
QVariantMap paramDefinition = api.value("notifications").toMap().value(notification).toMap().value("params").toMap();
m_result = validateMap(params, paramDefinition, api);
m_result = validateMap(params, paramDefinition, api, QIODevice::ReadOnly);
m_result.setWhere(notification + ", param " + m_result.where());
return m_result;
}
@ -101,14 +101,21 @@ JsonValidator::Result JsonValidator::result() const
return m_result;
}
JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const QVariantMap &definition, const QVariantMap &api)
JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const QVariantMap &definition, const QVariantMap &api, QIODevice::OpenMode openMode)
{
// Make sure all required values are available
foreach (const QString &key, definition.keys()) {
if (key.startsWith("o:")) {
QRegExp isOptional = QRegExp("^([a-z]:)*o:.*");
if (isOptional.exactMatch(key)) {
continue;
}
if (!map.contains(key)) {
QRegExp isReadOnly = QRegExp("^([a-z]:)*r:.*");
if (isReadOnly.exactMatch(key) && openMode.testFlag(QIODevice::WriteOnly)) {
continue;
}
QString trimmedKey = key;
trimmedKey.remove(QRegExp("^(o:|r:)"));
if (!map.contains(trimmedKey)) {
return Result(false, "Missing required key: " + key, key);
}
}
@ -117,6 +124,15 @@ JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const Q
foreach (const QString &key, map.keys()) {
// Is the key allowed in here?
QVariant expectedValue = definition.value(key);
foreach (const QString &definitionKey, definition.keys()) {
QRegExp regExp = QRegExp("(o:|r:)*" + key);
if (regExp.exactMatch(definitionKey)) {
expectedValue = definition.value(definitionKey);
}
}
if (!expectedValue.isValid()) {
expectedValue = definition.value("o:" + key);
}
if (!expectedValue.isValid()) {
expectedValue = definition.value("o:" + key);
}
@ -127,7 +143,7 @@ JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const Q
// Validate content
QVariant value = map.value(key);
Result result = validateEntry(value, expectedValue, api);
Result result = validateEntry(value, expectedValue, api, openMode);
if (!result.success()) {
result.setWhere(key + '.' + result.where());
result.setErrorString(result.errorString());
@ -139,7 +155,7 @@ JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const Q
return Result(true);
}
JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const QVariant &definition, const QVariantMap &api)
JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const QVariant &definition, const QVariantMap &api, QIODevice::OpenMode openMode)
{
if (definition.type() == QVariant::String) {
QString expectedTypeName = definition.toString();
@ -168,7 +184,7 @@ JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const
}
QString flagEnum = refDefinition.toList().first().toString();
foreach (const QVariant &flagsEntry, value.toList()) {
Result result = validateEntry(flagsEntry, flagEnum, api);
Result result = validateEntry(flagsEntry, flagEnum, api, openMode);
if (!result.success()) {
return result;
}
@ -178,7 +194,7 @@ JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const
QVariantMap types = api.value("types").toMap();
QVariant refDefinition = types.value(refName);
return validateEntry(value, refDefinition, api);
return validateEntry(value, refDefinition, api, openMode);
}
JsonHandler::BasicType expectedBasicType = JsonHandler::enumNameToValue<JsonHandler::BasicType>(expectedTypeName);
@ -240,7 +256,7 @@ JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const
if (value.type() != QVariant::Map) {
return Result(false, "Invalid value. Expected a map bug received: " + value.toString());
}
return validateMap(value.toMap(), definition.toMap(), api);
return validateMap(value.toMap(), definition.toMap(), api, openMode);
}
if (definition.type() == QVariant::List) {
@ -250,7 +266,7 @@ JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const
return Result(false, "Expected list of " + entryDefinition.toString() + " but got value of type " + value.typeName() + "\n" + QJsonDocument::fromVariant(value).toJson());
}
foreach (const QVariant &entry, value.toList()) {
Result result = validateEntry(entry, entryDefinition, api);
Result result = validateEntry(entry, entryDefinition, api, openMode);
if (!result.success()) {
return result;
}

View File

@ -23,6 +23,7 @@
#include <QPair>
#include <QVariant>
#include <QIODevice>
namespace nymeaserver {
@ -58,8 +59,8 @@ public:
Result result() const;
private:
Result validateMap(const QVariantMap &map, const QVariantMap &definition, const QVariantMap &api);
Result validateEntry(const QVariant &value, const QVariant &definition, const QVariantMap &api);
Result validateMap(const QVariantMap &map, const QVariantMap &definition, const QVariantMap &api, QIODevice::OpenMode openMode);
Result validateEntry(const QVariant &value, const QVariant &definition, const QVariantMap &api, QIODevice::OpenMode openMode);
Result m_result;
};

View File

@ -173,6 +173,9 @@ void JsonHandler::registerObject(const QMetaObject &metaObject)
if (metaProperty.isUser()) {
name.prepend("o:");
}
if (!metaProperty.isWritable()) {
name.prepend("r:");
}
QVariant typeName;
if (metaProperty.type() == QVariant::UserType) {
if (metaProperty.typeName() == QStringLiteral("QVariant::Type")) {
@ -305,12 +308,22 @@ QVariant JsonHandler::pack(const QMetaObject &metaObject, const void *value) con
continue;
}
// Manually converting QList<int>... Only QVariantList is known to the meta system
if (propertyTypeName.startsWith("QList<int>")) {
// Manually converting QList<BasicType>... Only QVariantList is known to the meta system
if (propertyTypeName.startsWith("QList<")) {
QVariantList list;
foreach (int entry, propertyValue.value<QList<int>>()) {
list << entry;
if (propertyTypeName == "QList<int>") {
foreach (int entry, propertyValue.value<QList<int>>()) {
list << entry;
}
} else if (propertyTypeName == "QList<QUuid>") {
foreach (const QUuid &entry, propertyValue.value<QList<QUuid>>()) {
list << entry;
}
} else {
Q_ASSERT_X(false, this->metaObject()->className(), QString("Unhandled list type: %1").arg(propertyTypeName).toUtf8());
qCWarning(dcJsonRpc()) << "Cannot pack property of unhandled list type" << propertyTypeName;
}
if (!list.isEmpty() || !metaProperty.isUser()) {
ret.insert(metaProperty.name(), list);
}
@ -410,12 +423,20 @@ QVariant JsonHandler::unpack(const QMetaObject &metaObject, const QVariant &valu
continue;
}
if (metaProperty.typeName() == QStringLiteral("QList<int>")) {
QList<int> intList;
foreach (const QVariant &val, variant.toList()) {
intList.append(val.toInt());
if (QString(metaProperty.typeName()).startsWith("QList<")) {
if (metaProperty.typeName() == QStringLiteral("QList<int>")) {
QList<int> intList;
foreach (const QVariant &val, variant.toList()) {
intList.append(val.toInt());
}
metaProperty.writeOnGadget(ptr, QVariant::fromValue(intList));
} else if (metaProperty.typeName() == QStringLiteral("QList<QUuid>")) {
QList<QUuid> uuidList;
foreach (const QVariant &val, variant.toList()) {
uuidList.append(val.toUuid());
}
metaProperty.writeOnGadget(ptr, QVariant::fromValue(uuidList));
}
metaProperty.writeOnGadget(ptr, QVariant::fromValue(intList));
continue;
}

View File

@ -210,43 +210,3 @@ QDebug operator<<(QDebug dbg, const RepeatingOption &repeatingOption)
dbg.nospace() << "RepeatingOption(Mode:" << repeatingOption.mode() << ", Monthdays:" << repeatingOption.monthDays() << "Weekdays:" << repeatingOption.weekDays() << ")";
return dbg;
}
WeekDays::WeekDays()
{
}
WeekDays::WeekDays(const QList<int> &other): QList<int>(other)
{
}
QVariant WeekDays::get(int index) const
{
return at(index);
}
void WeekDays::put(const QVariant &value)
{
append(value.toInt());
}
MonthDays::MonthDays()
{
}
MonthDays::MonthDays(const QList<int> &other): QList<int>(other)
{
}
QVariant MonthDays::get(int index) const
{
return at(index);
}
void MonthDays::put(const QVariant &value)
{
append(value.toInt());
}

View File

@ -27,30 +27,6 @@
class QDateTime;
class WeekDays: public QList<int>
{
Q_GADGET
Q_PROPERTY(int count READ count)
public:
WeekDays();
WeekDays(const QList<int> &other);
Q_INVOKABLE QVariant get(int index) const;
Q_INVOKABLE void put(const QVariant &value);
};
Q_DECLARE_METATYPE(WeekDays)
class MonthDays: public QList<int>
{
Q_GADGET
Q_PROPERTY(int count READ count)
public:
MonthDays();
MonthDays(const QList<int> &other);
Q_INVOKABLE QVariant get(int index) const;
Q_INVOKABLE void put(const QVariant &value);
};
Q_DECLARE_METATYPE(MonthDays)
class RepeatingOption
{
Q_GADGET

View File

@ -1683,10 +1683,10 @@
},
"ActionType": {
"displayName": "String",
"id": "Uuid",
"index": "Int",
"name": "String",
"paramTypes": "$ref:ParamTypes"
"paramTypes": "$ref:ParamTypes",
"r:id": "Uuid"
},
"ActionTypes": [
"$ref:ActionType"
@ -1715,51 +1715,51 @@
"$ref:CalendarItem"
],
"Device": {
"deviceClassId": "Uuid",
"id": "Uuid",
"name": "String",
"o:parentId": "Uuid",
"params": "$ref:ParamList",
"r:deviceClassId": "Uuid",
"r:id": "Uuid",
"settings": "$ref:ParamList",
"setupComplete": "Bool",
"states": "$ref:States"
},
"DeviceClass": {
"actionTypes": "$ref:ActionTypes",
"browsable": "Bool",
"browserItemActionTypes": "$ref:ActionTypes",
"createMethods": "$ref:CreateMethods",
"discoveryParamTypes": "$ref:ParamTypes",
"displayName": "String",
"eventTypes": "$ref:EventTypes",
"id": "Uuid",
"interfaces": "StringList",
"name": "String",
"paramTypes": "$ref:ParamTypes",
"pluginId": "Uuid",
"settingsTypes": "$ref:ParamTypes",
"setupMethod": "$ref:SetupMethod",
"stateTypes": "$ref:StateTypes",
"vendorId": "Uuid"
"r:actionTypes": "$ref:ActionTypes",
"r:browsable": "Bool",
"r:browserItemActionTypes": "$ref:ActionTypes",
"r:createMethods": "$ref:CreateMethods",
"r:discoveryParamTypes": "$ref:ParamTypes",
"r:displayName": "String",
"r:eventTypes": "$ref:EventTypes",
"r:id": "Uuid",
"r:interfaces": "StringList",
"r:name": "String",
"r:paramTypes": "$ref:ParamTypes",
"r:pluginId": "Uuid",
"r:settingsTypes": "$ref:ParamTypes",
"r:setupMethod": "$ref:SetupMethod",
"r:stateTypes": "$ref:StateTypes",
"r:vendorId": "Uuid"
},
"DeviceClasses": [
"$ref:DeviceClass"
],
"DeviceDescriptor": {
"description": "String",
"deviceParams": "$ref:ParamList",
"id": "Uuid",
"o:deviceId": "Uuid",
"title": "String"
"r:description": "String",
"r:deviceParams": "$ref:ParamList",
"r:id": "Uuid",
"r:o:deviceId": "Uuid",
"r:title": "String"
},
"DeviceDescriptors": [
"$ref:DeviceDescriptor"
],
"DevicePlugin": {
"displayName": "String",
"id": "Uuid",
"name": "String",
"paramTypes": "$ref:ParamTypes"
"r:displayName": "String",
"r:id": "Uuid",
"r:name": "String",
"r:paramTypes": "$ref:ParamTypes"
},
"DevicePlugins": [
"$ref:DevicePlugin"
@ -1768,9 +1768,9 @@
"$ref:Device"
],
"Event": {
"deviceId": "Uuid",
"eventTypeId": "Uuid",
"params": "$ref:ParamList"
"r:deviceId": "Uuid",
"r:eventTypeId": "Uuid",
"r:params": "$ref:ParamList"
},
"EventDescriptor": {
"o:deviceId": "Uuid",
@ -1784,10 +1784,10 @@
],
"EventType": {
"displayName": "String",
"id": "Uuid",
"index": "Int",
"name": "String",
"paramTypes": "$ref:ParamTypes"
"paramTypes": "$ref:ParamTypes",
"r:id": "Uuid",
"r:index": "Int"
},
"EventTypes": [
"$ref:EventType"
@ -1800,15 +1800,15 @@
"$ref:LogEntry"
],
"LogEntry": {
"loggingLevel": "$ref:LoggingLevel",
"o:active": "Bool",
"o:deviceId": "Uuid",
"o:errorCode": "String",
"o:eventType": "$ref:LoggingEventType",
"o:typeId": "Uuid",
"o:value": "Variant",
"source": "$ref:LoggingSource",
"timestamp": "Uint"
"r:loggingLevel": "$ref:LoggingLevel",
"r:o:active": "Bool",
"r:o:deviceId": "Uuid",
"r:o:errorCode": "String",
"r:o:eventType": "$ref:LoggingEventType",
"r:o:typeId": "Uuid",
"r:o:value": "Variant",
"r:source": "$ref:LoggingSource",
"r:timestamp": "Uint"
},
"MqttPolicy": {
"allowedPublishTopicFilters": "StringList",
@ -1818,15 +1818,15 @@
"username": "String"
},
"Package": {
"canRemove": "Bool",
"candidateVersion": "String",
"changelog": "String",
"displayName": "String",
"id": "String",
"installedVersion": "String",
"rollbackAvailable": "Bool",
"summary": "String",
"updateAvailable": "Bool"
"r:canRemove": "Bool",
"r:candidateVersion": "String",
"r:changelog": "String",
"r:displayName": "String",
"r:id": "String",
"r:installedVersion": "String",
"r:rollbackAvailable": "Bool",
"r:summary": "String",
"r:updateAvailable": "Bool"
},
"Packages": [
"$ref:Package"
@ -1849,7 +1849,6 @@
],
"ParamType": {
"displayName": "String",
"id": "Uuid",
"index": "Int",
"name": "String",
"o:allowedValues": [
@ -1861,6 +1860,7 @@
"o:minValue": "Variant",
"o:readOnly": "Bool",
"o:unit": "$ref:Unit",
"r:id": "Uuid",
"type": "$ref:BasicType"
},
"ParamTypes": [
@ -1879,21 +1879,21 @@
"$ref:Repository"
],
"Repository": {
"displayName": "String",
"enabled": "Bool",
"id": "String"
"r:displayName": "String",
"r:id": "String"
},
"Rule": {
"actions": "$ref:RuleActions",
"name": "String",
"o:active": "Bool",
"o:enabled": "Bool",
"o:eventDescriptors": "$ref:EventDescriptors",
"o:executable": "Bool",
"o:exitActions": "$ref:RuleActions",
"o:id": "Uuid",
"o:stateEvaluator": "$ref:StateEvaluator",
"o:timeDescriptor": "$ref:TimeDescriptor"
"o:timeDescriptor": "$ref:TimeDescriptor",
"r:o:active": "Bool"
},
"RuleAction": {
"o:actionTypeId": "Uuid",
@ -1936,8 +1936,8 @@
"sslEnabled": "Bool"
},
"State": {
"stateTypeId": "Uuid",
"value": "Variant"
"r:stateTypeId": "Uuid",
"r:value": "Variant"
},
"StateDescriptor": {
"o:deviceId": "Uuid",
@ -1958,7 +1958,6 @@
"StateType": {
"defaultValue": "Variant",
"displayName": "String",
"id": "Uuid",
"index": "Int",
"name": "String",
"o:maxValue": "Variant",
@ -1967,6 +1966,7 @@
"Variant"
],
"o:unit": "$ref:Unit",
"r:id": "Uuid",
"type": "$ref:BasicType"
},
"StateTypes": [
@ -1998,10 +1998,10 @@
"$ref:TimeEventItem"
],
"TokenInfo": {
"creationTime": "Uint",
"deviveName": "String",
"id": "Uuid",
"username": "String"
"r:creationTime": "Uint",
"r:deviveName": "String",
"r:id": "Uuid",
"r:username": "String"
},
"Vendor": {
"displayName": "String",
@ -2020,18 +2020,18 @@
"sslEnabled": "Bool"
},
"WiredNetworkDevice": {
"bitRate": "String",
"interface": "String",
"macAddress": "String",
"pluggedIn": "Bool",
"state": "$ref:NetworkDeviceState"
"r:bitRate": "String",
"r:interface": "String",
"r:macAddress": "String",
"r:pluggedIn": "Bool",
"r:state": "$ref:NetworkDeviceState"
},
"WirelessAccessPoint": {
"frequency": "Double",
"macAddress": "String",
"protected": "Bool",
"signalStrength": "Int",
"ssid": "String"
"r:frequency": "Double",
"r:macAddress": "String",
"r:protected": "Bool",
"r:signalStrength": "Int",
"r:ssid": "String"
},
"WirelessNetworkDevice": {
"bitRate": "String",