Merge PR #425: Add support fur user permissions
This commit is contained in:
commit
fca9ecee20
@ -1,187 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "actionhandler.h"
|
||||
#include "devicehandler.h"
|
||||
|
||||
#include "nymeacore.h"
|
||||
#include "integrations/thingmanager.h"
|
||||
#include "integrations/thingactioninfo.h"
|
||||
#include "integrations/browseractioninfo.h"
|
||||
#include "integrations/browseritemactioninfo.h"
|
||||
#include "types/action.h"
|
||||
#include "types/actiontype.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
ActionHandler::ActionHandler(QObject *parent) :
|
||||
JsonHandler(parent)
|
||||
{
|
||||
// Enums
|
||||
registerEnum<Types::InputType>();
|
||||
registerEnum<Types::Unit>();
|
||||
|
||||
// Objects
|
||||
registerObject<ParamType, ParamTypes>();
|
||||
registerObject<Param, ParamList>();
|
||||
registerObject<ActionType>();
|
||||
registerObject<Action>();
|
||||
|
||||
// Methods
|
||||
QString description; QVariantMap params; QVariantMap returns;
|
||||
description = "Execute a single action.";
|
||||
params.insert("actionTypeId", enumValueName(Uuid));
|
||||
params.insert("deviceId", enumValueName(Uuid));
|
||||
params.insert("o:params", objectRef<ParamList>());
|
||||
returns.insert("deviceError", enumRef<Device::DeviceError>());
|
||||
returns.insert("o:displayMessage", enumValueName(String));
|
||||
registerMethod("ExecuteAction", description, params, returns, "Please use Integrations.ExecuteAction instead.");
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get the ActionType for the given ActionTypeId.";
|
||||
params.insert("actionTypeId", enumValueName(Uuid));
|
||||
returns.insert("deviceError", enumRef<Device::DeviceError>());
|
||||
returns.insert("o:actionType", objectRef<ActionType>());
|
||||
registerMethod("GetActionType", description, params, returns, "Please use the Integrations namespace instead.");
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Execute the item identified by itemId on the given device.";
|
||||
params.insert("deviceId", enumValueName(Uuid));
|
||||
params.insert("itemId", enumValueName(String));
|
||||
returns.insert("deviceError", enumRef<Device::DeviceError>());
|
||||
registerMethod("ExecuteBrowserItem", description, params, returns, "Please use Integrations.ExecuteBrowserItem instead.");
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Execute the action for the browser item identified by actionTypeId and the itemId on the given device.";
|
||||
params.insert("deviceId", enumValueName(Uuid));
|
||||
params.insert("itemId", enumValueName(String));
|
||||
params.insert("actionTypeId", enumValueName(Uuid));
|
||||
params.insert("o:params", objectRef<ParamList>());
|
||||
returns.insert("deviceError", enumRef<Device::DeviceError>());
|
||||
registerMethod("ExecuteBrowserItemAction", description, params, returns, "Please use Integrations.ExecuteBrowserItem instead.");
|
||||
|
||||
}
|
||||
|
||||
QString ActionHandler::name() const
|
||||
{
|
||||
return "Actions";
|
||||
}
|
||||
|
||||
JsonReply* ActionHandler::ExecuteAction(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
ThingId thingId(params.value("deviceId").toString());
|
||||
ActionTypeId actionTypeId(params.value("actionTypeId").toString());
|
||||
ParamList actionParams = unpack<ParamList>(params.value("params"));
|
||||
QLocale locale = context.locale();
|
||||
|
||||
Action action(actionTypeId, thingId);
|
||||
action.setParams(actionParams);
|
||||
|
||||
JsonReply *jsonReply = createAsyncReply("ExecuteAction");
|
||||
|
||||
ThingActionInfo *info = NymeaCore::instance()->thingManager()->executeAction(action);
|
||||
connect(info, &ThingActionInfo::finished, jsonReply, [info, jsonReply, locale](){
|
||||
QVariantMap data;
|
||||
data.insert("deviceError", enumValueName(info->status()).replace("Thing", "Device"));
|
||||
if (!info->displayMessage().isEmpty()) {
|
||||
data.insert("displayMessage", info->translatedDisplayMessage(locale));
|
||||
}
|
||||
jsonReply->setData(data);
|
||||
jsonReply->finished();
|
||||
});
|
||||
|
||||
return jsonReply;
|
||||
}
|
||||
|
||||
JsonReply *ActionHandler::GetActionType(const QVariantMap ¶ms, const JsonContext &context) const
|
||||
{
|
||||
QLocale locale = context.locale();
|
||||
qCDebug(dcJsonRpc) << "asked for action type" << params;
|
||||
ActionTypeId actionTypeId(params.value("actionTypeId").toString());
|
||||
foreach (const ThingClass &deviceClass, NymeaCore::instance()->thingManager()->supportedThings()) {
|
||||
foreach (const ActionType &actionType, deviceClass.actionTypes()) {
|
||||
if (actionType.id() == actionTypeId) {
|
||||
ActionType translatedActionType = actionType;
|
||||
translatedActionType.setDisplayName(NymeaCore::instance()->thingManager()->translate(deviceClass.pluginId(), actionType.displayName(), locale));
|
||||
|
||||
QVariantMap data;
|
||||
data.insert("deviceError", enumValueName<Thing::ThingError>(Thing::ThingErrorNoError).replace("ThingError", "DeviceError"));
|
||||
data.insert("actionType", pack(translatedActionType));
|
||||
return createReply(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
QVariantMap data;
|
||||
data.insert("deviceError", enumValueName<Thing::ThingError>(Thing::ThingErrorActionTypeNotFound).replace("ThingError", "DeviceError"));
|
||||
return createReply(data);
|
||||
}
|
||||
|
||||
JsonReply *ActionHandler::ExecuteBrowserItem(const QVariantMap ¶ms)
|
||||
{
|
||||
ThingId thingId = ThingId(params.value("deviceId").toString());
|
||||
QString itemId = params.value("itemId").toString();
|
||||
BrowserAction action(thingId, itemId);
|
||||
|
||||
JsonReply *jsonReply = createAsyncReply("ExecuteBrowserItem");
|
||||
|
||||
BrowserActionInfo *info = NymeaCore::instance()->executeBrowserItem(action);
|
||||
connect(info, &BrowserActionInfo::finished, jsonReply, [info, jsonReply](){
|
||||
QVariantMap data;
|
||||
data.insert("deviceError", enumValueName<Thing::ThingError>(info->status()).replace("ThingError", "DeviceError"));
|
||||
jsonReply->setData(data);
|
||||
jsonReply->finished();
|
||||
});
|
||||
|
||||
return jsonReply;
|
||||
}
|
||||
|
||||
JsonReply *ActionHandler::ExecuteBrowserItemAction(const QVariantMap ¶ms)
|
||||
{
|
||||
ThingId thingId = ThingId(params.value("deviceId").toString());
|
||||
QString itemId = params.value("itemId").toString();
|
||||
ActionTypeId actionTypeId = ActionTypeId(params.value("actionTypeId").toString());
|
||||
ParamList paramList = unpack<ParamList>(params.value("params"));
|
||||
BrowserItemAction browserItemAction(thingId, itemId, actionTypeId, paramList);
|
||||
|
||||
JsonReply *jsonReply = createAsyncReply("ExecuteBrowserItemAction");
|
||||
|
||||
BrowserItemActionInfo *info = NymeaCore::instance()->executeBrowserItemAction(browserItemAction);
|
||||
connect(info, &BrowserItemActionInfo::finished, jsonReply, [info, jsonReply](){
|
||||
QVariantMap data;
|
||||
data.insert("deviceError", enumValueName<Thing::ThingError>(info->status()).replace("Thing", "Device"));
|
||||
jsonReply->setData(data);
|
||||
jsonReply->finished();
|
||||
});
|
||||
|
||||
return jsonReply;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef ACTIONHANDLER_H
|
||||
#define ACTIONHANDLER_H
|
||||
|
||||
#include "jsonrpc/jsonhandler.h"
|
||||
#include "integrations/thingmanager.h"
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
class ActionHandler : public JsonHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ActionHandler(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
|
||||
Q_INVOKABLE JsonReply *ExecuteAction(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *GetActionType(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
|
||||
Q_INVOKABLE JsonReply *ExecuteBrowserItem(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *ExecuteBrowserItemAction(const QVariantMap ¶ms);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // ACTIONHANDLER_H
|
||||
@ -92,12 +92,12 @@ ConfigurationHandler::ConfigurationHandler(QObject *parent):
|
||||
QString description; QVariantMap params; QVariantMap returns;
|
||||
description = "Get the list of available timezones.";
|
||||
returns.insert("timeZones", QVariantList() << enumValueName(String));
|
||||
registerMethod("GetTimeZones", description, params, returns, "Use System.GetTimeZones instead.");
|
||||
registerMethod("GetTimeZones", description, params, returns, Types::PermissionScopeNone, "Use System.GetTimeZones instead.");
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Returns a list of locale codes available for the server. i.e. en_US, de_AT";
|
||||
returns.insert("languages", QVariantList() << enumValueName(String));
|
||||
registerMethod("GetAvailableLanguages", description, params, returns, "Use the locale property in the Handshake message instead.");
|
||||
registerMethod("GetAvailableLanguages", description, params, returns, Types::PermissionScopeNone, "Use the locale property in the Handshake message instead.");
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get all configuration parameters of the server.";
|
||||
@ -135,13 +135,13 @@ ConfigurationHandler::ConfigurationHandler(QObject *parent):
|
||||
description = "Set the time zone of the server. See also: \"GetTimeZones\"";
|
||||
params.insert("timeZone", enumValueName(String));
|
||||
returns.insert("configurationError", enumRef<NymeaConfiguration::ConfigurationError>());
|
||||
registerMethod("SetTimeZone", description, params, returns, "Use System.SetTimeZone instead.");
|
||||
registerMethod("SetTimeZone", description, params, returns, Types::PermissionScopeAdmin, "Use System.SetTimeZone instead.");
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Sets the server language to the given language. See also: \"GetAvailableLanguages\"";
|
||||
params.insert("language", enumValueName(String));
|
||||
returns.insert("configurationError", enumRef<NymeaConfiguration::ConfigurationError>());
|
||||
registerMethod("SetLanguage", description, params, returns, "Use the locale property in the Handshake message instead.");
|
||||
registerMethod("SetLanguage", description, params, returns, Types::PermissionScopeAdmin, "Use the locale property in the Handshake message instead.");
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Enable or disable the debug server.";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,210 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef DEVICEHANDLER_H
|
||||
#define DEVICEHANDLER_H
|
||||
|
||||
#include "jsonrpc/jsonhandler.h"
|
||||
#include "integrations/thingmanager.h"
|
||||
#include "integrations/thing.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
DECLARE_TYPE_ID(DeviceClass)
|
||||
DECLARE_TYPE_ID(Device)
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
// Device has been renamed to Thing. As we need to keep compatibility with the Devices API for a bit,
|
||||
// let's create them here
|
||||
|
||||
class DevicePlugin: public IntegrationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
};
|
||||
|
||||
class DevicePlugins: public IntegrationPlugins
|
||||
{
|
||||
Q_GADGET
|
||||
};
|
||||
|
||||
class DeviceClass: public ThingClass
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
DeviceClass(): ThingClass() {}
|
||||
DeviceClass(const ThingClass &other);
|
||||
};
|
||||
|
||||
class DeviceClasses: public ThingClasses
|
||||
{
|
||||
Q_GADGET
|
||||
};
|
||||
|
||||
class Device: public Thing
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QUuid deviceClassId READ deviceClassId)
|
||||
public:
|
||||
enum DeviceError {
|
||||
DeviceErrorNoError,
|
||||
DeviceErrorPluginNotFound,
|
||||
DeviceErrorVendorNotFound,
|
||||
DeviceErrorDeviceNotFound,
|
||||
DeviceErrorDeviceClassNotFound,
|
||||
DeviceErrorActionTypeNotFound,
|
||||
DeviceErrorStateTypeNotFound,
|
||||
DeviceErrorEventTypeNotFound,
|
||||
DeviceErrorDeviceDescriptorNotFound,
|
||||
DeviceErrorMissingParameter,
|
||||
DeviceErrorInvalidParameter,
|
||||
DeviceErrorSetupFailed,
|
||||
DeviceErrorDuplicateUuid,
|
||||
DeviceErrorCreationMethodNotSupported,
|
||||
DeviceErrorSetupMethodNotSupported,
|
||||
DeviceErrorHardwareNotAvailable,
|
||||
DeviceErrorHardwareFailure,
|
||||
DeviceErrorAuthenticationFailure,
|
||||
DeviceErrorDeviceInUse,
|
||||
DeviceErrorDeviceInRule,
|
||||
DeviceErrorDeviceIsChild,
|
||||
DeviceErrorPairingTransactionIdNotFound,
|
||||
DeviceErrorParameterNotWritable,
|
||||
DeviceErrorItemNotFound,
|
||||
DeviceErrorItemNotExecutable,
|
||||
DeviceErrorUnsupportedFeature,
|
||||
DeviceErrorTimeout,
|
||||
};
|
||||
Q_ENUM(DeviceError)
|
||||
|
||||
enum DeviceSetupStatus {
|
||||
DeviceSetupStatusNone,
|
||||
DeviceSetupStatusInProgress,
|
||||
DeviceSetupStatusComplete,
|
||||
DeviceSetupStatusFailed,
|
||||
};
|
||||
Q_ENUM(DeviceSetupStatus)
|
||||
|
||||
DeviceClassId deviceClassId() const;
|
||||
};
|
||||
|
||||
class Devices: public Things
|
||||
{
|
||||
Q_GADGET
|
||||
};
|
||||
|
||||
class DeviceDescriptor: public ThingDescriptor
|
||||
{
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QUuid deviceId READ thingId USER true)
|
||||
Q_PROPERTY(ParamList deviceParams READ params)
|
||||
};
|
||||
|
||||
class DeviceDescriptors: public ThingDescriptors
|
||||
{
|
||||
Q_GADGET
|
||||
};
|
||||
|
||||
class DeviceHandler : public JsonHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeviceHandler(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QHash<QString, QString> cacheHashes() const override;
|
||||
|
||||
QVariantMap translateNotification(const QString ¬ification, const QVariantMap ¶ms, const QLocale &locale) override;
|
||||
|
||||
Q_INVOKABLE JsonReply *GetSupportedVendors(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
Q_INVOKABLE JsonReply *GetSupportedDevices(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
Q_INVOKABLE JsonReply *GetDiscoveredDevices(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
Q_INVOKABLE JsonReply *GetPlugins(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
Q_INVOKABLE JsonReply *GetPluginConfiguration(const QVariantMap ¶ms) const;
|
||||
Q_INVOKABLE JsonReply *SetPluginConfiguration(const QVariantMap ¶ms);
|
||||
|
||||
Q_INVOKABLE JsonReply *AddConfiguredDevice(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *PairDevice(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *ConfirmPairing(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *GetConfiguredDevices(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
Q_INVOKABLE JsonReply *ReconfigureDevice(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *EditDevice(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *RemoveConfiguredDevice(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *SetDeviceSettings(const QVariantMap ¶ms);
|
||||
|
||||
Q_INVOKABLE JsonReply *GetEventTypes(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
Q_INVOKABLE JsonReply *GetActionTypes(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
Q_INVOKABLE JsonReply *GetStateTypes(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
Q_INVOKABLE JsonReply *GetStateValue(const QVariantMap ¶ms) const;
|
||||
Q_INVOKABLE JsonReply *GetStateValues(const QVariantMap ¶ms) const;
|
||||
|
||||
Q_INVOKABLE JsonReply *BrowseDevice(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
Q_INVOKABLE JsonReply *GetBrowserItem(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
|
||||
Q_INVOKABLE JsonReply *ExecuteAction(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *ExecuteBrowserItem(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *ExecuteBrowserItemAction(const QVariantMap ¶ms, const JsonContext &context);
|
||||
|
||||
static QVariantMap packBrowserItem(const BrowserItem &item);
|
||||
|
||||
signals:
|
||||
void PluginConfigurationChanged(const QVariantMap ¶ms);
|
||||
void StateChanged(const QVariantMap ¶ms);
|
||||
void DeviceRemoved(const QVariantMap ¶ms);
|
||||
void DeviceAdded(const QVariantMap ¶ms);
|
||||
void DeviceChanged(const QVariantMap ¶ms);
|
||||
void DeviceSettingChanged(const QVariantMap ¶ms);
|
||||
void EventTriggered(const QVariantMap ¶ms);
|
||||
|
||||
private slots:
|
||||
void pluginConfigChanged(const PluginId &id, const ParamList &config);
|
||||
|
||||
void deviceStateChanged(Thing *device, const QUuid &stateTypeId, const QVariant &value);
|
||||
|
||||
void deviceRemovedNotification(const QUuid &deviceId);
|
||||
|
||||
void deviceAddedNotification(Thing *thing);
|
||||
|
||||
void deviceChangedNotification(Thing *thing);
|
||||
|
||||
void deviceSettingChangedNotification(const ThingId &thingId, const ParamTypeId ¶mTypeId, const QVariant &value);
|
||||
|
||||
private:
|
||||
QVariantMap statusToReply(Device::ThingError status) const;
|
||||
|
||||
QHash<QString, QString> m_cacheHashes;
|
||||
};
|
||||
|
||||
}
|
||||
Q_DECLARE_METATYPE(nymeaserver::DeviceClass)
|
||||
Q_DECLARE_METATYPE(nymeaserver::Device::DeviceError)
|
||||
|
||||
#endif // DEVICEHANDLER_H
|
||||
@ -1,100 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "eventhandler.h"
|
||||
#include "nymeacore.h"
|
||||
#include "loggingcategories.h"
|
||||
#include "devicehandler.h"
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
/*! Constructs a new \l EventHandler with the given \a parent. */
|
||||
EventHandler::EventHandler(QObject *parent) :
|
||||
JsonHandler(parent)
|
||||
{
|
||||
registerEnum<Types::InputType>();
|
||||
registerEnum<Types::Unit>();
|
||||
// Objects
|
||||
registerObject<Param, ParamList>();
|
||||
registerObject<Event>();
|
||||
registerObject<ParamType, ParamTypes>();
|
||||
registerObject<EventType>();
|
||||
|
||||
// Methods
|
||||
QString description; QVariantMap params; QVariantMap returns;
|
||||
description = "Get the EventType for the given eventTypeId.";
|
||||
params.insert("eventTypeId", enumValueName(Uuid));
|
||||
returns.insert("deviceError", enumRef<Device::DeviceError>());
|
||||
returns.insert("o:eventType", objectRef<EventType>());
|
||||
registerMethod("GetEventType", description, params, returns, "Please use the Devices namespace instead.");
|
||||
|
||||
// Notifications
|
||||
params.clear(); returns.clear();
|
||||
description = "Emitted whenever an Event is triggered.";
|
||||
params.insert("event", objectRef<Event>());
|
||||
registerNotification("EventTriggered", description, params, "Please use Devices.EventTriggered instead.");
|
||||
connect(NymeaCore::instance(), &NymeaCore::eventTriggered, this, &EventHandler::eventTriggered);
|
||||
}
|
||||
|
||||
/*! Returns the name of the \l{EventHandler}. In this case \b Events.*/
|
||||
QString EventHandler::name() const
|
||||
{
|
||||
return "Events";
|
||||
}
|
||||
|
||||
void EventHandler::eventTriggered(const Event &event)
|
||||
{
|
||||
QVariantMap params;
|
||||
params.insert("event", pack(event));
|
||||
emit EventTriggered(params);
|
||||
}
|
||||
|
||||
JsonReply* EventHandler::GetEventType(const QVariantMap ¶ms, const JsonContext &context) const
|
||||
{
|
||||
qCDebug(dcJsonRpc) << "asked for event type" << params;
|
||||
EventTypeId eventTypeId(params.value("eventTypeId").toString());
|
||||
foreach (const ThingClass &deviceClass, NymeaCore::instance()->thingManager()->supportedThings()) {
|
||||
foreach (const EventType &eventType, deviceClass.eventTypes()) {
|
||||
if (eventType.id() == eventTypeId) {
|
||||
EventType translatedEventType = eventType;
|
||||
translatedEventType.setDisplayName(NymeaCore::instance()->thingManager()->translate(deviceClass.pluginId(), eventType.displayName(), context.locale()));
|
||||
QVariantMap data;
|
||||
data.insert("deviceError", enumValueName<Thing::ThingError>(Thing::ThingErrorNoError).replace("ThingError", "DeviceError"));
|
||||
data.insert("eventType", pack(translatedEventType));
|
||||
return createReply(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
QVariantMap data;
|
||||
data.insert("deviceError", enumValueName<Thing::ThingError>(Thing::ThingErrorEventTypeNotFound).replace("ThingError", "DeviceError"));
|
||||
return createReply(data);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef EVENTHANDLER_H
|
||||
#define EVENTHANDLER_H
|
||||
|
||||
#include "jsonrpc/jsonhandler.h"
|
||||
|
||||
#include "types/event.h"
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
class EventHandler : public JsonHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EventHandler(QObject *parent = nullptr);
|
||||
QString name() const override;
|
||||
|
||||
Q_INVOKABLE JsonReply *GetEventType(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
|
||||
signals:
|
||||
void EventTriggered(const QVariantMap ¶ms);
|
||||
|
||||
private slots:
|
||||
void eventTriggered(const Event &event);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // EVENTHANDLER_H
|
||||
@ -102,7 +102,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
QString description; QVariantMap returns; QVariantMap params;
|
||||
description = "Returns a list of supported Vendors.";
|
||||
returns.insert("vendors", objectRef<Vendors>());
|
||||
registerMethod("GetVendors", description, params, returns);
|
||||
registerMethod("GetVendors", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Returns a list of supported thing classes, optionally filtered by vendorId or by a list of thing class ids.";
|
||||
@ -110,26 +110,26 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
params.insert("o:thingClassIds", QVariantList() << enumValueName(Uuid));
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:thingClasses", objectRef<ThingClasses>());
|
||||
registerMethod("GetThingClasses", description, params, returns);
|
||||
registerMethod("GetThingClasses", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Returns a list of loaded plugins.";
|
||||
returns.insert("plugins", objectRef<IntegrationPlugins>());
|
||||
registerMethod("GetPlugins", description, params, returns);
|
||||
registerMethod("GetPlugins", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get a plugin's params.";
|
||||
params.insert("pluginId", enumValueName(Uuid));
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:configuration", objectRef<ParamList>());
|
||||
registerMethod("GetPluginConfiguration", description, params, returns);
|
||||
registerMethod("GetPluginConfiguration", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Set a plugin's params.";
|
||||
params.insert("pluginId", enumValueName(Uuid));
|
||||
params.insert("configuration", objectRef<ParamList>());
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
registerMethod("SetPluginConfiguration", description, params, returns);
|
||||
registerMethod("SetPluginConfiguration", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Add a new thing to the system. "
|
||||
@ -146,7 +146,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:thingId", enumValueName(Uuid));
|
||||
returns.insert("o:displayMessage", enumValueName(String));
|
||||
registerMethod("AddThing", description, params, returns);
|
||||
registerMethod("AddThing", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Pair a new thing. "
|
||||
@ -178,7 +178,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
returns.insert("o:displayMessage", enumValueName(String));
|
||||
returns.insert("o:oAuthUrl", enumValueName(String));
|
||||
returns.insert("o:pin", enumValueName(String));
|
||||
registerMethod("PairThing", description, params, returns);
|
||||
registerMethod("PairThing", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Confirm an ongoing pairing. For SetupMethodUserAndPassword, provide the username in the \"username\" field "
|
||||
@ -191,14 +191,14 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:displayMessage", enumValueName(String));
|
||||
returns.insert("o:thingId", enumValueName(Uuid));
|
||||
registerMethod("ConfirmPairing", description, params, returns);
|
||||
registerMethod("ConfirmPairing", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Returns a list of configured things, optionally filtered by thingId.";
|
||||
params.insert("o:thingId", enumValueName(Uuid));
|
||||
returns.insert("o:things", objectRef<Things>());
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
registerMethod("GetThings", description, params, returns);
|
||||
registerMethod("GetThings", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Performs a thing discovery for things of the given thingClassId and returns the results. "
|
||||
@ -211,7 +211,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:displayMessage", enumValueName(String));
|
||||
returns.insert("o:thingDescriptors", objectRef<ThingDescriptors>());
|
||||
registerMethod("DiscoverThings", description, params, returns);
|
||||
registerMethod("DiscoverThings", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Reconfigure a thing. This comes down to removing and recreating a thing with new parameters "
|
||||
@ -226,21 +226,21 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
params.insert("o:thingParams", objectRef<ParamList>());
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:displayMessage", enumValueName(String));
|
||||
registerMethod("ReconfigureThing", description, params, returns);
|
||||
registerMethod("ReconfigureThing", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Edit the name of a thing.";
|
||||
params.insert("thingId", enumValueName(Uuid));
|
||||
params.insert("name", enumValueName(String));
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
registerMethod("EditThing", description, params, returns);
|
||||
registerMethod("EditThing", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Change the settings of a thing.";
|
||||
params.insert("thingId", enumValueName(Uuid));
|
||||
params.insert("settings", objectRef<ParamList>());
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
registerMethod("SetThingSettings", description, params, returns);
|
||||
registerMethod("SetThingSettings", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Enable/disable logging for the given event type on the given thing.";
|
||||
@ -248,7 +248,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
params.insert("eventTypeId", enumValueName(Uuid));
|
||||
params.insert("enabled", enumValueName(Bool));
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
registerMethod("SetEventLogging", description, params, returns);
|
||||
registerMethod("SetEventLogging", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Set the filter for the given state on the given thing.";
|
||||
@ -256,7 +256,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
params.insert("stateTypeId", enumValueName(Uuid));
|
||||
params.insert("filter", enumRef<Types::StateValueFilter>());
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
registerMethod("SetStateFilter", description, params, returns);
|
||||
registerMethod("SetStateFilter", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Remove a thing from the system.";
|
||||
@ -270,25 +270,25 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
params.insert("o:removePolicyList", removePolicyList);
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:ruleIds", QVariantList() << enumValueName(Uuid));
|
||||
registerMethod("RemoveThing", description, params, returns);
|
||||
registerMethod("RemoveThing", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get event types for a specified thingClassId.";
|
||||
params.insert("thingClassId", enumValueName(Uuid));
|
||||
returns.insert("eventTypes", objectRef<EventTypes>());
|
||||
registerMethod("GetEventTypes", description, params, returns);
|
||||
registerMethod("GetEventTypes", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get action types for a specified thingClassId.";
|
||||
params.insert("thingClassId", enumValueName(Uuid));
|
||||
returns.insert("actionTypes", objectRef<ActionTypes>());
|
||||
registerMethod("GetActionTypes", description, params, returns);
|
||||
registerMethod("GetActionTypes", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get state types for a specified thingClassId.";
|
||||
params.insert("thingClassId", enumValueName(Uuid));
|
||||
returns.insert("stateTypes", objectRef<StateTypes>());
|
||||
registerMethod("GetStateTypes", description, params, returns);
|
||||
registerMethod("GetStateTypes", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get the value of the given thing and the given stateType";
|
||||
@ -296,14 +296,14 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
params.insert("stateTypeId", enumValueName(Uuid));
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:value", enumValueName(Variant));
|
||||
registerMethod("GetStateValue", description, params, returns);
|
||||
registerMethod("GetStateValue", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get all the state values of the given thing.";
|
||||
params.insert("thingId", enumValueName(Uuid));
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:values", objectRef<States>());
|
||||
registerMethod("GetStateValues", description, params, returns);
|
||||
registerMethod("GetStateValues", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Browse a thing. "
|
||||
@ -318,7 +318,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:displayMessage", enumValueName(String));
|
||||
returns.insert("items", QVariantList() << objectRef("BrowserItem"));
|
||||
registerMethod("BrowseThing", description, params, returns);
|
||||
registerMethod("BrowseThing", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get a single item from the browser. "
|
||||
@ -332,7 +332,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:displayMessage", enumValueName(String));
|
||||
returns.insert("o:item", objectRef("BrowserItem"));
|
||||
registerMethod("GetBrowserItem", description, params, returns);
|
||||
registerMethod("GetBrowserItem", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Execute a single action.";
|
||||
@ -341,7 +341,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
params.insert("o:params", objectRef<ParamList>());
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:displayMessage", enumValueName(String));
|
||||
registerMethod("ExecuteAction", description, params, returns);
|
||||
registerMethod("ExecuteAction", description, params, returns, Types::PermissionScopeControlThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Execute the item identified by itemId on the given thing.\n"
|
||||
@ -352,7 +352,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
params.insert("itemId", enumValueName(String));
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:displayMessage", enumValueName(String));
|
||||
registerMethod("ExecuteBrowserItem", description, params, returns);
|
||||
registerMethod("ExecuteBrowserItem", description, params, returns, Types::PermissionScopeControlThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Execute the action for the browser item identified by actionTypeId and the itemId on the given thing.\n"
|
||||
@ -365,13 +365,13 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
params.insert("o:params", objectRef<ParamList>());
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:displayMessage", enumValueName(String));
|
||||
registerMethod("ExecuteBrowserItemAction", description, params, returns);
|
||||
registerMethod("ExecuteBrowserItemAction", description, params, returns, Types::PermissionScopeControlThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Fetch IO connections. Optionally filtered by thingId and stateTypeId.";
|
||||
params.insert("o:thingId", enumValueName(Uuid));
|
||||
returns.insert("ioConnections", objectRef<IOConnections>());
|
||||
registerMethod("GetIOConnections", description, params, returns);
|
||||
registerMethod("GetIOConnections", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Connect two generic IO states. Input and output need to be compatible, that is, either a digital input "
|
||||
@ -383,13 +383,13 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
params.insert("o:inverted", enumValueName(Bool));
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
returns.insert("o:ioConnectionId", enumValueName(Uuid));
|
||||
registerMethod("ConnectIO", description, params, returns);
|
||||
registerMethod("ConnectIO", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Disconnect an existing IO connection.";
|
||||
params.insert("ioConnectionId", enumValueName(Uuid));
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
registerMethod("DisconnectIO", description, params, returns);
|
||||
registerMethod("DisconnectIO", description, params, returns, Types::PermissionScopeConfigureThings);
|
||||
|
||||
|
||||
// Notifications
|
||||
|
||||
@ -60,14 +60,10 @@
|
||||
#include "version.h"
|
||||
#include "cloud/cloudmanager.h"
|
||||
|
||||
#include "devicehandler.h"
|
||||
#include "integrationshandler.h"
|
||||
#include "actionhandler.h"
|
||||
#include "ruleshandler.h"
|
||||
#include "scriptshandler.h"
|
||||
#include "eventhandler.h"
|
||||
#include "logginghandler.h"
|
||||
#include "statehandler.h"
|
||||
#include "configurationhandler.h"
|
||||
#include "networkmanagerhandler.h"
|
||||
#include "tagshandler.h"
|
||||
@ -95,6 +91,7 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration
|
||||
registerEnum<BasicType>();
|
||||
registerEnum<UserManager::UserError>();
|
||||
registerEnum<CloudManager::CloudConnectionState>();
|
||||
registerFlag<Types::PermissionScope, Types::PermissionScopes>();
|
||||
|
||||
// Objects
|
||||
registerObject<TokenInfo>();
|
||||
@ -122,7 +119,9 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration
|
||||
"like initialSetupRequired might change if the setup has been performed in the meantime.\n "
|
||||
"The field cacheHashes may contain a map of methods and MD5 hashes. As long as the hash for "
|
||||
"a method does not change, a client may use a previously cached copy of the call instead of "
|
||||
"fetching the content again.";
|
||||
"fetching the content again. While the Hello call doesn't necessarily require a token, this "
|
||||
"can be called with a token. If a token is provided, it will be verified and the reply contains "
|
||||
"information about the tokens validity and the user and permissions for the given token.";
|
||||
params.insert("o:locale", enumValueName(String));
|
||||
returns.insert("server", enumValueName(String));
|
||||
returns.insert("name", enumValueName(String));
|
||||
@ -136,14 +135,17 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration
|
||||
returns.insert("pushButtonAuthAvailable", enumValueName(Bool));
|
||||
returns.insert("o:experiences", QVariantList() << objectRef("Experience"));
|
||||
returns.insert("o:cacheHashes", QVariantList() << objectRef("CacheHash"));
|
||||
registerMethod("Hello", description, params, returns);
|
||||
returns.insert("o:authenticated", enumValueName(Bool));
|
||||
returns.insert("o:permissionScopes", flagRef<Types::PermissionScopes>());
|
||||
returns.insert("o:username", enumValueName(String));
|
||||
registerMethod("Hello", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Introspect this API.";
|
||||
returns.insert("methods", enumValueName(Object));
|
||||
returns.insert("notifications", enumValueName(Object));
|
||||
returns.insert("types", enumValueName(Object));
|
||||
registerMethod("Introspect", description, params, returns);
|
||||
registerMethod("Introspect", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Version of this nymea/JSONRPC interface.";
|
||||
@ -151,7 +153,7 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration
|
||||
returns.insert("protocol version", enumValueName(String));
|
||||
returns.insert("qtVersion", enumValueName(String));
|
||||
returns.insert("qtBuildVersion", enumValueName(String));
|
||||
registerMethod("Version", description, params, returns);
|
||||
registerMethod("Version", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Enable/Disable notifications for this connections. Either \"enabled\" or """
|
||||
@ -166,14 +168,18 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration
|
||||
params.insert("d:o:enabled", enumValueName(Bool));
|
||||
returns.insert("namespaces", enumValueName(StringList));
|
||||
returns.insert("d:enabled", enumValueName(Bool));
|
||||
registerMethod("SetNotificationStatus", description, params, returns);
|
||||
registerMethod("SetNotificationStatus", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Create a new user in the API. Currently this is only allowed to be called once when a new nymea instance is set up. Call Authenticate after this to obtain a device token for this user.";
|
||||
description = "Create a new user in the API. This is only allowed to be called when the initial setup is required. "
|
||||
"To create additional users, use Users.CreateUser instead. Call Authenticate after this to obtain a "
|
||||
"device token for the newly created user.";
|
||||
params.insert("username", enumValueName(String));
|
||||
params.insert("password", enumValueName(String));
|
||||
params.insert("o:displayName", enumValueName(String));
|
||||
params.insert("o:email", enumValueName(String));
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("CreateUser", description, params, returns, "Use Users.CreateUser instead.");
|
||||
registerMethod("CreateUser", description, params, returns, Types::PermissionScopeAdmin);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Authenticate a client to the api via user & password challenge. Provide "
|
||||
@ -185,7 +191,9 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration
|
||||
params.insert("deviceName", enumValueName(String));
|
||||
returns.insert("success", enumValueName(Bool));
|
||||
returns.insert("o:token", enumValueName(String));
|
||||
registerMethod("Authenticate", description, params, returns, "Use Users.Authenticate instead.");
|
||||
returns.insert("o:username", enumValueName(String));
|
||||
returns.insert("o:scopes", flagRef<Types::PermissionScopes>());
|
||||
registerMethod("Authenticate", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Authenticate a client to the api via Push Button method. "
|
||||
@ -204,18 +212,7 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration
|
||||
params.insert("deviceName", enumValueName(String));
|
||||
returns.insert("success", enumValueName(Bool));
|
||||
returns.insert("transactionId", enumValueName(Int));
|
||||
registerMethod("RequestPushButtonAuth", description, params, returns, "Use Users.RequestPushButtonAuth instead.");
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Return a list of TokenInfo objects of all the tokens for the current user.";
|
||||
returns.insert("tokenInfoList", QVariantList() << objectRef("TokenInfo"));
|
||||
registerMethod("Tokens", description, params, returns, "Use Users.GetTokens instead.");
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Revoke access for a given token.";
|
||||
params.insert("tokenId", enumValueName(Uuid));
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("RemoveToken", description, params, returns, "Use Users.RemoveToken instead.");
|
||||
registerMethod("RequestPushButtonAuth", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Sets up the cloud connection by deploying a certificate and its configuration.";
|
||||
@ -246,7 +243,7 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration
|
||||
params.insert("sessionId", enumValueName(String));
|
||||
returns.insert("success", enumValueName(Bool));
|
||||
returns.insert("sessionId", enumValueName(String));
|
||||
registerMethod("KeepAlive", description, params, returns);
|
||||
registerMethod("KeepAlive", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
// Notifications
|
||||
params.clear(); returns.clear();
|
||||
@ -260,11 +257,14 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration
|
||||
params.insert("success", enumValueName(Bool));
|
||||
params.insert("transactionId", enumValueName(Int));
|
||||
params.insert("o:token", enumValueName(String));
|
||||
registerNotification("PushButtonAuthFinished", description, params, "Use Users.PushButtonAuthFinished instead.");
|
||||
registerNotification("PushButtonAuthFinished", description, params);
|
||||
|
||||
QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection);
|
||||
|
||||
connect(NymeaCore::instance()->userManager(), &UserManager::pushButtonAuthFinished, this, &JsonRPCServerImplementation::onPushButtonAuthFinished);
|
||||
|
||||
m_connectionLockdownTimer.setSingleShot(true);
|
||||
m_connectionLockdownTimer.setInterval(3000);
|
||||
}
|
||||
|
||||
/*! Returns the \e namespace of \l{JsonHandler}. */
|
||||
@ -290,7 +290,67 @@ JsonReply *JsonRPCServerImplementation::Hello(const QVariantMap ¶ms, const J
|
||||
delete m_newConnectionWaitTimers.take(clientId);
|
||||
}
|
||||
|
||||
return createReply(createWelcomeMessage(interface, clientId));
|
||||
// Compose the reply
|
||||
QVariantMap handshake;
|
||||
handshake.insert("server", "nymea");
|
||||
handshake.insert("name", NymeaCore::instance()->configuration()->serverName());
|
||||
handshake.insert("version", NYMEA_VERSION_STRING);
|
||||
handshake.insert("uuid", NymeaCore::instance()->configuration()->serverUuid().toString());
|
||||
// "language" is deprecated
|
||||
handshake.insert("language", m_clientLocales.value(clientId).name());
|
||||
handshake.insert("locale", m_clientLocales.value(clientId).name());
|
||||
handshake.insert("protocol version", JSON_PROTOCOL_VERSION);
|
||||
handshake.insert("initialSetupRequired", (interface->configuration().authenticationEnabled ? NymeaCore::instance()->userManager()->initRequired() : false));
|
||||
handshake.insert("authenticationRequired", interface->configuration().authenticationEnabled);
|
||||
handshake.insert("pushButtonAuthAvailable", NymeaCore::instance()->userManager()->pushButtonAuthAvailable());
|
||||
if (!m_experiences.isEmpty()) {
|
||||
QVariantList experiences;
|
||||
foreach (JsonHandler* handler, m_experiences.keys()) {
|
||||
QVariantMap experience;
|
||||
experience.insert("name", handler->name());
|
||||
experience.insert("version", m_experiences.value(handler));
|
||||
experiences.append(experience);
|
||||
}
|
||||
handshake.insert("experiences", experiences);
|
||||
}
|
||||
QVariantList cacheHashes;
|
||||
foreach (const QString &handlerName, m_handlers.keys()) {
|
||||
QHash<QString, QString> hashes = m_handlers.value(handlerName)->cacheHashes();
|
||||
foreach (const QString &hashName, hashes.keys()) {
|
||||
QVariantMap cacheHash;
|
||||
cacheHash.insert("method", handlerName + "." + hashName);
|
||||
cacheHash.insert("hash", hashes.value(hashName));
|
||||
cacheHashes.append(cacheHash);
|
||||
}
|
||||
}
|
||||
if (!cacheHashes.isEmpty()) {
|
||||
handshake.insert("cacheHashes", cacheHashes);
|
||||
}
|
||||
|
||||
bool badToken = false;
|
||||
if (!context.token().isEmpty()) {
|
||||
TokenInfo tokenInfo = NymeaCore::instance()->userManager()->tokenInfo(context.token());
|
||||
UserInfo userInfo = NymeaCore::instance()->userManager()->userInfo(tokenInfo.username());
|
||||
badToken = tokenInfo.id().isNull();
|
||||
handshake.insert("authenticated", !badToken);
|
||||
handshake.insert("permissionScopes", Types::scopesToStringList(userInfo.scopes()));
|
||||
handshake.insert("username", userInfo.username());
|
||||
}
|
||||
|
||||
// If the connection is locked down already (because of a previous failed attempt) and authentication failed
|
||||
// again, drop the client. He won't be able to reconnect until the lockdown timer runs out.
|
||||
// This will give at max 2 attempts to present a valid token per lockdown period.
|
||||
if (m_connectionLockdownTimer.isActive() && badToken) {
|
||||
qCWarning(dcJsonRpc()) << "Dropping client because of repeated bad token authentication.";
|
||||
interface->terminateClientConnection(clientId);
|
||||
}
|
||||
|
||||
if (badToken) {
|
||||
qCWarning(dcJsonRpc()) << "Staring connection lockdown timer";
|
||||
m_connectionLockdownTimer.start();
|
||||
}
|
||||
|
||||
return createReply(handshake);;
|
||||
}
|
||||
|
||||
JsonReply* JsonRPCServerImplementation::Introspect(const QVariantMap ¶ms) const
|
||||
@ -342,15 +402,17 @@ JsonReply *JsonRPCServerImplementation::CreateUser(const QVariantMap ¶ms)
|
||||
{
|
||||
QString username = params.value("username").toString();
|
||||
QString password = params.value("password").toString();
|
||||
QString email = params.value("email").toString();
|
||||
QString displayName = params.value("displayName").toString();
|
||||
|
||||
UserManager::UserError status = NymeaCore::instance()->userManager()->createUser(username, password);
|
||||
UserManager::UserError status = NymeaCore::instance()->userManager()->createUser(username, password, email, displayName, Types::PermissionScopeAdmin);
|
||||
|
||||
QVariantMap returns;
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(status));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *JsonRPCServerImplementation::Authenticate(const QVariantMap ¶ms)
|
||||
JsonReply *JsonRPCServerImplementation::Authenticate(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
QString username = params.value("username").toString();
|
||||
QString password = params.value("password").toString();
|
||||
@ -361,7 +423,26 @@ JsonReply *JsonRPCServerImplementation::Authenticate(const QVariantMap ¶ms)
|
||||
ret.insert("success", !token.isEmpty());
|
||||
if (!token.isEmpty()) {
|
||||
ret.insert("token", token);
|
||||
TokenInfo tokenInfo = NymeaCore::instance()->userManager()->tokenInfo(token);
|
||||
UserInfo userInfo = NymeaCore::instance()->userManager()->userInfo(tokenInfo.username());
|
||||
ret.insert("username", userInfo.username());
|
||||
ret.insert("scopes", Types::scopesToStringList(userInfo.scopes()));
|
||||
}
|
||||
|
||||
// If the connection is locked down already (because of a previous failed attempt) and authentication failed
|
||||
// again, drop the client. He won't be able to reconnect until the lockdown timer runs out.
|
||||
// This will give at max 2 attempts to present a valid token per lockdown period.
|
||||
if (m_connectionLockdownTimer.isActive() && token.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Dropping client because of repeated bad user/password authentication.";
|
||||
TransportInterface *interface = reinterpret_cast<TransportInterface*>(property("transportInterface").toLongLong());
|
||||
interface->terminateClientConnection(context.clientId());
|
||||
}
|
||||
|
||||
if (token.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Staring connection lockdown timer";
|
||||
m_connectionLockdownTimer.start();
|
||||
}
|
||||
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
@ -380,30 +461,6 @@ JsonReply *JsonRPCServerImplementation::RequestPushButtonAuth(const QVariantMap
|
||||
return createReply(data);
|
||||
}
|
||||
|
||||
JsonReply *JsonRPCServerImplementation::Tokens(const QVariantMap ¶ms, const JsonContext &context) const
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
|
||||
TokenInfo tokenInfo = NymeaCore::instance()->userManager()->tokenInfo(context.token());
|
||||
QList<TokenInfo> tokens = NymeaCore::instance()->userManager()->tokens(tokenInfo.username());
|
||||
QVariantList retList;
|
||||
foreach (const TokenInfo &tokenInfo, tokens) {
|
||||
retList << pack(tokenInfo);
|
||||
}
|
||||
QVariantMap retMap;
|
||||
retMap.insert("tokenInfoList", retList);
|
||||
return createReply(retMap);
|
||||
}
|
||||
|
||||
JsonReply *JsonRPCServerImplementation::RemoveToken(const QVariantMap ¶ms)
|
||||
{
|
||||
QUuid tokenId = params.value("tokenId").toUuid();
|
||||
UserManager::UserError error = NymeaCore::instance()->userManager()->removeToken(tokenId);
|
||||
QVariantMap ret;
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(error));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
JsonReply *JsonRPCServerImplementation::SetupCloudConnection(const QVariantMap ¶ms)
|
||||
{
|
||||
if (NymeaCore::instance()->cloudManager()->connectionState() != CloudManager::CloudConnectionStateUnconfigured) {
|
||||
@ -543,56 +600,12 @@ void JsonRPCServerImplementation::sendUnauthorizedResponse(TransportInterface *i
|
||||
interface->sendData(clientId, data);
|
||||
}
|
||||
|
||||
QVariantMap JsonRPCServerImplementation::createWelcomeMessage(TransportInterface *interface, const QUuid &clientId) const
|
||||
{
|
||||
QVariantMap handshake;
|
||||
handshake.insert("server", "nymea");
|
||||
handshake.insert("name", NymeaCore::instance()->configuration()->serverName());
|
||||
handshake.insert("version", NYMEA_VERSION_STRING);
|
||||
handshake.insert("uuid", NymeaCore::instance()->configuration()->serverUuid().toString());
|
||||
// "language" is deprecated
|
||||
handshake.insert("language", m_clientLocales.value(clientId).name());
|
||||
handshake.insert("locale", m_clientLocales.value(clientId).name());
|
||||
handshake.insert("protocol version", JSON_PROTOCOL_VERSION);
|
||||
handshake.insert("initialSetupRequired", (interface->configuration().authenticationEnabled ? NymeaCore::instance()->userManager()->initRequired() : false));
|
||||
handshake.insert("authenticationRequired", interface->configuration().authenticationEnabled);
|
||||
handshake.insert("pushButtonAuthAvailable", NymeaCore::instance()->userManager()->pushButtonAuthAvailable());
|
||||
if (!m_experiences.isEmpty()) {
|
||||
QVariantList experiences;
|
||||
foreach (JsonHandler* handler, m_experiences.keys()) {
|
||||
QVariantMap experience;
|
||||
experience.insert("name", handler->name());
|
||||
experience.insert("version", m_experiences.value(handler));
|
||||
experiences.append(experience);
|
||||
}
|
||||
handshake.insert("experiences", experiences);
|
||||
}
|
||||
QVariantList cacheHashes;
|
||||
foreach (const QString &handlerName, m_handlers.keys()) {
|
||||
QHash<QString, QString> hashes = m_handlers.value(handlerName)->cacheHashes();
|
||||
foreach (const QString &hashName, hashes.keys()) {
|
||||
QVariantMap cacheHash;
|
||||
cacheHash.insert("method", handlerName + "." + hashName);
|
||||
cacheHash.insert("hash", hashes.value(hashName));
|
||||
cacheHashes.append(cacheHash);
|
||||
}
|
||||
}
|
||||
if (!cacheHashes.isEmpty()) {
|
||||
handshake.insert("cacheHashes", cacheHashes);
|
||||
}
|
||||
return handshake;
|
||||
}
|
||||
|
||||
void JsonRPCServerImplementation::setup()
|
||||
{
|
||||
registerHandler(this);
|
||||
registerHandler(new IntegrationsHandler(NymeaCore::instance()->thingManager(), this));
|
||||
registerHandler(new DeviceHandler(this));
|
||||
registerHandler(new ActionHandler(this));
|
||||
registerHandler(new RulesHandler(this));
|
||||
registerHandler(new EventHandler(this));
|
||||
registerHandler(new LoggingHandler(this));
|
||||
registerHandler(new StateHandler(this));
|
||||
registerHandler(new ConfigurationHandler(this));
|
||||
registerHandler(new NetworkManagerHandler(NymeaCore::instance()->networkManager(), this));
|
||||
registerHandler(new TagsHandler(this));
|
||||
@ -654,7 +667,8 @@ void JsonRPCServerImplementation::processJsonPacket(TransportInterface *interfac
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList commandList = message.value("method").toString().split('.');
|
||||
QString methodString = message.value("method").toString();
|
||||
QStringList commandList = methodString.split('.');
|
||||
if (commandList.count() != 2) {
|
||||
qCWarning(dcJsonRpc) << "Error parsing method.\nGot:" << message.value("method").toString() << "\nExpected: \"Namespace.method\"";
|
||||
sendErrorResponse(interface, clientId, commandId, QString("Error parsing method. Got: '%1'', Expected: 'Namespace.method'").arg(message.value("method").toString()));
|
||||
@ -666,23 +680,38 @@ void JsonRPCServerImplementation::processJsonPacket(TransportInterface *interfac
|
||||
// check if authentication is required for this transport
|
||||
if (m_interfaces.value(interface)) {
|
||||
QByteArray token = message.value("token").toByteArray();
|
||||
QStringList authExemptMethodsNoUser = {"JSONRPC.Introspect", "JSONRPC.Hello", "JSONRPC.RequestPushButtonAuth", "JSONRPC.CreateUser", "Users.RequestPushButtonAuth", "Users.CreateUser"};
|
||||
QStringList authExemptMethodsWithUser = {"JSONRPC.Introspect", "JSONRPC.Hello", "JSONRPC.Authenticate", "JSONRPC.RequestPushButtonAuth", "Users.Authenticate", "Users.RequestPushButtonAuth"};
|
||||
// if there is no user in the system yet, let's fail unless this is special method for authentication itself
|
||||
QStringList authExemptMethodsNoUser = {"JSONRPC.Introspect", "JSONRPC.Hello", "JSONRPC.RequestPushButtonAuth", "JSONRPC.CreateUser"};
|
||||
QStringList authExemptMethodsWithUser = {"JSONRPC.Introspect", "JSONRPC.Hello", "JSONRPC.Authenticate", "JSONRPC.RequestPushButtonAuth"};
|
||||
// if there is no user in the system yet, let's fail unless this is a special method for authentication itself
|
||||
if (NymeaCore::instance()->userManager()->initRequired()) {
|
||||
if (!authExemptMethodsNoUser.contains(targetNamespace + "." + method) && (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token))) {
|
||||
if (!authExemptMethodsNoUser.contains(methodString) && (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token))) {
|
||||
sendUnauthorizedResponse(interface, clientId, commandId, "Initial setup required. Call Users.CreateUser first.");
|
||||
qCWarning(dcJsonRpc()) << "Initial setup required but client does not call the setup. Dropping connection.";
|
||||
interface->terminateClientConnection(clientId);
|
||||
qCWarning(dcJsonRpc()) << "Staring connection lockdown timer";
|
||||
m_connectionLockdownTimer.start();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// ok, we have a user. if there isn't a valid token, let's fail unless this is a Authenticate, Introspect Hello call
|
||||
if (!authExemptMethodsWithUser.contains(targetNamespace + "." + method) && (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token))) {
|
||||
sendUnauthorizedResponse(interface, clientId, commandId, "Forbidden: Invalid token.");
|
||||
qCWarning(dcJsonRpc()) << "Client did not not present a valid token. Dropping connection.";
|
||||
interface->terminateClientConnection(clientId);
|
||||
return;
|
||||
if (!authExemptMethodsWithUser.contains(methodString)) {
|
||||
if (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token)) {
|
||||
sendUnauthorizedResponse(interface, clientId, commandId, "Forbidden: Invalid token.");
|
||||
qCWarning(dcJsonRpc()) << "Client did not not present a valid token. Dropping connection.";
|
||||
interface->terminateClientConnection(clientId);
|
||||
qCWarning(dcJsonRpc()) << "Staring connection lockdown timer";
|
||||
m_connectionLockdownTimer.start();
|
||||
return;
|
||||
}
|
||||
// Check if the user has the required permissions
|
||||
TokenInfo tokenInfo = NymeaCore::instance()->userManager()->tokenInfo(token);
|
||||
UserInfo userInfo = NymeaCore::instance()->userManager()->userInfo(tokenInfo.username());
|
||||
Types::PermissionScope methodScope = Types::scopeFromString(m_api.value("methods").toMap().value(methodString).toMap().value("permissionScope").toString());
|
||||
if (methodScope != Types::PermissionScopeNone && !userInfo.scopes().testFlag(Types::PermissionScopeAdmin) && !userInfo.scopes().testFlag(methodScope)) {
|
||||
qCWarning(dcJsonRpc()) << "Method" << methodString << "requires" << Types::scopeToString(methodScope) << "but client token has:" << Types::scopesToStringList(userInfo.scopes());
|
||||
sendErrorResponse(interface, clientId, commandId, "Permission denied.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1025,6 +1054,12 @@ void JsonRPCServerImplementation::clientConnected(const QUuid &clientId)
|
||||
qCDebug(dcJsonRpc()) << "Client connected with uuid" << clientId.toString();
|
||||
TransportInterface *interface = qobject_cast<TransportInterface *>(sender());
|
||||
|
||||
if (m_connectionLockdownTimer.isActive()) {
|
||||
qCWarning(dcJsonRpc()) << "Connection is locked down. Rejecting new client connection.";
|
||||
interface->terminateClientConnection(clientId);
|
||||
return;
|
||||
}
|
||||
|
||||
m_clientTransports.insert(clientId, interface);
|
||||
|
||||
// Initialize the connection locale to the settings default
|
||||
|
||||
@ -63,10 +63,8 @@ public:
|
||||
Q_INVOKABLE JsonReply *SetNotificationStatus(const QVariantMap ¶ms, const JsonContext &context);
|
||||
|
||||
Q_INVOKABLE JsonReply *CreateUser(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *Authenticate(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *Authenticate(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *RequestPushButtonAuth(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *Tokens(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
Q_INVOKABLE JsonReply *RemoveToken(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *SetupCloudConnection(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *SetupRemoteAccess(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *IsCloudConnected(const QVariantMap ¶ms);
|
||||
@ -90,7 +88,6 @@ private:
|
||||
void sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap ¶ms = QVariantMap(), const QString &deprecationWarning = QString());
|
||||
void sendErrorResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error);
|
||||
void sendUnauthorizedResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error);
|
||||
QVariantMap createWelcomeMessage(TransportInterface *interface, const QUuid &clientId) const;
|
||||
|
||||
void processJsonPacket(TransportInterface *interface, const QUuid &clientId, const QByteArray &data);
|
||||
|
||||
@ -129,6 +126,8 @@ private:
|
||||
|
||||
int m_notificationId;
|
||||
|
||||
QTimer m_connectionLockdownTimer;
|
||||
|
||||
QString formatAssertion(const QString &targetNamespace, const QString &method, QMetaMethod::MethodType methodType, JsonHandler *handler, const QVariantMap &data) const;
|
||||
};
|
||||
|
||||
|
||||
@ -179,7 +179,7 @@ JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const
|
||||
|
||||
QVariantList enumList = refDefinition.toList();
|
||||
if (!enumList.contains(value.toString())) {
|
||||
return Result(false, "Expected enum " + refName + " but got " + value.toJsonDocument().toJson());
|
||||
return Result(false, "Expected enum value for" + refName + " but got " + value.toString());
|
||||
}
|
||||
return Result(true);
|
||||
}
|
||||
@ -187,7 +187,7 @@ JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const
|
||||
QVariantMap flags = api.value("flags").toMap();
|
||||
if (flags.contains(refName)) {
|
||||
QVariant refDefinition = flags.value(refName);
|
||||
if (value.type() != QVariant::StringList) {
|
||||
if (value.type() != QVariant::List && value.type() != QVariant::StringList) {
|
||||
return Result(false, "Expected flags " + refName + " but got " + value.toString());
|
||||
}
|
||||
QString flagEnum = refDefinition.toList().first().toString();
|
||||
|
||||
@ -76,7 +76,6 @@ LoggingHandler::LoggingHandler(QObject *parent) :
|
||||
params.insert("o:eventTypes", QVariantList() << enumRef<Logging::LoggingEventType>());
|
||||
params.insert("o:typeIds", QVariantList() << enumValueName(Uuid));
|
||||
params.insert("o:thingIds", QVariantList() << enumValueName(Uuid));
|
||||
params.insert("d:o:deviceIds", QVariantList() << enumValueName(Uuid));
|
||||
params.insert("o:values", QVariantList() << enumValueName(Variant));
|
||||
params.insert("o:limit", enumValueName(Int));
|
||||
params.insert("o:offset", enumValueName(Int));
|
||||
@ -189,7 +188,6 @@ QVariantMap LoggingHandler::packLogEntry(const LogEntry &logEntry)
|
||||
logEntryMap.insert("typeId", logEntry.typeId());
|
||||
}
|
||||
logEntryMap.insert("thingId", logEntry.thingId());
|
||||
logEntryMap.insert("deviceId", logEntry.thingId()); // DEPRECATED
|
||||
logEntryMap.insert("value", LogValueTool::convertVariantToString(logEntry.value()));
|
||||
break;
|
||||
case Logging::LoggingSourceSystem:
|
||||
@ -251,13 +249,6 @@ LogFilter LoggingHandler::unpackLogFilter(const QVariantMap &logFilterMap)
|
||||
filter.addThingId(ThingId(thingId.toString()));
|
||||
}
|
||||
}
|
||||
// DEPRECATED
|
||||
if (logFilterMap.contains("deviceIds")) {
|
||||
QVariantList deviceIds = logFilterMap.value("deviceIds").toList();
|
||||
foreach (const QVariant &deviceId, deviceIds) {
|
||||
filter.addThingId(ThingId(deviceId.toString()));
|
||||
}
|
||||
}
|
||||
if (logFilterMap.contains("values")) {
|
||||
QVariantList values = logFilterMap.value("values").toList();
|
||||
foreach (const QVariant &value, values) {
|
||||
|
||||
@ -107,7 +107,7 @@ RulesHandler::RulesHandler(QObject *parent) :
|
||||
description = "Get the descriptions of all configured rules. If you need more information about a specific rule use the "
|
||||
"method Rules.GetRuleDetails.";
|
||||
returns.insert("ruleDescriptions", QVariantList() << objectRef("RuleDescription"));
|
||||
registerMethod("GetRules", description, params, returns);
|
||||
registerMethod("GetRules", description, params, returns, Types::PermissionScopeConfigureRules);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get details for the rule identified by ruleId";
|
||||
@ -166,8 +166,7 @@ RulesHandler::RulesHandler(QObject *parent) :
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Find a list of rules containing any of the given parameters.";
|
||||
params.insert("o:thingId", enumValueName(Uuid)); // TODO: remove "o:" from thingId once we drop deviceId support
|
||||
params.insert("d:o:deviceId", enumValueName(Uuid));
|
||||
params.insert("thingId", enumValueName(Uuid));
|
||||
returns.insert("ruleIds", QVariantList() << enumValueName(Uuid));
|
||||
registerMethod("FindRules", description, params, returns);
|
||||
|
||||
@ -301,10 +300,7 @@ JsonReply* RulesHandler::RemoveRule(const QVariantMap ¶ms)
|
||||
|
||||
JsonReply *RulesHandler::FindRules(const QVariantMap ¶ms)
|
||||
{
|
||||
ThingId thingId = ThingId(params.value("deviceId").toString()); // DEPRECATED
|
||||
if (params.contains("thingId")) {
|
||||
thingId = ThingId(params.value("thingId").toString());
|
||||
}
|
||||
ThingId thingId = ThingId(params.value("thingId").toString());
|
||||
QList<RuleId> rules = NymeaCore::instance()->ruleEngine()->findRules(thingId);
|
||||
|
||||
QVariantList rulesList;
|
||||
|
||||
@ -1,97 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/*!
|
||||
\class nymeaserver::StateHandler
|
||||
\brief This subclass of \l{JsonHandler} processes the JSON requests for the \tt States namespace of the JSON-RPC API.
|
||||
|
||||
\ingroup json
|
||||
\inmodule core
|
||||
|
||||
This \l{JsonHandler} will be created in the \l{JsonRPCServer} and used to handle JSON-RPC requests
|
||||
for the \tt {States} namespace of the API.
|
||||
|
||||
\sa State, JsonHandler, JsonRPCServer
|
||||
*/
|
||||
|
||||
#include "statehandler.h"
|
||||
#include "nymeacore.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
#include "devicehandler.h"
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
/*! Constructs a new \l{StateHandler} with the given \a parent. */
|
||||
StateHandler::StateHandler(QObject *parent) :
|
||||
JsonHandler(parent)
|
||||
{
|
||||
registerEnum<Types::Unit>();
|
||||
registerEnum<Types::IOType>();
|
||||
registerObject<State>();
|
||||
registerObject<StateType>();
|
||||
|
||||
// Methods
|
||||
QString description; QVariantMap params; QVariantMap returns;
|
||||
description = "Get the StateType for the given stateTypeId.";
|
||||
params.insert("stateTypeId", enumValueName(Uuid));
|
||||
returns.insert("deviceError", enumRef<Device::DeviceError>());
|
||||
returns.insert("o:stateType", objectRef<StateType>());
|
||||
registerMethod("GetStateType", description, params, returns, "Please use the Integrations namespace instead.");
|
||||
}
|
||||
|
||||
/*! Returns the name of the \l{StateHandler}. In this case \b States.*/
|
||||
QString StateHandler::name() const
|
||||
{
|
||||
return "States";
|
||||
}
|
||||
|
||||
JsonReply* StateHandler::GetStateType(const QVariantMap ¶ms, const JsonContext &context) const
|
||||
{
|
||||
qCDebug(dcJsonRpc) << "asked for state type" << params;
|
||||
StateTypeId stateTypeId(params.value("stateTypeId").toString());
|
||||
foreach (const ThingClass &deviceClass, NymeaCore::instance()->thingManager()->supportedThings()) {
|
||||
foreach (const StateType &stateType, deviceClass.stateTypes()) {
|
||||
if (stateType.id() == stateTypeId) {
|
||||
QVariantMap data;
|
||||
data.insert("deviceError", enumValueName<Thing::ThingError>(Thing::ThingErrorNoError).replace("ThingError", "DeviceError"));
|
||||
StateType translatedStateType = stateType;
|
||||
translatedStateType.setDisplayName(NymeaCore::instance()->thingManager()->translate(deviceClass.pluginId(), stateType.displayName(), context.locale()));
|
||||
data.insert("stateType", pack(translatedStateType));
|
||||
return createReply(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
QVariantMap data;
|
||||
data.insert("deviceError", enumValueName<Thing::ThingError>(Thing::ThingErrorStateTypeNotFound).replace("ThingError", "DeviceError"));
|
||||
return createReply(data);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef STATEHANDLER_H
|
||||
#define STATEHANDLER_H
|
||||
|
||||
#include "jsonrpc/jsonhandler.h"
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
class StateHandler : public JsonHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit StateHandler(QObject *parent = nullptr);
|
||||
QString name() const override;
|
||||
|
||||
Q_INVOKABLE JsonReply *GetStateType(const QVariantMap ¶ms, const JsonContext &context) const;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // EVENTHANDLER_H
|
||||
@ -49,7 +49,6 @@ TagsHandler::TagsHandler(QObject *parent) : JsonHandler(parent)
|
||||
"Tags can be filtered by a thingID, a ruleId, an appId, a tagId or a combination of any (however, "
|
||||
"combining thingId and ruleId will return an empty result set).";
|
||||
params.insert("o:thingId", enumValueName(Uuid));
|
||||
params.insert("d:o:deviceId", enumValueName(Uuid));
|
||||
params.insert("o:ruleId", enumValueName(Uuid));
|
||||
params.insert("o:appId", enumValueName(String));
|
||||
params.insert("o:tagId", enumValueName(String));
|
||||
@ -108,10 +107,6 @@ JsonReply *TagsHandler::GetTags(const QVariantMap ¶ms) const
|
||||
if (params.contains("thingId") && params.value("thingId").toUuid() != tag.thingId()) {
|
||||
continue;
|
||||
}
|
||||
if (params.contains("deviceId") && params.value("deviceId").toUuid() != tag.thingId()) {
|
||||
// nymea < 0.19
|
||||
continue;
|
||||
}
|
||||
if (params.contains("ruleId") && params.value("ruleId").toUuid() != tag.ruleId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -40,50 +40,23 @@ UsersHandler::UsersHandler(UserManager *userManager, QObject *parent):
|
||||
JsonHandler(parent),
|
||||
m_userManager(userManager)
|
||||
{
|
||||
registerObject<UserInfo>();
|
||||
registerFlag<Types::PermissionScope, Types::PermissionScopes>();
|
||||
registerObject<UserInfo, UserInfoList>();
|
||||
registerObject<TokenInfo, TokenInfoList>();
|
||||
|
||||
QVariantMap params, returns;
|
||||
QString description;
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Create a new user in the API. Currently this is only allowed to be called once when a new nymea instance is set up. Call Authenticate after this to obtain a device token for this user.";
|
||||
description = "Create a new user in the API with the given username and password. Use scopes to define the permissions for the new user. If no scopes are given, this user will be an admin user. Call Authenticate after this to obtain a device token for this user.";
|
||||
params.insert("username", enumValueName(String));
|
||||
params.insert("password", enumValueName(String));
|
||||
params.insert("o:email", enumValueName(String));
|
||||
params.insert("o:displayName", enumValueName(String));
|
||||
params.insert("o:scopes", flagRef<Types::PermissionScopes>());
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("CreateUser", description, params, returns);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Authenticate a client to the api via user & password challenge. Provide "
|
||||
"a device name which allows the user to identify the client and revoke the token in case "
|
||||
"the device is lost or stolen. This will return a new token to be used to authorize a "
|
||||
"client at the API.";
|
||||
params.insert("username", enumValueName(String));
|
||||
params.insert("password", enumValueName(String));
|
||||
params.insert("deviceName", enumValueName(String));
|
||||
returns.insert("success", enumValueName(Bool));
|
||||
returns.insert("o:token", enumValueName(String));
|
||||
registerMethod("Authenticate", description, params, returns);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Authenticate a client to the api via Push Button method. "
|
||||
"Provide a device name which allows the user to identify the client and revoke the "
|
||||
"token in case the device is lost or stolen. If push button hardware is available, "
|
||||
"this will return with success and start listening for push button presses. When the "
|
||||
"push button is pressed, the PushButtonAuthFinished notification will be sent to the "
|
||||
"requesting client. The procedure will be cancelled when the connection is interrupted. "
|
||||
"If another client requests push button authentication while a procedure is still going "
|
||||
"on, the second call will take over and the first one will be notified by the "
|
||||
"PushButtonAuthFinished signal about the error. The application should make it clear "
|
||||
"to the user to not press the button when the procedure fails as this can happen for 2 "
|
||||
"reasons: a) a second user is trying to auth at the same time and only the currently "
|
||||
"active user should press the button or b) it might indicate an attacker trying to take "
|
||||
"over and snooping in for tokens.";
|
||||
params.insert("deviceName", enumValueName(String));
|
||||
returns.insert("success", enumValueName(Bool));
|
||||
returns.insert("transactionId", enumValueName(Int));
|
||||
registerMethod("RequestPushButtonAuth", description, params, returns);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Change the password for the currently logged in user.";
|
||||
params.insert("newPassword", enumValueName(String));
|
||||
@ -94,7 +67,7 @@ UsersHandler::UsersHandler(UserManager *userManager, QObject *parent):
|
||||
description = "Get info about the current token (the currently logged in user).";
|
||||
returns.insert("o:userInfo", objectRef<UserInfo>());
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("GetUserInfo", description, params, returns);
|
||||
registerMethod("GetUserInfo", description, params, returns, Types::PermissionScopeNone);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get all the tokens for the current user.";
|
||||
@ -108,7 +81,48 @@ UsersHandler::UsersHandler(UserManager *userManager, QObject *parent):
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("RemoveToken", description, params, returns);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Return a list of all users in the system.";
|
||||
returns.insert("users", objectRef<UserInfoList>());
|
||||
registerMethod("GetUsers", description, params, returns);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Remove a user from the system.";
|
||||
params.insert("username", enumValueName(String));
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("RemoveUser", description, params, returns);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Set the permissions (scopes) for a given user.";
|
||||
params.insert("username", enumValueName(String));
|
||||
params.insert("scopes", flagRef<Types::PermissionScopes>());
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("SetUserScopes", description, params, returns);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Change user info. If username is given, info for the respective user is changed, otherwise the current user info is edited. Requires admin permissions to edit user info other than the own.";
|
||||
params.insert("o:username", enumValueName(String));
|
||||
params.insert("o:displayName", enumValueName(String));
|
||||
params.insert("o:email", enumValueName(String));
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("SetUserInfo", description, params, returns);
|
||||
|
||||
// Notifications
|
||||
params.clear();
|
||||
description = "Emitted when a user is added to the system.";
|
||||
params.insert("userInfo", objectRef<UserInfo>());
|
||||
registerNotification("UserAdded", description, params);
|
||||
|
||||
params.clear();
|
||||
description = "Emitted when a user is removed from the system.";
|
||||
params.insert("username", enumValueName(String));
|
||||
registerNotification("UserRemoved", description, params);
|
||||
|
||||
params.clear();
|
||||
description = "Emitted whenever a user is changed.";
|
||||
params.insert("userInfo", objectRef<UserInfo>());
|
||||
registerNotification("UserChanged", description, params);
|
||||
|
||||
params.clear();
|
||||
description = "Emitted when a push button authentication reaches final state. NOTE: This notification is "
|
||||
"special. It will only be emitted to connections that did actively request a push button "
|
||||
@ -118,7 +132,21 @@ UsersHandler::UsersHandler(UserManager *userManager, QObject *parent):
|
||||
params.insert("o:token", enumValueName(String));
|
||||
registerNotification("PushButtonAuthFinished", description, params);
|
||||
|
||||
connect(m_userManager, &UserManager::pushButtonAuthFinished, this, &UsersHandler::onPushButtonAuthFinished);
|
||||
connect(m_userManager, &UserManager::userAdded, this, [this](const QString &username){
|
||||
QVariantMap params;
|
||||
params.insert("userInfo", pack(m_userManager->userInfo(username)));
|
||||
emit UserAdded(params);
|
||||
});
|
||||
connect(m_userManager, &UserManager::userChanged, this, [this](const QString &username){
|
||||
QVariantMap params;
|
||||
params.insert("userInfo", pack(m_userManager->userInfo(username)));
|
||||
emit UserChanged(params);
|
||||
});
|
||||
connect(m_userManager, &UserManager::userRemoved, this, [this](const QString &username){
|
||||
QVariantMap params;
|
||||
params.insert("username", username);
|
||||
emit UserRemoved(params);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@ -131,8 +159,12 @@ JsonReply *UsersHandler::CreateUser(const QVariantMap ¶ms)
|
||||
{
|
||||
QString username = params.value("username").toString();
|
||||
QString password = params.value("password").toString();
|
||||
QString email = params.value("email").toString();
|
||||
QString displayName = params.value("displayName").toString();
|
||||
QStringList scopesList = params.value("scopes", Types::scopesToStringList(Types::PermissionScopeAdmin)).toStringList();
|
||||
Types::PermissionScopes scopes = Types::scopesFromStringList(scopesList);
|
||||
|
||||
UserManager::UserError status = m_userManager->createUser(username, password);
|
||||
UserManager::UserError status = m_userManager->createUser(username, password, email, displayName, scopes);
|
||||
|
||||
QVariantMap returns;
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(status));
|
||||
@ -157,42 +189,14 @@ JsonReply *UsersHandler::ChangePassword(const QVariantMap ¶ms, const JsonCon
|
||||
}
|
||||
|
||||
QString newPassword = params.value("newPassword").toString();
|
||||
QString username = m_userManager->userInfo(currentToken).username();
|
||||
|
||||
UserManager::UserError status = m_userManager->changePassword(username, newPassword);
|
||||
TokenInfo tokenInfo = m_userManager->tokenInfo(currentToken);
|
||||
|
||||
UserManager::UserError status = m_userManager->changePassword(tokenInfo.username(), newPassword);
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(status));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::Authenticate(const QVariantMap ¶ms)
|
||||
{
|
||||
QString username = params.value("username").toString();
|
||||
QString password = params.value("password").toString();
|
||||
QString deviceName = params.value("deviceName").toString();
|
||||
|
||||
QByteArray token = m_userManager->authenticate(username, password, deviceName);
|
||||
QVariantMap ret;
|
||||
ret.insert("success", !token.isEmpty());
|
||||
if (!token.isEmpty()) {
|
||||
ret.insert("token", token);
|
||||
}
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::RequestPushButtonAuth(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
QString deviceName = params.value("deviceName").toString();
|
||||
|
||||
int transactionId = m_userManager->requestPushButtonAuth(deviceName);
|
||||
m_pushButtonTransactions.insert(transactionId, context.clientId());
|
||||
|
||||
QVariantMap data;
|
||||
data.insert("transactionId", transactionId);
|
||||
// TODO: return false if pushbutton auth is disabled in settings
|
||||
data.insert("success", true);
|
||||
return createReply(data);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::GetUserInfo(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
@ -200,7 +204,7 @@ JsonReply *UsersHandler::GetUserInfo(const QVariantMap ¶ms, const JsonContex
|
||||
|
||||
QByteArray currentToken = context.token();
|
||||
if (currentToken.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot get user info form an unauthenticated connection";
|
||||
qCWarning(dcJsonRpc()) << "Cannot get user info from an unauthenticated connection";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
}
|
||||
@ -211,7 +215,9 @@ JsonReply *UsersHandler::GetUserInfo(const QVariantMap ¶ms, const JsonContex
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
UserInfo userInfo = m_userManager->userInfo(currentToken);
|
||||
TokenInfo tokenInfo = m_userManager->tokenInfo(currentToken);
|
||||
|
||||
UserInfo userInfo = m_userManager->userInfo(tokenInfo.username());
|
||||
ret.insert("userInfo", pack(userInfo));
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorNoError));
|
||||
return createReply(ret);
|
||||
@ -224,7 +230,7 @@ JsonReply *UsersHandler::GetTokens(const QVariantMap ¶ms, const JsonContext
|
||||
|
||||
QByteArray currentToken = context.token();
|
||||
if (currentToken.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot fetch tokens form an unauthenticated connection";
|
||||
qCWarning(dcJsonRpc()) << "Cannot fetch tokens for an unauthenticated connection";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
}
|
||||
@ -236,7 +242,7 @@ JsonReply *UsersHandler::GetTokens(const QVariantMap ¶ms, const JsonContext
|
||||
}
|
||||
|
||||
TokenInfo tokenInfo = m_userManager->tokenInfo(currentToken);
|
||||
qCDebug(dcJsonRpc()) << "Fetching tokens for user" << tokenInfo.username();
|
||||
qCDebug(dcJsonRpc()) << "Fetching tokens for user" << currentToken << tokenInfo.username();
|
||||
QList<TokenInfo> tokens = m_userManager->tokens(tokenInfo.username());
|
||||
QVariantList retList;
|
||||
foreach (const TokenInfo &tokenInfo, tokens) {
|
||||
@ -285,24 +291,70 @@ JsonReply *UsersHandler::RemoveToken(const QVariantMap ¶ms, const JsonContex
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
void UsersHandler::onPushButtonAuthFinished(int transactionId, bool success, const QByteArray &token)
|
||||
JsonReply *UsersHandler::GetUsers(const QVariantMap ¶ms)
|
||||
{
|
||||
Q_UNUSED(success)
|
||||
Q_UNUSED(token)
|
||||
QUuid clientId = m_pushButtonTransactions.take(transactionId);
|
||||
if (clientId.isNull()) {
|
||||
qCDebug(dcJsonRpc()) << "Received a PushButton reply but wasn't expecting it.";
|
||||
return;
|
||||
Q_UNUSED(params)
|
||||
QVariantMap reply;
|
||||
reply.insert("users", pack(m_userManager->users()));
|
||||
return createReply(reply);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::RemoveUser(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
Q_UNUSED(context)
|
||||
QString username = params.value("username").toString();
|
||||
QVariantMap returns;
|
||||
UserManager::UserError error = m_userManager->removeUser(username);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(error));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::SetUserScopes(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
Q_UNUSED(context)
|
||||
QString username = params.value("username").toString();
|
||||
Types::PermissionScopes scopes = Types::scopesFromStringList(params.value("scopes").toStringList());
|
||||
UserManager::UserError error = m_userManager->setUserScopes(username, scopes);
|
||||
QVariantMap returns;
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(error));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::SetUserInfo(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
QVariantMap ret;
|
||||
|
||||
TokenInfo callingTokenInfo = m_userManager->tokenInfo(context.token());
|
||||
QString username;
|
||||
|
||||
if (params.contains("username")) {
|
||||
username = params.value("username").toString();
|
||||
} else {
|
||||
username = callingTokenInfo.username();
|
||||
}
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("transactionId", transactionId);
|
||||
params.insert("success", success);
|
||||
if (success) {
|
||||
params.insert("token", token);
|
||||
if (callingTokenInfo.username() != username && !m_userManager->userInfo(callingTokenInfo.username()).scopes().testFlag(Types::PermissionScopeAdmin)) {
|
||||
ret.insert("error", enumValueName(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
emit PushButtonAuthFinished(clientId, params);
|
||||
UserInfo changedUserInfo = m_userManager->userInfo(username);
|
||||
|
||||
QString email;
|
||||
if (params.contains("email")) {
|
||||
email = params.value("email").toString();
|
||||
} else {
|
||||
email = changedUserInfo.email();
|
||||
}
|
||||
QString displayName;
|
||||
if (params.contains("displayName")) {
|
||||
displayName = params.value("displayName").toString();
|
||||
} else {
|
||||
displayName = changedUserInfo.displayName();
|
||||
}
|
||||
UserManager::UserError status = m_userManager->setUserInfo(username, email, displayName);
|
||||
ret.insert("error", enumValueName(status));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -49,17 +49,18 @@ public:
|
||||
|
||||
Q_INVOKABLE JsonReply *CreateUser(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *ChangePassword(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *Authenticate(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *RequestPushButtonAuth(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *GetUserInfo(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *GetTokens(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *RemoveToken(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *GetUsers(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *RemoveUser(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *SetUserScopes(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *SetUserInfo(const QVariantMap ¶ms, const JsonContext &context);
|
||||
|
||||
signals:
|
||||
void PushButtonAuthFinished(const QUuid &clientId, const QVariantMap ¶ms);
|
||||
|
||||
private slots:
|
||||
void onPushButtonAuthFinished(int transactionId, bool success, const QByteArray &token);
|
||||
void UserAdded(const QVariantMap ¶ms);
|
||||
void UserRemoved(const QVariantMap ¶ms);
|
||||
void UserChanged(const QVariantMap ¶ms);
|
||||
|
||||
private:
|
||||
UserManager *m_userManager = nullptr;
|
||||
|
||||
@ -103,11 +103,7 @@ HEADERS += nymeacore.h \
|
||||
jsonrpc/jsonrpcserverimplementation.h \
|
||||
jsonrpc/jsonvalidator.h \
|
||||
jsonrpc/integrationshandler.h \
|
||||
jsonrpc/devicehandler.h \
|
||||
jsonrpc/ruleshandler.h \
|
||||
jsonrpc/actionhandler.h \
|
||||
jsonrpc/eventhandler.h \
|
||||
jsonrpc/statehandler.h \
|
||||
jsonrpc/logginghandler.h \
|
||||
jsonrpc/configurationhandler.h \
|
||||
jsonrpc/networkmanagerhandler.h \
|
||||
@ -122,6 +118,7 @@ HEADERS += nymeacore.h \
|
||||
logging/logentry.h \
|
||||
logging/logvaluetool.h \
|
||||
time/timemanager.h \
|
||||
usermanager/userautorizer.h \
|
||||
usermanager/userinfo.h \
|
||||
usermanager/usermanager.h \
|
||||
usermanager/tokeninfo.h \
|
||||
@ -197,11 +194,7 @@ SOURCES += nymeacore.cpp \
|
||||
jsonrpc/jsonrpcserverimplementation.cpp \
|
||||
jsonrpc/jsonvalidator.cpp \
|
||||
jsonrpc/integrationshandler.cpp \
|
||||
jsonrpc/devicehandler.cpp \
|
||||
jsonrpc/ruleshandler.cpp \
|
||||
jsonrpc/actionhandler.cpp \
|
||||
jsonrpc/eventhandler.cpp \
|
||||
jsonrpc/statehandler.cpp \
|
||||
jsonrpc/logginghandler.cpp \
|
||||
jsonrpc/configurationhandler.cpp \
|
||||
jsonrpc/networkmanagerhandler.cpp \
|
||||
@ -215,6 +208,7 @@ SOURCES += nymeacore.cpp \
|
||||
logging/logentry.cpp \
|
||||
logging/logvaluetool.cpp \
|
||||
time/timemanager.cpp \
|
||||
usermanager/userautorizer.cpp \
|
||||
usermanager/userinfo.cpp \
|
||||
usermanager/usermanager.cpp \
|
||||
usermanager/tokeninfo.cpp \
|
||||
|
||||
@ -48,7 +48,6 @@ class LogEntry
|
||||
Q_PROPERTY(Logging::LoggingSource source READ source)
|
||||
Q_PROPERTY(QUuid typeId READ typeId USER true)
|
||||
Q_PROPERTY(QUuid thingId READ thingId USER true)
|
||||
Q_PROPERTY(QUuid deviceId READ thingId USER true REVISION 1)
|
||||
Q_PROPERTY(QVariant value READ value USER true)
|
||||
Q_PROPERTY(bool active READ active USER true)
|
||||
Q_PROPERTY(Logging::LoggingEventType eventType READ eventType USER true)
|
||||
|
||||
@ -40,7 +40,6 @@ class LIBNYMEA_EXPORT RuleAction
|
||||
{
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QUuid thingId READ thingId WRITE setThingId USER true)
|
||||
Q_PROPERTY(QUuid deviceId READ thingId WRITE setThingId USER true REVISION 1)
|
||||
Q_PROPERTY(QUuid actionTypeId READ actionTypeId WRITE setActionTypeId USER true)
|
||||
Q_PROPERTY(QString interface READ interface WRITE setInterface USER true)
|
||||
Q_PROPERTY(QString interfaceAction READ interfaceAction WRITE setInterfaceAction USER true)
|
||||
|
||||
@ -48,7 +48,6 @@ class LIBNYMEA_EXPORT RuleActionParam
|
||||
Q_PROPERTY(QUuid eventTypeId READ eventTypeId WRITE setEventTypeId USER true)
|
||||
Q_PROPERTY(QUuid eventParamTypeId READ eventParamTypeId WRITE setEventParamTypeId USER true)
|
||||
Q_PROPERTY(QUuid stateThingId READ stateThingId WRITE setStateThingId USER true)
|
||||
Q_PROPERTY(QUuid stateDeviceId READ stateThingId WRITE setStateThingId USER true REVISION 1)
|
||||
Q_PROPERTY(QUuid stateTypeId READ stateTypeId WRITE setStateTypeId USER true)
|
||||
|
||||
public:
|
||||
|
||||
@ -57,12 +57,9 @@ QtMessageHandler ScriptEngine::s_upstreamMessageHandler;
|
||||
QLoggingCategory::CategoryFilter ScriptEngine::s_oldCategoryFilter = nullptr;
|
||||
QMutex ScriptEngine::s_loggerMutex;
|
||||
|
||||
ScriptEngine::ScriptEngine(ThingManager *deviceManager, QObject *parent) : QObject(parent),
|
||||
m_deviceManager(deviceManager)
|
||||
ScriptEngine::ScriptEngine(ThingManager *thingManager, QObject *parent) : QObject(parent),
|
||||
m_thingManager(thingManager)
|
||||
{
|
||||
qmlRegisterType<ScriptEvent>("nymea", 1, 0, "DeviceEvent");
|
||||
qmlRegisterType<ScriptAction>("nymea", 1, 0, "DeviceAction");
|
||||
qmlRegisterType<ScriptState>("nymea", 1, 0, "DeviceState");
|
||||
qmlRegisterType<ScriptEvent>("nymea", 1, 0, "ThingEvent");
|
||||
qmlRegisterType<ScriptAction>("nymea", 1, 0, "ThingAction");
|
||||
qmlRegisterType<ScriptState>("nymea", 1, 0, "ThingState");
|
||||
@ -71,7 +68,7 @@ ScriptEngine::ScriptEngine(ThingManager *deviceManager, QObject *parent) : QObje
|
||||
qmlRegisterType<ScriptAlarm>("nymea", 1, 0, "Alarm");
|
||||
|
||||
m_engine = new QQmlEngine(this);
|
||||
m_engine->setProperty("thingManager", reinterpret_cast<quint64>(m_deviceManager));
|
||||
m_engine->setProperty("thingManager", reinterpret_cast<quint64>(m_thingManager));
|
||||
|
||||
// Don't automatically print script warnings (that is, runtime errors, *not* console.warn() messages)
|
||||
// to stdout as they'd end up on the "default" logging category.
|
||||
|
||||
@ -75,7 +75,7 @@ public:
|
||||
QByteArray content;
|
||||
};
|
||||
|
||||
explicit ScriptEngine(ThingManager *deviceManager, QObject *parent = nullptr);
|
||||
explicit ScriptEngine(ThingManager *thingManager, QObject *parent = nullptr);
|
||||
~ScriptEngine();
|
||||
|
||||
Scripts scripts();
|
||||
@ -102,7 +102,7 @@ private:
|
||||
|
||||
void onScriptMessage(QtMsgType type, const QMessageLogContext &context, const QString &message);
|
||||
private:
|
||||
ThingManager *m_deviceManager = nullptr;
|
||||
ThingManager *m_thingManager = nullptr;
|
||||
QQmlEngine *m_engine = nullptr;
|
||||
|
||||
QHash<QUuid, Script*> m_scripts;
|
||||
|
||||
@ -116,6 +116,7 @@ void TcpServer::terminateClientConnection(const QUuid &clientId)
|
||||
{
|
||||
QTcpSocket *client = m_clientList.value(clientId);
|
||||
if (client) {
|
||||
client->flush();
|
||||
client->close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,6 +121,7 @@ void WebSocketServer::terminateClientConnection(const QUuid &clientId)
|
||||
{
|
||||
QWebSocket *client = m_clientList.value(clientId);
|
||||
if (client) {
|
||||
client->flush();
|
||||
client->close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +44,6 @@ class Tag
|
||||
Q_PROPERTY(QString appId READ appId WRITE setAppId)
|
||||
Q_PROPERTY(QString tagId READ tagId WRITE setTagId)
|
||||
Q_PROPERTY(QUuid thingId READ thingId WRITE setThingId USER true)
|
||||
Q_PROPERTY(QUuid deviceId READ thingId WRITE setThingId USER true REVISION 1)
|
||||
Q_PROPERTY(QUuid ruleId READ ruleId WRITE setRuleId USER true)
|
||||
Q_PROPERTY(QString value READ value WRITE setValue USER true)
|
||||
public:
|
||||
|
||||
6
libnymea-core/usermanager/userautorizer.cpp
Normal file
6
libnymea-core/usermanager/userautorizer.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include "userautorizer.h"
|
||||
|
||||
UserAutorizer::UserAutorizer(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
16
libnymea-core/usermanager/userautorizer.h
Normal file
16
libnymea-core/usermanager/userautorizer.h
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef USERAUTORIZER_H
|
||||
#define USERAUTORIZER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class UserAutorizer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UserAutorizer(QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
|
||||
};
|
||||
|
||||
#endif // USERAUTORIZER_H
|
||||
@ -1,5 +1,9 @@
|
||||
#include "userinfo.h"
|
||||
|
||||
#include <QMetaEnum>
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
UserInfo::UserInfo()
|
||||
{
|
||||
|
||||
@ -20,3 +24,45 @@ void UserInfo::setUsername(const QString &username)
|
||||
{
|
||||
m_username = username;
|
||||
}
|
||||
|
||||
QString UserInfo::email()
|
||||
{
|
||||
return m_email;
|
||||
}
|
||||
|
||||
void UserInfo::setEmail(const QString &email)
|
||||
{
|
||||
m_email = email;
|
||||
}
|
||||
|
||||
QString UserInfo::displayName() const
|
||||
{
|
||||
return m_displayName;
|
||||
}
|
||||
|
||||
void UserInfo::setDisplayName(const QString &displayName)
|
||||
{
|
||||
m_displayName = displayName;
|
||||
}
|
||||
|
||||
Types::PermissionScopes UserInfo::scopes() const
|
||||
{
|
||||
return m_scopes;
|
||||
}
|
||||
|
||||
void UserInfo::setScopes(Types::PermissionScopes scopes)
|
||||
{
|
||||
m_scopes = scopes;
|
||||
}
|
||||
|
||||
QVariant UserInfoList::get(int index) const
|
||||
{
|
||||
return QVariant::fromValue(at(index));
|
||||
}
|
||||
|
||||
void UserInfoList::put(const QVariant &variant)
|
||||
{
|
||||
append(variant.value<UserInfo>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -33,11 +33,18 @@
|
||||
|
||||
#include <QUuid>
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include "typeutils.h"
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
class UserInfo
|
||||
{
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QString username READ username)
|
||||
Q_PROPERTY(QString email READ email)
|
||||
Q_PROPERTY(QString displayName READ displayName)
|
||||
Q_PROPERTY(Types::PermissionScopes scopes READ scopes)
|
||||
|
||||
public:
|
||||
UserInfo();
|
||||
@ -46,8 +53,29 @@ public:
|
||||
QString username() const;
|
||||
void setUsername(const QString &username);
|
||||
|
||||
QString email();
|
||||
void setEmail(const QString &email);
|
||||
|
||||
QString displayName() const;
|
||||
void setDisplayName(const QString &displayName);
|
||||
|
||||
Types::PermissionScopes scopes() const;
|
||||
void setScopes(Types::PermissionScopes scopes);
|
||||
|
||||
private:
|
||||
QString m_username;
|
||||
QString m_email;
|
||||
QString m_displayName;
|
||||
Types::PermissionScopes m_scopes = Types::PermissionScopeNone;
|
||||
};
|
||||
|
||||
class UserInfoList: public QList<UserInfo>
|
||||
{
|
||||
Q_GADGET
|
||||
Q_PROPERTY(int count READ count)
|
||||
public:
|
||||
Q_INVOKABLE QVariant get(int index) const;
|
||||
Q_INVOKABLE void put(const QVariant &variant);
|
||||
};
|
||||
}
|
||||
#endif // USERINFO_H
|
||||
|
||||
@ -77,6 +77,7 @@
|
||||
#include <QUuid>
|
||||
#include <QCryptographicHash>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlResult>
|
||||
#include <QVariant>
|
||||
#include <QSqlError>
|
||||
#include <QRegExpValidator>
|
||||
@ -105,7 +106,7 @@ UserManager::UserManager(const QString &dbName, QObject *parent):
|
||||
if (QFileInfo(m_db.databaseName()).exists()) {
|
||||
rotate(m_db.databaseName());
|
||||
if (!initDB()) {
|
||||
qCWarning(dcLogEngine()) << "Error fixing user database. Giving up. Users can't be stored.";
|
||||
qCWarning(dcUserManager()) << "Error fixing user database. Giving up. Users can't be stored.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -133,19 +134,23 @@ bool UserManager::initRequired() const
|
||||
}
|
||||
|
||||
/*! Returns the list of user names for this UserManager. */
|
||||
QStringList UserManager::users() const
|
||||
UserInfoList UserManager::users() const
|
||||
{
|
||||
QString userQuery("SELECT username FROM users;");
|
||||
QString userQuery("SELECT * FROM users;");
|
||||
QSqlQuery result = m_db.exec(userQuery);
|
||||
QStringList ret;
|
||||
UserInfoList users;
|
||||
while (result.next()) {
|
||||
ret << result.value("username").toString();
|
||||
UserInfo info = UserInfo(result.value("username").toString());
|
||||
info.setEmail(result.value("email").toString());
|
||||
info.setDisplayName(result.value("displayName").toString());
|
||||
info.setScopes(Types::scopesFromStringList(result.value("scopes").toString().split(',')));
|
||||
users.append(info);
|
||||
}
|
||||
return ret;
|
||||
return users;
|
||||
}
|
||||
|
||||
/*! Creates a new user with the given \a username and \a password. Returns the \l UserError to inform about the result. */
|
||||
UserManager::UserError UserManager::createUser(const QString &username, const QString &password)
|
||||
UserManager::UserError UserManager::createUser(const QString &username, const QString &password, const QString &email, const QString &displayName, Types::PermissionScopes scopes)
|
||||
{
|
||||
if (!validateUsername(username)) {
|
||||
qCWarning(dcUserManager) << "Error creating user. Invalid username:" << username;
|
||||
@ -157,24 +162,33 @@ UserManager::UserError UserManager::createUser(const QString &username, const QS
|
||||
return UserErrorBadPassword;
|
||||
}
|
||||
|
||||
QString checkForDuplicateUserQuery = QString("SELECT * FROM users WHERE lower(username) = \"%1\";").arg(username.toLower());
|
||||
QSqlQuery result = m_db.exec(checkForDuplicateUserQuery);
|
||||
if (result.first()) {
|
||||
QSqlQuery checkForDuplicateUserQuery(m_db);
|
||||
checkForDuplicateUserQuery.prepare("SELECT * FROM users WHERE lower(username) = ?;");
|
||||
checkForDuplicateUserQuery.addBindValue(username.toLower());
|
||||
checkForDuplicateUserQuery.exec();
|
||||
if (checkForDuplicateUserQuery.first()) {
|
||||
qCWarning(dcUserManager) << "Username already in use";
|
||||
return UserErrorDuplicateUserId;
|
||||
}
|
||||
|
||||
QByteArray salt = QUuid::createUuid().toString().remove(QRegExp("[{}]")).toUtf8();
|
||||
QByteArray hashedPassword = QCryptographicHash::hash(QString(password + salt).toUtf8(), QCryptographicHash::Sha512).toBase64();
|
||||
QString queryString = QString("INSERT INTO users(username, password, salt) values(\"%1\", \"%2\", \"%3\");")
|
||||
.arg(username.toLower())
|
||||
.arg(QString::fromUtf8(hashedPassword))
|
||||
.arg(QString::fromUtf8(salt));
|
||||
m_db.exec(queryString);
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Error creating user:" << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("INSERT INTO users(username, email, displayName, password, salt, scopes) VALUES(?, ?, ?, ?, ?, ?);");
|
||||
query.addBindValue(username.toLower());
|
||||
query.addBindValue(email);
|
||||
query.addBindValue(displayName);
|
||||
query.addBindValue(QString::fromUtf8(hashedPassword));
|
||||
query.addBindValue(QString::fromUtf8(salt));
|
||||
query.addBindValue(Types::scopesToStringList(scopes).join(','));
|
||||
query.exec();
|
||||
if (query.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Error creating user:" << query.lastError().databaseText() << query.lastError().driverText();
|
||||
return UserErrorBackendError;
|
||||
}
|
||||
|
||||
qCInfo(dcUserManager()) << "New user" << username << "added to the system with permissions:" << Types::scopesToStringList(scopes);
|
||||
emit userAdded(username);
|
||||
return UserErrorNoError;
|
||||
}
|
||||
|
||||
@ -215,17 +229,49 @@ UserManager::UserError UserManager::changePassword(const QString &username, cons
|
||||
|
||||
UserManager::UserError UserManager::removeUser(const QString &username)
|
||||
{
|
||||
if (!username.isEmpty()) {
|
||||
QString dropUserQuery = QString("DELETE FROM users WHERE lower(username) =\"%1\";").arg(username.toLower());
|
||||
QSqlQuery result = m_db.exec(dropUserQuery);
|
||||
if (result.numRowsAffected() == 0) {
|
||||
return UserErrorInvalidUserId;
|
||||
}
|
||||
QString dropUserQuery = QString("DELETE FROM users WHERE lower(username) =\"%1\";").arg(username.toLower());
|
||||
QSqlQuery result = m_db.exec(dropUserQuery);
|
||||
if (result.numRowsAffected() == 0) {
|
||||
return UserErrorInvalidUserId;
|
||||
}
|
||||
|
||||
QString dropTokensQuery = QString("DELETE FROM tokens WHERE lower(username) = \"%1\";").arg(username.toLower());
|
||||
m_db.exec(dropTokensQuery);
|
||||
|
||||
emit userRemoved(username);
|
||||
return UserErrorNoError;
|
||||
}
|
||||
|
||||
UserManager::UserError UserManager::setUserScopes(const QString &username, Types::PermissionScopes scopes)
|
||||
{
|
||||
QString scopesString = Types::scopesToStringList(scopes).join(',');
|
||||
QSqlQuery setScopesQuery(m_db);
|
||||
setScopesQuery.prepare("UPDATE users SET scopes = '%1' WHERE username = '%2'");
|
||||
setScopesQuery.addBindValue(scopesString);
|
||||
setScopesQuery.addBindValue(username);
|
||||
setScopesQuery.exec();
|
||||
if (setScopesQuery.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager()) << "Error updating scopes for user" << username << setScopesQuery.lastError().databaseText() << setScopesQuery.lastError().driverText();
|
||||
return UserErrorBackendError;
|
||||
}
|
||||
|
||||
emit userChanged(username);
|
||||
return UserErrorNoError;
|
||||
}
|
||||
|
||||
UserManager::UserError UserManager::setUserInfo(const QString &username, const QString &email, const QString &displayName)
|
||||
{
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("UPDATE users SET email = ?, displayName = ? WHERE username = ?;");
|
||||
query.addBindValue(email);
|
||||
query.addBindValue(displayName);
|
||||
query.addBindValue(username);
|
||||
query.exec();
|
||||
if (query.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager()) << "Error updating user info for user" << username << query.lastError().databaseText() << query.lastError().driverText() << query.executedQuery();
|
||||
return UserErrorBackendError;
|
||||
}
|
||||
emit userChanged(username);
|
||||
return UserErrorNoError;
|
||||
}
|
||||
|
||||
@ -241,18 +287,20 @@ bool UserManager::pushButtonAuthAvailable() const
|
||||
QByteArray UserManager::authenticate(const QString &username, const QString &password, const QString &deviceName)
|
||||
{
|
||||
if (!validateUsername(username)) {
|
||||
qCWarning(dcUserManager) << "Username did not pass validation:" << username;
|
||||
qCWarning(dcUserManager) << "Authenticate: Username did not pass validation:" << username;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QString passwordQuery = QString("SELECT password, salt FROM users WHERE lower(username) = \"%1\";").arg(username.toLower());
|
||||
QSqlQuery result = m_db.exec(passwordQuery);
|
||||
if (!result.first()) {
|
||||
QSqlQuery passwordQuery(m_db);
|
||||
passwordQuery.prepare("SELECT password, salt FROM users WHERE lower(username) = ?;");
|
||||
passwordQuery.addBindValue(username.toLower());
|
||||
passwordQuery.exec();
|
||||
if (!passwordQuery.first()) {
|
||||
qCWarning(dcUserManager) << "No such username" << username;
|
||||
return QByteArray();
|
||||
}
|
||||
QByteArray salt = result.value("salt").toByteArray();
|
||||
QByteArray hashedPassword = result.value("password").toByteArray();
|
||||
QByteArray salt = passwordQuery.value("salt").toByteArray();
|
||||
QByteArray hashedPassword = passwordQuery.value("password").toByteArray();
|
||||
|
||||
if (hashedPassword != QCryptographicHash::hash(QString(password + salt).toUtf8(), QCryptographicHash::Sha512).toBase64()) {
|
||||
qCWarning(dcUserManager) << "Authentication error for user:" << username;
|
||||
@ -309,50 +357,47 @@ void UserManager::cancelPushButtonAuth(int transactionId)
|
||||
|
||||
}
|
||||
|
||||
UserInfo UserManager::userInfo(const QByteArray &token) const
|
||||
/*! Request UserInfo.
|
||||
The UserInfo for the given username is returned.
|
||||
*/
|
||||
UserInfo UserManager::userInfo(const QString &username) const
|
||||
{
|
||||
TokenInfo tokenInfo = this->tokenInfo(token);
|
||||
|
||||
if (tokenInfo.id().isNull()) {
|
||||
qCWarning(dcUserManager) << "Cannot fetch user info for invalid token:" << token;
|
||||
return UserInfo();
|
||||
}
|
||||
|
||||
// OK, this seems pointless, but data structures are prepared to have more details about users than just the username
|
||||
// i.e. permissions etc will be in here at some point
|
||||
QString getUserQuery = QString("SELECT username FROM users WHERE lower(username) = \"%1\";")
|
||||
.arg(tokenInfo.username().toLower());
|
||||
QString getUserQuery = QString("SELECT * FROM users WHERE lower(username) = \"%1\";")
|
||||
.arg(username);
|
||||
QSqlQuery result = m_db.exec(getUserQuery);
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Query for token failed:" << m_db.lastError().databaseText() << m_db.lastError().driverText() << getUserQuery;
|
||||
qCWarning(dcUserManager) << "Query for user" << username << "failed:" << m_db.lastError().databaseText() << m_db.lastError().driverText() << getUserQuery;
|
||||
return UserInfo();
|
||||
}
|
||||
|
||||
if (!result.first()) {
|
||||
return UserInfo();
|
||||
}
|
||||
return UserInfo(result.value("username").toString());
|
||||
|
||||
UserInfo userInfo = UserInfo(result.value("username").toString());
|
||||
userInfo.setEmail(result.value("email").toString());
|
||||
userInfo.setDisplayName(result.value("displayName").toString());
|
||||
userInfo.setScopes(Types::scopesFromStringList(result.value("scopes").toString().split(',')));
|
||||
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
QList<TokenInfo> UserManager::tokens(const QString &username) const
|
||||
{
|
||||
QList<TokenInfo> ret;
|
||||
|
||||
if (!validateUsername(username)) {
|
||||
qCWarning(dcUserManager) << "Username did not pass validation:" << username;
|
||||
return ret;
|
||||
}
|
||||
QString getTokensQuery = QString("SELECT id, username, creationdate, deviceName FROM tokens WHERE lower(username) = \"%1\";")
|
||||
.arg(username.toLower());
|
||||
QSqlQuery result = m_db.exec(getTokensQuery);
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("SELECT id, username, creationdate, deviceName FROM tokens WHERE lower(username) = ?;");
|
||||
query.addBindValue(username.toLower());
|
||||
query.exec();
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Query for tokens failed:" << m_db.lastError().databaseText() << m_db.lastError().driverText() << getTokensQuery;
|
||||
qCWarning(dcUserManager) << "Query for tokens failed:" << query.lastError().databaseText() << query.lastError().driverText() << query.executedQuery();
|
||||
return ret;
|
||||
}
|
||||
|
||||
while (result.next()) {
|
||||
ret << TokenInfo(result.value("id").toUuid(), result.value("username").toString(), result.value("creationdate").toDateTime(), result.value("devicename").toString());
|
||||
while (query.next()) {
|
||||
ret << TokenInfo(query.value("id").toUuid(), query.value("username").toString(), query.value("creationdate").toDateTime(), query.value("devicename").toString());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -441,30 +486,137 @@ bool UserManager::initDB()
|
||||
m_db.close();
|
||||
|
||||
if (!m_db.open()) {
|
||||
qCWarning(dcUserManager()) << "Can't open user database. Init failed.";
|
||||
dumpDBError("Can't open user database. Init failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
int currentVersion = -1;
|
||||
int newVersion = 1;
|
||||
if (m_db.tables().contains("metadata")) {
|
||||
QSqlQuery query = m_db.exec("SELECT data FROM metadata WHERE `key` = 'version';");
|
||||
if (query.next()) {
|
||||
currentVersion = query.value("data").toInt();
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_db.tables().contains("users")) {
|
||||
qCDebug(dcUserManager()) << "Empty user database. Setting up metadata...";
|
||||
m_db.exec("CREATE TABLE users (username VARCHAR(40) UNIQUE, password VARCHAR(100), salt VARCHAR(100));");
|
||||
m_db.exec("CREATE TABLE users (username VARCHAR(40) UNIQUE PRIMARY KEY, email VARCHAR(40), displayName VARCHAR(40), password VARCHAR(100), salt VARCHAR(100), scopes TEXT);");
|
||||
if (m_db.lastError().isValid()) {
|
||||
qCWarning(dcUserManager) << "Error initualizing user database. Driver error:" << m_db.lastError().driverText() << "Database error:" << m_db.lastError().databaseText();
|
||||
dumpDBError("Error initializing user database (table users).");
|
||||
m_db.close();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (currentVersion < 1) {
|
||||
m_db.exec("ALTER TABLE users ADD COLUMN scopes TEXT;");
|
||||
if (m_db.lastError().isValid()) {
|
||||
dumpDBError("Error migrating user database (table users).");
|
||||
m_db.close();
|
||||
return false;
|
||||
}
|
||||
// Migrated existing users from before multiuser support are admins by default
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("UPDATE users SET scopes = ?;");
|
||||
query.addBindValue(Types::scopesToStringList(Types::PermissionScopeAdmin).join(','));
|
||||
query.exec();
|
||||
|
||||
if (query.lastError().isValid()) {
|
||||
dumpDBError("Error migrating user database (updating existing users).");
|
||||
m_db.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_db.exec("ALTER TABLE users ADD COLUMN email VARCHAR(40);");
|
||||
if (m_db.lastError().isValid()) {
|
||||
dumpDBError("Error migrating user database (table users).");
|
||||
m_db.close();
|
||||
return false;
|
||||
}
|
||||
m_db.exec("ALTER TABLE users ADD COLUMN displayName VARCHAR(40);");
|
||||
if (m_db.lastError().isValid()) {
|
||||
dumpDBError("Error migrating user database (table users).");
|
||||
m_db.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Up until schema 1, username was an email. Copy it to initialize the email field.
|
||||
m_db.exec("UPDATE users SET email = username;");
|
||||
if (m_db.lastError().isValid()) {
|
||||
dumpDBError("Error migrating user database (table users).");
|
||||
m_db.close();
|
||||
return false;
|
||||
}
|
||||
currentVersion = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_db.tables().contains("tokens")) {
|
||||
qCDebug(dcUserManager()) << "Empty user database. Setting up metadata...";
|
||||
m_db.exec("CREATE TABLE tokens (id VARCHAR(40) UNIQUE, username VARCHAR(40), token VARCHAR(100) UNIQUE, creationdate DATETIME, devicename VARCHAR(40));");
|
||||
if (m_db.lastError().isValid()) {
|
||||
qCWarning(dcUserManager()) << "Error initializing user database. Driver error:" << m_db.lastError().driverText() << "Database error:" << m_db.lastError().databaseText();
|
||||
dumpDBError("Error initializing user database (table tokens).");
|
||||
m_db.close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_db.tables().contains("metadata")) {
|
||||
if (currentVersion < newVersion) {
|
||||
m_db.exec(QString("UPDATE metadata SET data = %1 WHERE `key` = 'version')").arg(newVersion));
|
||||
if (m_db.lastError().isValid()) {
|
||||
dumpDBError("Error updating up user database schema version!");
|
||||
m_db.close();
|
||||
return false;
|
||||
}
|
||||
qCInfo(dcUserManager()) << "Successfully migrated user database.";
|
||||
}
|
||||
} else {
|
||||
m_db.exec("CREATE TABLE metadata (`key` VARCHAR(10), data VARCHAR(40));");
|
||||
if (m_db.lastError().isValid()) {
|
||||
dumpDBError("Error setting up user database (table metadata)!");
|
||||
m_db.close();
|
||||
return false;
|
||||
}
|
||||
m_db.exec(QString("INSERT INTO metadata (`key`, `data`) VALUES ('version', %1);").arg(newVersion));
|
||||
if (m_db.lastError().isValid()) {
|
||||
dumpDBError("Error setting up user database (setting version metadata)!");
|
||||
m_db.close();
|
||||
return false;
|
||||
}
|
||||
qCInfo(dcUserManager()) << "Successfully initialized user database.";
|
||||
}
|
||||
|
||||
|
||||
// Migration from before 1.0:
|
||||
// Push button tokens were given out without an explicit user name
|
||||
// If we have push button tokens (userId "") but no explicit user, let's create it as admin
|
||||
// Users without valid username will have password login disabled.
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("SELECT * FROM tokens WHERE username = \"\";");
|
||||
query.exec();
|
||||
if (query.lastError().type() == QSqlError::NoError && query.next()) {
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("SELECT * FROM users WHERE username = \"\";");
|
||||
query.exec();
|
||||
if (!query.next()) {
|
||||
qCDebug(dcUserManager()) << "Tokens existing but no user. Creating token admin user";
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("INSERT INTO users(username, email, displayName, password, salt, scopes) values(?, ?, ?, ?, ?, ?);");
|
||||
query.addBindValue("");
|
||||
query.addBindValue("");
|
||||
query.addBindValue("Admin");
|
||||
query.addBindValue("");
|
||||
query.addBindValue("");
|
||||
query.addBindValue(Types::scopeToString(Types::PermissionScopeAdmin));
|
||||
query.exec();
|
||||
if (query.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Error creating push button user:" << query.lastError().databaseText() << query.lastError().driverText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
qCDebug(dcUserManager()) << "User database initialized successfully.";
|
||||
return true;
|
||||
}
|
||||
@ -486,7 +638,7 @@ void UserManager::rotate(const QString &dbName)
|
||||
|
||||
bool UserManager::validateUsername(const QString &username) const
|
||||
{
|
||||
QRegExp validator("(^[a-zA-Z0-9_\\.+-]+@[a-zA-Z0-9-_]+(\\.[a-zA-Z]+){1,2}$)");
|
||||
QRegExp validator("[a-zA-Z0-9_\\.+-@]{3,}");
|
||||
return validator.exactMatch(username);
|
||||
}
|
||||
|
||||
@ -513,6 +665,11 @@ bool UserManager::validateToken(const QByteArray &token) const
|
||||
return validator.exactMatch(token);
|
||||
}
|
||||
|
||||
void UserManager::dumpDBError(const QString &message)
|
||||
{
|
||||
qCCritical(dcUserManager) << message << "Driver error:" << m_db.lastError().driverText() << "Database error:" << m_db.lastError().databaseText();
|
||||
}
|
||||
|
||||
void UserManager::onPushButtonPressed()
|
||||
{
|
||||
if (m_pushButtonTransaction.first == -1) {
|
||||
@ -520,6 +677,24 @@ void UserManager::onPushButtonPressed()
|
||||
return;
|
||||
}
|
||||
|
||||
// Creating a user without username and password. It won't be able to log in via user/password
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("SELECT * FROM users WHERE username = \"\";");
|
||||
query.exec();
|
||||
if (!query.next()) {
|
||||
qCDebug(dcUserManager()) << "Creating token admin user";
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("INSERT INTO users(username, password, salt, scopes) values(?, ?, ?, ?);");
|
||||
query.addBindValue("");
|
||||
query.addBindValue("");
|
||||
query.addBindValue("");
|
||||
query.addBindValue(Types::scopeToString(Types::PermissionScopeAdmin));
|
||||
query.exec();
|
||||
if (query.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Error creating push button user:" << query.lastError().databaseText() << query.lastError().driverText();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray token = QCryptographicHash::hash(QUuid::createUuid().toByteArray(), QCryptographicHash::Sha256).toBase64();
|
||||
QString storeTokenQuery = QString("INSERT INTO tokens(id, username, token, creationdate, devicename) VALUES(\"%1\", \"%2\", \"%3\", \"%4\", \"%5\");")
|
||||
.arg(QUuid::createUuid().toString())
|
||||
|
||||
@ -59,11 +59,13 @@ public:
|
||||
explicit UserManager(const QString &dbName, QObject *parent = nullptr);
|
||||
|
||||
bool initRequired() const;
|
||||
QStringList users() const;
|
||||
UserInfoList users() const;
|
||||
|
||||
UserError createUser(const QString &username, const QString &password);
|
||||
UserError createUser(const QString &username, const QString &password, const QString &email, const QString &displayName, Types::PermissionScopes scopes);
|
||||
UserError changePassword(const QString &username, const QString &newPassword);
|
||||
UserError removeUser(const QString &username);
|
||||
UserError setUserScopes(const QString &username, Types::PermissionScopes scopes);
|
||||
UserError setUserInfo(const QString &username, const QString &email, const QString &displayName);
|
||||
|
||||
bool pushButtonAuthAvailable() const;
|
||||
|
||||
@ -71,7 +73,7 @@ public:
|
||||
int requestPushButtonAuth(const QString &deviceName);
|
||||
void cancelPushButtonAuth(int transactionId);
|
||||
|
||||
UserInfo userInfo(const QByteArray &token) const;
|
||||
UserInfo userInfo(const QString &username = QString()) const;
|
||||
TokenInfo tokenInfo(const QByteArray &token) const;
|
||||
TokenInfo tokenInfo(const QUuid &tokenId) const;
|
||||
QList<TokenInfo> tokens(const QString &username) const;
|
||||
@ -82,6 +84,9 @@ public:
|
||||
bool verifyToken(const QByteArray &token);
|
||||
|
||||
signals:
|
||||
void userAdded(const QString &username);
|
||||
void userRemoved(const QString &username);
|
||||
void userChanged(const QString &username);
|
||||
void pushButtonAuthFinished(int transactionId, bool success, const QByteArray &token);
|
||||
|
||||
private:
|
||||
@ -91,6 +96,8 @@ private:
|
||||
bool validatePassword(const QString &password) const;
|
||||
bool validateToken(const QByteArray &token) const;
|
||||
|
||||
void dumpDBError(const QString &message);
|
||||
|
||||
private slots:
|
||||
void onPushButtonPressed();
|
||||
|
||||
|
||||
@ -45,7 +45,6 @@ class LIBNYMEA_EXPORT ThingDescriptor
|
||||
Q_PROPERTY(QUuid thingId READ thingId USER true)
|
||||
Q_PROPERTY(QString title READ title)
|
||||
Q_PROPERTY(QString description READ description)
|
||||
Q_PROPERTY(ParamList deviceParams READ params REVISION 1) // Had been forgotten in the device->thing transition.
|
||||
Q_PROPERTY(ParamList params READ params) // added in 0.27
|
||||
|
||||
public:
|
||||
|
||||
@ -147,12 +147,13 @@ void JsonHandler::registerObject(const QString &name, const QVariantMap &object)
|
||||
m_objects.insert(name, object);
|
||||
}
|
||||
|
||||
void JsonHandler::registerMethod(const QString &name, const QString &description, const QVariantMap ¶ms, const QVariantMap &returns, const QString &deprecationInfo)
|
||||
void JsonHandler::registerMethod(const QString &name, const QString &description, const QVariantMap ¶ms, const QVariantMap &returns, Types::PermissionScope permissionScope, const QString &deprecationInfo)
|
||||
{
|
||||
QVariantMap methodData;
|
||||
methodData.insert("description", description);
|
||||
methodData.insert("params", params);
|
||||
methodData.insert("returns", returns);
|
||||
methodData.insert("permissionScope", enumValueName(permissionScope));
|
||||
if (!deprecationInfo.isEmpty()) {
|
||||
methodData.insert("deprecated", deprecationInfo);
|
||||
}
|
||||
@ -285,7 +286,8 @@ QVariant JsonHandler::pack(const QMetaObject &metaObject, const void *value) con
|
||||
int flagValue = propertyValue.toInt();
|
||||
QStringList flags;
|
||||
for (int i = 0; i < metaFlag.keyCount(); i++) {
|
||||
if ((metaFlag.value(i) & flagValue) > 0) {
|
||||
int flag = metaFlag.value(i) & flagValue;
|
||||
if (flag == metaFlag.value(i) && flag > 0) {
|
||||
flags.append(metaFlag.key(i));
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@
|
||||
|
||||
#include "jsonreply.h"
|
||||
#include "jsoncontext.h"
|
||||
#include "typeutils.h"
|
||||
|
||||
class JsonHandler : public QObject
|
||||
{
|
||||
@ -108,7 +109,7 @@ protected:
|
||||
// Deprecated QString based registerObject
|
||||
void registerObject(const QString &name, const QVariantMap &object);
|
||||
|
||||
void registerMethod(const QString &name, const QString &description, const QVariantMap ¶ms, const QVariantMap &returns, const QString &deprecationInfo = QString());
|
||||
void registerMethod(const QString &name, const QString &description, const QVariantMap ¶ms, const QVariantMap &returns, Types::PermissionScope permissionScope = Types::PermissionScopeAdmin, const QString &deprecationInfo = QString());
|
||||
void registerNotification(const QString &name, const QString &description, const QVariantMap ¶ms, const QString &deprecationInfo = QString());
|
||||
|
||||
JsonReply *createReply(const QVariantMap &data) const;
|
||||
|
||||
@ -200,6 +200,7 @@ SOURCES += \
|
||||
types/event.cpp \
|
||||
types/eventdescriptor.cpp \
|
||||
types/thingclass.cpp \
|
||||
types/typeutils.cpp \
|
||||
types/vendor.cpp \
|
||||
types/paramtype.cpp \
|
||||
types/param.cpp \
|
||||
|
||||
@ -42,7 +42,6 @@ class LIBNYMEA_EXPORT Action
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QUuid actionTypeId READ actionTypeId WRITE setActionTypeId)
|
||||
Q_PROPERTY(QUuid thingId READ thingId WRITE setThingId)
|
||||
Q_PROPERTY(QUuid deviceId READ thingId WRITE setThingId REVISION 1)
|
||||
Q_PROPERTY(ParamList params READ params WRITE setParams USER true)
|
||||
|
||||
public:
|
||||
|
||||
@ -44,7 +44,6 @@ class LIBNYMEA_EXPORT Event
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QUuid eventTypeId READ eventTypeId)
|
||||
Q_PROPERTY(QUuid thingId READ thingId)
|
||||
Q_PROPERTY(QUuid deviceId READ thingId REVISION 1)
|
||||
Q_PROPERTY(ParamList params READ params)
|
||||
public:
|
||||
Event();
|
||||
|
||||
@ -44,7 +44,6 @@ class LIBNYMEA_EXPORT EventDescriptor
|
||||
{
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QUuid thingId READ thingId WRITE setThingId USER true)
|
||||
Q_PROPERTY(QUuid deviceId READ thingId WRITE setThingId USER true REVISION 1)
|
||||
Q_PROPERTY(QUuid eventTypeId READ eventTypeId WRITE setEventTypeId USER true)
|
||||
Q_PROPERTY(QString interface READ interface WRITE setInterface USER true)
|
||||
Q_PROPERTY(QString interfaceEvent READ interfaceEvent WRITE setInterfaceEvent USER true)
|
||||
|
||||
@ -46,7 +46,6 @@ class LIBNYMEA_EXPORT StateDescriptor
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QUuid stateTypeId READ stateTypeId WRITE setStateTypeId USER true)
|
||||
Q_PROPERTY(QUuid thingId READ thingId WRITE setThingId USER true)
|
||||
Q_PROPERTY(QUuid deviceId READ thingId WRITE setThingId USER true REVISION 1)
|
||||
Q_PROPERTY(QString interface READ interface WRITE setInterface USER true)
|
||||
Q_PROPERTY(QString interfaceState READ interfaceState WRITE setInterfaceState USER true)
|
||||
Q_PROPERTY(QVariant value READ stateValue WRITE setStateValue USER true)
|
||||
|
||||
70
libnymea/types/typeutils.cpp
Normal file
70
libnymea/types/typeutils.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "typeutils.h"
|
||||
|
||||
#include <QMetaEnum>
|
||||
|
||||
QStringList Types::scopesToStringList(Types::PermissionScopes scopes)
|
||||
{
|
||||
QStringList ret;
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<PermissionScope>();
|
||||
for (int i = 0; i < metaEnum.keyCount(); i++) {
|
||||
if (scopes.testFlag(static_cast<PermissionScope>(metaEnum.value(i)))) {
|
||||
ret << metaEnum.key(i);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString Types::scopeToString(Types::PermissionScope scope)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<PermissionScope>();
|
||||
return metaEnum.valueToKey(scope);
|
||||
}
|
||||
|
||||
Types::PermissionScope Types::scopeFromString(const QString &scopeString)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<PermissionScope>();
|
||||
return static_cast<PermissionScope>(metaEnum.keyToValue(scopeString.toUtf8()));
|
||||
}
|
||||
|
||||
Types::PermissionScopes Types::scopesFromStringList(const QStringList &scopeList)
|
||||
{
|
||||
PermissionScopes ret;
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<PermissionScopes>();
|
||||
for (int i = 0; i < metaEnum.keyCount(); i++) {
|
||||
if (scopeList.contains(metaEnum.key(i))) {
|
||||
ret |= static_cast<PermissionScope>(metaEnum.value(i));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
@ -182,6 +182,23 @@ public:
|
||||
StateValueFilterAdaptive
|
||||
};
|
||||
Q_ENUM(StateValueFilter)
|
||||
|
||||
enum PermissionScope {
|
||||
PermissionScopeNone = 0x0000,
|
||||
PermissionScopeControlThings = 0x0001,
|
||||
PermissionScopeConfigureThings = 0x0003,
|
||||
PermissionScopeExecuteRules = 0x0010,
|
||||
PermissionScopeConfigureRules = 0x0030,
|
||||
PermissionScopeAdmin = 0xFFFF,
|
||||
};
|
||||
Q_ENUM(PermissionScope)
|
||||
Q_DECLARE_FLAGS(PermissionScopes, PermissionScope)
|
||||
Q_FLAG(PermissionScopes)
|
||||
|
||||
static PermissionScopes scopesFromStringList(const QStringList &scopeList);
|
||||
static PermissionScope scopeFromString(const QString &scopeString);
|
||||
static QStringList scopesToStringList(PermissionScopes scopes);
|
||||
static QString scopeToString(PermissionScope scope);
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Types::InputType)
|
||||
|
||||
@ -4,8 +4,8 @@ include(nymea.pri)
|
||||
NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p"')
|
||||
|
||||
# define protocol versions
|
||||
JSON_PROTOCOL_VERSION_MAJOR=5
|
||||
JSON_PROTOCOL_VERSION_MINOR=8
|
||||
JSON_PROTOCOL_VERSION_MAJOR=6
|
||||
JSON_PROTOCOL_VERSION_MINOR=0
|
||||
JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}"
|
||||
LIBNYMEA_API_VERSION_MAJOR=7
|
||||
LIBNYMEA_API_VERSION_MINOR=3
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
TARGET = testactions
|
||||
|
||||
include(../../../nymea.pri)
|
||||
include(../autotests.pri)
|
||||
|
||||
SOURCES += testactions.cpp
|
||||
@ -1,153 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "nymeatestbase.h"
|
||||
#include "integrations/thing.h"
|
||||
#include "jsonrpc/devicehandler.h"
|
||||
|
||||
using namespace nymeaserver;
|
||||
|
||||
class TestActions: public NymeaTestBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void executeAction_data();
|
||||
void executeAction();
|
||||
|
||||
void getActionType_data();
|
||||
void getActionType();
|
||||
|
||||
};
|
||||
|
||||
void TestActions::executeAction_data()
|
||||
{
|
||||
QTest::addColumn<ThingId>("deviceId");
|
||||
QTest::addColumn<ActionTypeId>("actionTypeId");
|
||||
QTest::addColumn<QVariantList>("actionParams");
|
||||
QTest::addColumn<Device::DeviceError>("error");
|
||||
|
||||
QVariantList params;
|
||||
QVariantMap param1;
|
||||
param1.insert("paramTypeId", mockWithParamsActionParam1ParamTypeId);
|
||||
param1.insert("value", 5);
|
||||
params.append(param1);
|
||||
QVariantMap param2;
|
||||
param2.insert("paramTypeId", mockWithParamsActionParam2ParamTypeId);
|
||||
param2.insert("value", true);
|
||||
params.append(param2);
|
||||
|
||||
QTest::newRow("valid action") << m_mockThingId << mockWithParamsActionTypeId << params << Device::DeviceErrorNoError;
|
||||
QTest::newRow("invalid deviceId") << ThingId::createThingId() << mockWithParamsActionTypeId << params << Device::DeviceErrorDeviceNotFound;
|
||||
QTest::newRow("invalid actionTypeId") << m_mockThingId << ActionTypeId::createActionTypeId() << params << Device::DeviceErrorActionTypeNotFound;
|
||||
QTest::newRow("missing params") << m_mockThingId << mockWithParamsActionTypeId << QVariantList() << Device::DeviceErrorMissingParameter;
|
||||
QTest::newRow("async action") << m_mockThingId << mockAsyncActionTypeId << QVariantList() << Device::DeviceErrorNoError;
|
||||
QTest::newRow("broken action") << m_mockThingId << mockFailingActionTypeId << QVariantList() << Device::DeviceErrorSetupFailed;
|
||||
QTest::newRow("async broken action") << m_mockThingId << mockAsyncFailingActionTypeId << QVariantList() << Device::DeviceErrorSetupFailed;
|
||||
}
|
||||
|
||||
void TestActions::executeAction()
|
||||
{
|
||||
QFETCH(ThingId, deviceId);
|
||||
QFETCH(ActionTypeId, actionTypeId);
|
||||
QFETCH(QVariantList, actionParams);
|
||||
QFETCH(Device::DeviceError, error);
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("actionTypeId", actionTypeId);
|
||||
params.insert("deviceId", deviceId);
|
||||
params.insert("params", actionParams);
|
||||
QVariant response = injectAndWait("Actions.ExecuteAction", params);
|
||||
verifyError(response, "deviceError", enumValueName(error));
|
||||
|
||||
// Fetch action execution history from mock device
|
||||
QNetworkAccessManager nam;
|
||||
QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*)));
|
||||
|
||||
QNetworkRequest request(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockThing1Port)));
|
||||
QNetworkReply *reply = nam.get(request);
|
||||
spy.wait();
|
||||
QCOMPARE(spy.count(), 1);
|
||||
reply->deleteLater();
|
||||
QByteArray data = reply->readAll();
|
||||
|
||||
if (error == Device::DeviceErrorNoError) {
|
||||
QVERIFY2(actionTypeId == ActionTypeId(data), QString("ActionTypeId mismatch. Got %1, Expected: %2")
|
||||
.arg(ActionTypeId(data).toString()).arg(actionTypeId.toString()).toLatin1().data());
|
||||
} else {
|
||||
QVERIFY2(data.length() == 0, QString("Data is %1, should be empty.").arg(QString(data)).toLatin1().data());
|
||||
}
|
||||
|
||||
// cleanup for the next run
|
||||
spy.clear();
|
||||
request.setUrl(QUrl(QString("http://localhost:%1/clearactionhistory").arg(m_mockThing1Port)));
|
||||
reply = nam.get(request);
|
||||
spy.wait();
|
||||
QCOMPARE(spy.count(), 1);
|
||||
reply->deleteLater();
|
||||
|
||||
spy.clear();
|
||||
request.setUrl(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockThing1Port)));
|
||||
reply = nam.get(request);
|
||||
spy.wait();
|
||||
QCOMPARE(spy.count(), 1);
|
||||
reply->deleteLater();
|
||||
data = reply->readAll();
|
||||
qDebug() << "cleared data:" << data;
|
||||
|
||||
}
|
||||
|
||||
void TestActions::getActionType_data()
|
||||
{
|
||||
QTest::addColumn<ActionTypeId>("actionTypeId");
|
||||
QTest::addColumn<Device::DeviceError>("error");
|
||||
|
||||
QTest::newRow("valid actiontypeid") << mockWithParamsActionTypeId << Device::DeviceErrorNoError;
|
||||
QTest::newRow("invalid actiontypeid") << ActionTypeId::createActionTypeId() << Device::DeviceErrorActionTypeNotFound;
|
||||
}
|
||||
|
||||
void TestActions::getActionType()
|
||||
{
|
||||
QFETCH(ActionTypeId, actionTypeId);
|
||||
QFETCH(Device::DeviceError, error);
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("actionTypeId", actionTypeId.toString());
|
||||
QVariant response = injectAndWait("Actions.GetActionType", params);
|
||||
|
||||
verifyError(response, "deviceError", enumValueName(error));
|
||||
|
||||
if (error == Device::DeviceErrorNoError) {
|
||||
QVERIFY2(ActionTypeId(response.toMap().value("params").toMap().value("actionType").toMap().value("id").toString()) == actionTypeId, "Didn't get a reply for the same actionTypeId as requested.");
|
||||
}
|
||||
}
|
||||
|
||||
#include "testactions.moc"
|
||||
QTEST_MAIN(TestActions)
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,7 @@
|
||||
TEMPLATE = subdirs
|
||||
|
||||
SUBDIRS = \
|
||||
actions \
|
||||
configurations \
|
||||
devices \
|
||||
events \
|
||||
integrations \
|
||||
ioconnections \
|
||||
jsonrpc \
|
||||
@ -16,7 +13,6 @@ SUBDIRS = \
|
||||
pythonplugins \
|
||||
rules \
|
||||
scripts \
|
||||
states \
|
||||
tags \
|
||||
timemanager \
|
||||
userloading \
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
include(../../../nymea.pri)
|
||||
include(../autotests.pri)
|
||||
|
||||
TARGET = testdevices
|
||||
SOURCES += testdevices.cpp
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,5 +0,0 @@
|
||||
include(../../../nymea.pri)
|
||||
include(../autotests.pri)
|
||||
|
||||
TARGET = testevents
|
||||
SOURCES += testevents.cpp
|
||||
@ -1,181 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "nymeatestbase.h"
|
||||
#include "nymeacore.h"
|
||||
|
||||
#include "jsonrpc/devicehandler.h"
|
||||
#include "servers/mocktcpserver.h"
|
||||
|
||||
using namespace nymeaserver;
|
||||
|
||||
class TestEvents: public NymeaTestBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void triggerEvent();
|
||||
void triggerStateChangeEvent();
|
||||
|
||||
void params();
|
||||
|
||||
void getEventType_data();
|
||||
void getEventType();
|
||||
};
|
||||
|
||||
void TestEvents::triggerEvent()
|
||||
{
|
||||
enableNotifications({"Events"});
|
||||
|
||||
QList<Thing*> devices = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId);
|
||||
QVERIFY2(devices.count() > 0, "There needs to be at least one configured Mock Device for this test");
|
||||
Thing *device = devices.first();
|
||||
|
||||
QSignalSpy spy(NymeaCore::instance(), SIGNAL(eventTriggered(const Event&)));
|
||||
QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
|
||||
|
||||
// Setup connection to mock client
|
||||
QNetworkAccessManager nam;
|
||||
|
||||
// trigger event in mock device
|
||||
int port = device->paramValue(mockThingHttpportParamTypeId).toInt();
|
||||
QNetworkRequest request(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2").arg(port).arg(mockEvent1EventTypeId.toString())));
|
||||
QNetworkReply *reply = nam.get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
|
||||
// Lets wait for the notification
|
||||
spy.wait();
|
||||
QVERIFY(spy.count() > 0);
|
||||
for (int i = 0; i < spy.count(); i++ ){
|
||||
Event event = spy.at(i).at(0).value<Event>();
|
||||
if (event.thingId() == device->id()) {
|
||||
// Make sure the event contains all the stuff we expect
|
||||
QCOMPARE(event.eventTypeId(), mockEvent1EventTypeId);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for the notification on JSON API
|
||||
QVariantList notifications;
|
||||
notifications = checkNotifications(notificationSpy, "Events.EventTriggered");
|
||||
QVERIFY2(notifications.count() == 1, "Should get Events.EventTriggered notification");
|
||||
QVERIFY2(notifications.first().toMap().contains("deprecationWarning"), "Deprecation warning not included in notification");
|
||||
|
||||
QVariantMap notificationContent = notifications.first().toMap().value("params").toMap();
|
||||
QCOMPARE(notificationContent.value("event").toMap().value("deviceId").toUuid().toString(), device->id().toString());
|
||||
QCOMPARE(notificationContent.value("event").toMap().value("eventTypeId").toUuid().toString(), mockEvent1EventTypeId.toString());
|
||||
}
|
||||
|
||||
void TestEvents::triggerStateChangeEvent()
|
||||
{
|
||||
enableNotifications({"Events"});
|
||||
|
||||
QList<Thing*> devices = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId);
|
||||
QVERIFY2(devices.count() > 0, "There needs to be at least one configured Mock Device for this test");
|
||||
Thing *device = devices.first();
|
||||
|
||||
QSignalSpy spy(NymeaCore::instance(), SIGNAL(eventTriggered(const Event&)));
|
||||
QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
|
||||
|
||||
// Setup connection to mock client
|
||||
QNetworkAccessManager nam;
|
||||
|
||||
// trigger state changed event in mock device
|
||||
int port = device->paramValue(mockThingHttpportParamTypeId).toInt();
|
||||
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockIntStateTypeId.toString()).arg(11)));
|
||||
QNetworkReply *reply = nam.get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
|
||||
// Lets wait for the notification
|
||||
spy.wait();
|
||||
QVERIFY(spy.count() > 0);
|
||||
for (int i = 0; i < spy.count(); i++ ){
|
||||
Event event = spy.at(i).at(0).value<Event>();
|
||||
if (event.thingId() == device->id()) {
|
||||
// Make sure the event contains all the stuff we expect
|
||||
QCOMPARE(event.eventTypeId().toString(), mockIntStateTypeId.toString());
|
||||
QCOMPARE(event.param(ParamTypeId(mockIntStateTypeId.toString())).value().toInt(), 11);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for the notification on JSON API
|
||||
QVariantList notifications;
|
||||
notifications = checkNotifications(notificationSpy, "Events.EventTriggered");
|
||||
QVERIFY2(notifications.count() == 1, "Should get Devices.EventTriggered notification");
|
||||
QVERIFY2(notifications.first().toMap().contains("deprecationWarning"), "Deprecation warning not included in notification!");
|
||||
|
||||
QVariantMap notificationContent = notifications.first().toMap().value("params").toMap();
|
||||
|
||||
QCOMPARE(notificationContent.value("event").toMap().value("deviceId").toUuid().toString(), device->id().toString());
|
||||
QCOMPARE(notificationContent.value("event").toMap().value("eventTypeId").toUuid().toString(), mockIntEventTypeId.toString());
|
||||
}
|
||||
|
||||
void TestEvents::params()
|
||||
{
|
||||
Event event;
|
||||
ParamList params;
|
||||
ParamTypeId id = ParamTypeId::createParamTypeId();
|
||||
Param p(id, "foo bar");
|
||||
params.append(p);
|
||||
event.setParams(params);
|
||||
|
||||
QVERIFY(event.param(id).value().toString() == "foo bar");
|
||||
QVERIFY(!event.param(ParamTypeId::createParamTypeId()).value().isValid());
|
||||
}
|
||||
|
||||
void TestEvents::getEventType_data()
|
||||
{
|
||||
QTest::addColumn<EventTypeId>("eventTypeId");
|
||||
QTest::addColumn<Device::DeviceError>("error");
|
||||
|
||||
QTest::newRow("valid eventypeid") << mockEvent1EventTypeId << Device::DeviceErrorNoError;
|
||||
QTest::newRow("invalid eventypeid") << EventTypeId::createEventTypeId() << Device::DeviceErrorEventTypeNotFound;
|
||||
}
|
||||
|
||||
void TestEvents::getEventType()
|
||||
{
|
||||
QFETCH(EventTypeId, eventTypeId);
|
||||
QFETCH(Device::DeviceError, error);
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("eventTypeId", eventTypeId.toString());
|
||||
QVariant response = injectAndWait("Events.GetEventType", params);
|
||||
|
||||
verifyError(response, "deviceError", enumValueName(error));
|
||||
|
||||
qCDebug(dcTests()) << "*content" << response;
|
||||
QVERIFY2(response.toMap().contains("deprecationWarning"), "Deprecation warning not shown in reply");
|
||||
|
||||
if (error == Device::DeviceErrorNoError) {
|
||||
QVERIFY2(EventTypeId(response.toMap().value("params").toMap().value("eventType").toMap().value("id").toString()) == eventTypeId, "Didn't get a reply for the same actionTypeId as requested.");
|
||||
}
|
||||
}
|
||||
|
||||
#include "testevents.moc"
|
||||
QTEST_MAIN(TestEvents)
|
||||
@ -84,6 +84,8 @@ private slots:
|
||||
|
||||
void storedThings();
|
||||
|
||||
void stateCache();
|
||||
|
||||
void discoverThings_data();
|
||||
void discoverThings();
|
||||
|
||||
@ -650,6 +652,74 @@ void TestIntegrations::storedThings()
|
||||
verifyThingError(response);
|
||||
}
|
||||
|
||||
void TestIntegrations::stateCache()
|
||||
{
|
||||
ThingClass mockThingClass = NymeaCore::instance()->thingManager()->findThingClass(mockThingClassId);
|
||||
|
||||
QVERIFY2(mockThingClass.getStateType(mockIntStateTypeId).cached(), "Mock int state is not cached (required to be true for this test)");
|
||||
QVERIFY2(!mockThingClass.getStateType(mockBoolStateTypeId).cached(), "Mock bool state is cached (required to be false for this test)");
|
||||
|
||||
Thing* thing = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId).first();
|
||||
int port = thing->paramValue(mockThingHttpportParamTypeId).toInt();
|
||||
QNetworkAccessManager nam;
|
||||
QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*)));
|
||||
|
||||
|
||||
// First set the state values to something that is *not* the default
|
||||
int oldIntValue = mockThingClass.getStateType(mockIntStateTypeId).defaultValue().toInt();
|
||||
int newIntValue = oldIntValue + 1;
|
||||
bool oldBoolValue = mockThingClass.getStateType(mockBoolStateTypeId).defaultValue().toBool();
|
||||
bool newBoolValue = !oldBoolValue;
|
||||
|
||||
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockIntStateTypeId.toString()).arg(newIntValue)));
|
||||
QNetworkReply *reply = nam.get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
spy.wait();
|
||||
|
||||
spy.clear();
|
||||
request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockBoolStateTypeId.toString()).arg(newBoolValue)));
|
||||
reply = nam.get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
spy.wait();
|
||||
|
||||
// For completeness, verify through JSONRPC that they were actually yet.
|
||||
QVariantMap params;
|
||||
params.insert("thingId", thing->id());
|
||||
|
||||
params["stateTypeId"] = mockIntStateTypeId;
|
||||
QVariant response = injectAndWait("Integrations.GetStateValue", params);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("value").toInt(), newIntValue);
|
||||
|
||||
params["stateTypeId"] = mockBoolStateTypeId;
|
||||
response = injectAndWait("Integrations.GetStateValue", params);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("value").toBool(), newBoolValue);
|
||||
|
||||
// Restart the server
|
||||
restartServer();
|
||||
|
||||
// And check if the cached int state has successfully been restored
|
||||
params["stateTypeId"] = mockIntStateTypeId;
|
||||
response = injectAndWait("Integrations.GetStateValue", params);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("value").toInt(), newIntValue);
|
||||
|
||||
// and that the non-cached bool state is back to its default
|
||||
params["stateTypeId"] = mockBoolStateTypeId;
|
||||
response = injectAndWait("Integrations.GetStateValue", params);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("value").toBool(), mockThingClass.getStateType(mockBoolStateTypeId).defaultValue().toBool());
|
||||
|
||||
// Reset back to default values
|
||||
spy.clear();
|
||||
request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockBoolStateTypeId.toString()).arg(oldIntValue)));
|
||||
reply = nam.get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
spy.wait();
|
||||
spy.clear();
|
||||
request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockBoolStateTypeId.toString()).arg(oldBoolValue)));
|
||||
reply = nam.get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
spy.wait();
|
||||
}
|
||||
|
||||
void TestIntegrations::discoverThings_data()
|
||||
{
|
||||
QTest::addColumn<ThingClassId>("thingClassId");
|
||||
@ -1011,6 +1081,7 @@ void TestIntegrations::getStateValue_data()
|
||||
|
||||
void TestIntegrations::getStateValue()
|
||||
{
|
||||
NymeaCore::instance()->thingManager()->findConfiguredThing(m_mockThingId)->setStateValue(mockIntStateTypeId, 10);
|
||||
QFETCH(ThingId, thingId);
|
||||
QFETCH(StateTypeId, stateTypeId);
|
||||
QFETCH(Thing::ThingError, statusCode);
|
||||
@ -2085,7 +2156,7 @@ void TestIntegrations::triggerStateChangeEvent()
|
||||
|
||||
// trigger state changed event in mock device
|
||||
int port = thing->paramValue(mockThingHttpportParamTypeId).toInt();
|
||||
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockIntStateTypeId.toString()).arg(11)));
|
||||
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockIntStateTypeId.toString()).arg(37)));
|
||||
QNetworkReply *reply = nam.get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
|
||||
@ -2097,7 +2168,7 @@ void TestIntegrations::triggerStateChangeEvent()
|
||||
if (event.thingId() == thing->id()) {
|
||||
// Make sure the event contains all the stuff we expect
|
||||
QCOMPARE(event.eventTypeId().toString(), mockIntStateTypeId.toString());
|
||||
QCOMPARE(event.param(ParamTypeId(mockIntStateTypeId.toString())).value().toInt(), 11);
|
||||
QCOMPARE(event.param(ParamTypeId(mockIntStateTypeId.toString())).value().toInt(), 37);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2107,7 +2178,7 @@ void TestIntegrations::triggerStateChangeEvent()
|
||||
QVERIFY2(notifications.count() == 1, "Should get Integrations.EventTriggered notification");
|
||||
QVariantMap notificationContent = notifications.first().toMap().value("params").toMap();
|
||||
|
||||
QCOMPARE(notificationContent.value("event").toMap().value("deviceId").toUuid().toString(), thing->id().toString());
|
||||
QCOMPARE(notificationContent.value("event").toMap().value("thingId").toUuid().toString(), thing->id().toString());
|
||||
QCOMPARE(notificationContent.value("event").toMap().value("eventTypeId").toUuid().toString(), mockIntEventTypeId.toString());
|
||||
|
||||
}
|
||||
|
||||
@ -150,8 +150,7 @@ QStringList TestJSONRPC::extractRefs(const QVariant &variant)
|
||||
void TestJSONRPC::initTestCase()
|
||||
{
|
||||
NymeaDBusService::setBusType(QDBusConnection::SessionBus);
|
||||
NymeaTestBase::initTestCase();
|
||||
QLoggingCategory::setFilterRules("*.debug=false\n"
|
||||
NymeaTestBase::initTestCase("*.debug=false\n"
|
||||
// "JsonRpcTraffic.debug=true\n"
|
||||
"JsonRpc.debug=true\n"
|
||||
"Translations.debug=true\n"
|
||||
@ -236,8 +235,8 @@ void TestJSONRPC::testHandshakeLocale()
|
||||
|
||||
void TestJSONRPC::testInitialSetup()
|
||||
{
|
||||
foreach (const QString &user, NymeaCore::instance()->userManager()->users()) {
|
||||
NymeaCore::instance()->userManager()->removeUser(user);
|
||||
foreach (const UserInfo &userInfo, NymeaCore::instance()->userManager()->users()) {
|
||||
NymeaCore::instance()->userManager()->removeUser(userInfo.username());
|
||||
}
|
||||
NymeaCore::instance()->userManager()->removeUser("");
|
||||
|
||||
@ -246,7 +245,6 @@ void TestJSONRPC::testInitialSetup()
|
||||
|
||||
QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
|
||||
QVERIFY(spy.isValid());
|
||||
QSignalSpy connectedSpy(m_mockTcpServer, &MockTcpServer::clientConnected);
|
||||
QSignalSpy disconnectedSpy(m_mockTcpServer, &MockTcpServer::clientDisconnected);
|
||||
|
||||
// Introspect call should work in any case
|
||||
@ -294,11 +292,10 @@ void TestJSONRPC::testInitialSetup()
|
||||
if (disconnectedSpy.count() == 0) disconnectedSpy.wait();
|
||||
QCOMPARE(disconnectedSpy.count(), 1);
|
||||
qCDebug(dcTests()) << "Mock client disconnected";
|
||||
connectedSpy.clear();
|
||||
|
||||
// The connection will be locked down for 3 seconds
|
||||
QTest::qWait(3200);
|
||||
emit m_mockTcpServer->clientConnected(m_clientId);
|
||||
if (connectedSpy.count() == 0) connectedSpy.wait();
|
||||
QCOMPARE(connectedSpy.count(), 1);
|
||||
qCDebug(dcTests()) << "Mock client connected";
|
||||
|
||||
spy.clear();
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 0, \"method\": \"JSONRPC.Hello\"}");
|
||||
@ -312,7 +309,7 @@ void TestJSONRPC::testInitialSetup()
|
||||
// But it should still fail when giving a an invalid username
|
||||
spy.clear();
|
||||
qCDebug(dcTests()) << "Calling CreateUser, expecting failure (bad username)";
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.CreateUser\", \"params\": {\"username\": \"dummy\", \"password\": \"DummyPW1!\"}}\n");
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.CreateUser\", \"params\": {\"username\": \"a\", \"password\": \"DummyPW1!\"}}\n");
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
}
|
||||
@ -326,7 +323,7 @@ void TestJSONRPC::testInitialSetup()
|
||||
// or when giving a bad password
|
||||
spy.clear();
|
||||
qCDebug(dcTests()) << "Calling CreateUser, expecting failure (bad password)";
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.CreateUser\", \"params\": {\"username\": \"dummy@guh.io\", \"password\": \"weak\"}}\n");
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.CreateUser\", \"params\": {\"username\": \"nymea\", \"password\": \"weak\"}}\n");
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
}
|
||||
@ -382,12 +379,10 @@ void TestJSONRPC::testInitialSetup()
|
||||
// Connection should terminate
|
||||
if (disconnectedSpy.count() == 0) disconnectedSpy.wait();
|
||||
QCOMPARE(disconnectedSpy.count(), 1);
|
||||
qCDebug(dcTests()) << "Mock client disconnected";
|
||||
connectedSpy.clear();
|
||||
|
||||
// The connection will be locked down for 3 secs
|
||||
QTest::qWait(3200);
|
||||
emit m_mockTcpServer->clientConnected(m_clientId);
|
||||
if (connectedSpy.count() == 0) connectedSpy.wait();
|
||||
QCOMPARE(connectedSpy.count(), 1);
|
||||
qCDebug(dcTests()) << "Mock client connected";
|
||||
|
||||
spy.clear();
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 0, \"method\": \"JSONRPC.Hello\"}");
|
||||
@ -415,6 +410,7 @@ void TestJSONRPC::testInitialSetup()
|
||||
|
||||
// Now lets authenticate with a wrong password
|
||||
spy.clear();
|
||||
disconnectedSpy.clear();
|
||||
qCDebug(dcTests()) << "Calling Authenticate, expecting failure (bad password)";
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.Authenticate\", \"params\": {\"username\": \"Dummy@guh.io\", \"password\": \"wrongpw\", \"deviceName\": \"testcase\"}}\n");
|
||||
if (spy.count() == 0) {
|
||||
@ -423,11 +419,24 @@ void TestJSONRPC::testInitialSetup()
|
||||
QVERIFY(spy.count() == 1);
|
||||
jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray());
|
||||
response = jsonDoc.toVariant().toMap();
|
||||
qCDebug(dcTests()) << "Calling Authenticate with wrong password:" << response.value("params").toMap().value("success").toString() << response.value("params").toMap().value("token").toString();
|
||||
QCOMPARE(response.value("status").toString(), QStringLiteral("success"));
|
||||
QCOMPARE(response.value("params").toMap().value("success").toBool(), false);
|
||||
QVERIFY(response.value("params").toMap().value("token").toByteArray().isEmpty());
|
||||
|
||||
// Connection should terminate
|
||||
if (disconnectedSpy.count() == 0) disconnectedSpy.wait();
|
||||
QCOMPARE(disconnectedSpy.count(), 1);
|
||||
|
||||
// The connection will be locked down for 3 secs
|
||||
QTest::qWait(3200);
|
||||
emit m_mockTcpServer->clientConnected(m_clientId);
|
||||
|
||||
spy.clear();
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 0, \"method\": \"JSONRPC.Hello\"}");
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
}
|
||||
QVERIFY(spy.count() == 1);
|
||||
|
||||
// Now lets authenticate for real (but intentionally use a lowercase email here, should still work)
|
||||
spy.clear();
|
||||
@ -466,19 +475,17 @@ void TestJSONRPC::testRevokeToken()
|
||||
QVERIFY(spy.isValid());
|
||||
QSignalSpy disconnectedSpy(m_mockTcpServer, &MockTcpServer::clientDisconnected);
|
||||
QVERIFY(disconnectedSpy.isValid());
|
||||
QSignalSpy connectedSpy(m_mockTcpServer, &MockTcpServer::clientConnected);
|
||||
QVERIFY(connectedSpy.isValid());
|
||||
|
||||
// Now get all the tokens
|
||||
spy.clear();
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 123, \"token\": \"" + m_apiToken + "\", \"method\": \"JSONRPC.Tokens\"}\n");
|
||||
qCDebug(dcTests()) << "Getting existing Tokens";
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 123, \"token\": \"" + m_apiToken + "\", \"method\": \"Users.GetTokens\"}\n");
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
}
|
||||
QVERIFY(spy.count() == 1);
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray());
|
||||
QVariantMap response = jsonDoc.toVariant().toMap();
|
||||
qCDebug(dcTests()) << "Getting existing Tokens" << response.value("status").toString() << response;
|
||||
QCOMPARE(response.value("status").toString(), QStringLiteral("success"));
|
||||
QVariantList tokenList = response.value("params").toMap().value("tokenInfoList").toList();
|
||||
QCOMPARE(tokenList.count(), 1);
|
||||
@ -486,6 +493,7 @@ void TestJSONRPC::testRevokeToken()
|
||||
|
||||
// Authenticate and create a new token
|
||||
spy.clear();
|
||||
qCDebug(dcTests()) << "Calling Authenticate with valid credentials" ;
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.Authenticate\", \"params\": {\"username\": \"dummy@guh.io\", \"password\": \"DummyPW1!\", \"deviceName\": \"testcase\"}}\n");
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
@ -493,7 +501,6 @@ void TestJSONRPC::testRevokeToken()
|
||||
QVERIFY(spy.count() == 1);
|
||||
jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray());
|
||||
response = jsonDoc.toVariant().toMap();
|
||||
qCDebug(dcTests()) << "Calling Authenticate with valid credentials:" << response.value("params").toMap().value("success").toString() << response.value("params").toMap().value("token").toString();
|
||||
QCOMPARE(response.value("status").toString(), QStringLiteral("success"));
|
||||
QCOMPARE(response.value("params").toMap().value("success").toBool(), true);
|
||||
QByteArray newToken = response.value("params").toMap().value("token").toByteArray();
|
||||
@ -513,14 +520,14 @@ void TestJSONRPC::testRevokeToken()
|
||||
|
||||
// Now get all the tokens using the old token
|
||||
spy.clear();
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 123, \"token\": \"" + m_apiToken + "\", \"method\": \"JSONRPC.Tokens\"}\n");
|
||||
qCDebug(dcTests()) << "Calling Tokens";
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 123, \"token\": \"" + m_apiToken + "\", \"method\": \"Users.GetTokens\"}\n");
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
}
|
||||
QVERIFY(spy.count() == 1);
|
||||
jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray());
|
||||
response = jsonDoc.toVariant().toMap();
|
||||
qCDebug(dcTests()) << "Calling Tokens" << response.value("status").toString();
|
||||
QCOMPARE(response.value("status").toString(), QStringLiteral("success"));
|
||||
tokenList = response.value("params").toMap().value("tokenInfoList").toList();
|
||||
QCOMPARE(tokenList.count(), 2);
|
||||
@ -536,19 +543,20 @@ void TestJSONRPC::testRevokeToken()
|
||||
|
||||
// Revoke the new token
|
||||
spy.clear();
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 123, \"token\": \"" + m_apiToken + "\", \"method\": \"JSONRPC.RemoveToken\", \"params\": {\"tokenId\": \"" + newTokenId.toByteArray() + "\"}}\n");
|
||||
qCDebug(dcTests()) << "Calling RemoveToken";
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 123, \"token\": \"" + m_apiToken + "\", \"method\": \"Users.RemoveToken\", \"params\": {\"tokenId\": \"" + newTokenId.toByteArray() + "\"}}\n");
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
}
|
||||
QVERIFY(spy.count() == 1);
|
||||
jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray());
|
||||
response = jsonDoc.toVariant().toMap();
|
||||
qCDebug(dcTests()) << "Calling RemoveToken" << response.value("status").toString() << response;
|
||||
QCOMPARE(response.value("status").toString(), QStringLiteral("success"));
|
||||
|
||||
// Do a call with the now removed token, it should be forbidden
|
||||
spy.clear();
|
||||
disconnectedSpy.clear();
|
||||
qCDebug(dcTests()) << "Calling Version with now removed token";
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"token\": \"" + newToken + "\", \"method\": \"JSONRPC.Version\"}\n");
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
@ -556,18 +564,16 @@ void TestJSONRPC::testRevokeToken()
|
||||
QVERIFY(spy.count() == 1);
|
||||
jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray());
|
||||
response = jsonDoc.toVariant().toMap();
|
||||
qCDebug(dcTests()) << "Calling Version with valid token:" << response.value("status").toString() << response.value("error").toString();
|
||||
QCOMPARE(response.value("status").toString(), QStringLiteral("unauthorized"));
|
||||
|
||||
// And connection should drop
|
||||
if (disconnectedSpy.count() == 0) disconnectedSpy.wait();
|
||||
QCOMPARE(disconnectedSpy.count(), 1);
|
||||
|
||||
QTest::qWait(3200);
|
||||
|
||||
// Connect again to not impact subsequent tests...
|
||||
connectedSpy.clear();
|
||||
emit m_mockTcpServer->clientConnected(m_clientId);
|
||||
if (connectedSpy.count() == 0) connectedSpy.wait();
|
||||
QCOMPARE(connectedSpy.count(), 1);
|
||||
injectAndWait("JSONRPC.Hello");
|
||||
}
|
||||
|
||||
@ -674,7 +680,7 @@ void TestJSONRPC::enableDisableNotifications_legacy()
|
||||
|
||||
QStringList expectedNamespaces;
|
||||
if (enabled == "true") {
|
||||
expectedNamespaces << "Actions" << "NetworkManager" << "Devices" << "Integrations" << "System" << "Rules" << "States" << "Logging" << "Tags" << "AppData" << "JSONRPC" << "Configuration" << "Events" << "Scripts" << "Users" << "Zigbee" << "ModbusRtu";
|
||||
expectedNamespaces << "NetworkManager" << "Integrations" << "System" << "Rules"<< "Logging" << "Tags" << "AppData" << "JSONRPC" << "Configuration" << "Scripts" << "Users" << "Zigbee" << "ModbusRtu";
|
||||
}
|
||||
std::sort(expectedNamespaces.begin(), expectedNamespaces.end());
|
||||
|
||||
@ -700,7 +706,6 @@ void TestJSONRPC::ruleAddedRemovedNotifications()
|
||||
QVariantMap stateDescriptor;
|
||||
stateDescriptor.insert("stateTypeId", mockIntStateTypeId);
|
||||
stateDescriptor.insert("thingId", m_mockThingId);
|
||||
stateDescriptor.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
stateDescriptor.insert("operator", enumValueName(Types::ValueOperatorLess));
|
||||
stateDescriptor.insert("value", "20");
|
||||
// This is a bit odd: QUuid.toString() wraps the uuids in {}, however, the implicit cast doesn't
|
||||
@ -715,7 +720,6 @@ void TestJSONRPC::ruleAddedRemovedNotifications()
|
||||
QVariantMap actionNoParams;
|
||||
actionNoParams.insert("actionTypeId", mockWithoutParamsActionTypeId);
|
||||
actionNoParams.insert("thingId", m_mockThingId);
|
||||
actionNoParams.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
// This is a bit odd: QUuid.toString() wraps the uuids in {}, however, the implicit cast doesn't
|
||||
// .toString(QUuid::WithoutBraces) has only been added in 5.11 so we can't use that either...
|
||||
// Only hack I can come up with right now is to convert it to a Json and back to use the implicit cast
|
||||
@ -725,7 +729,6 @@ void TestJSONRPC::ruleAddedRemovedNotifications()
|
||||
QVariantMap eventDescriptor;
|
||||
eventDescriptor.insert("eventTypeId", mockEvent1EventTypeId);
|
||||
eventDescriptor.insert("thingId", m_mockThingId);
|
||||
eventDescriptor.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
// This is a bit odd: QUuid.toString() wraps the uuids in {}, however, the implicit cast doesn't
|
||||
// .toString(QUuid::WithoutBraces) has only been added in 5.11 so we can't use that either...
|
||||
// Only hack I can come up with right now is to convert it to a Json and back to use the implicit cast
|
||||
@ -789,7 +792,6 @@ void TestJSONRPC::ruleActiveChangedNotifications()
|
||||
QVariantMap stateDescriptor;
|
||||
stateDescriptor.insert("stateTypeId", mockIntStateTypeId);
|
||||
stateDescriptor.insert("thingId", m_mockThingId);
|
||||
stateDescriptor.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
stateDescriptor.insert("operator", enumValueName(Types::ValueOperatorEquals));
|
||||
stateDescriptor.insert("value", "20");
|
||||
// This is a bit odd: QUuid.toString() wraps the uuids in {}, however, the implicit cast doesn't
|
||||
@ -804,7 +806,6 @@ void TestJSONRPC::ruleActiveChangedNotifications()
|
||||
QVariantMap actionNoParams;
|
||||
actionNoParams.insert("actionTypeId", mockWithoutParamsActionTypeId);
|
||||
actionNoParams.insert("thingId", m_mockThingId);
|
||||
actionNoParams.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
// This is a bit odd: QUuid.toString() wraps the uuids in {}, however, the implicit cast doesn't
|
||||
// .toString(QUuid::WithoutBraces) has only been added in 5.11 so we can't use that either...
|
||||
// Only hack I can come up with right now is to convert it to a Json and back to use the implicit cast
|
||||
@ -855,21 +856,18 @@ void TestJSONRPC::ruleActiveChangedNotifications()
|
||||
spy.clear(); clientSpy.clear();
|
||||
|
||||
// set the rule inactive
|
||||
qDebug() << "setting mock int state to 42";
|
||||
qCDebug(dcTests) << "setting mock int state to 42";
|
||||
QNetworkRequest request2(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockThing1Port).arg(mockIntStateTypeId.toString()).arg(42)));
|
||||
QNetworkReply *reply2 = nam.get(request2);
|
||||
connect(reply2, SIGNAL(finished()), reply2, SLOT(deleteLater()));
|
||||
|
||||
// Waiting for notifications:
|
||||
// Devices.StateChanged for the change we did
|
||||
// Devices.EventTriggered
|
||||
// Events.EventTriggered <-- deprecated
|
||||
// Integrations.StateChanged for the change we did
|
||||
// Integrations.EventTriggered
|
||||
// Rules.RuleActiveChanged
|
||||
// Logging.LogEntryAdded
|
||||
// Devices.StateChanged for the change done by the rule
|
||||
// Devices.EventTriggered
|
||||
// Events.EventTriggered <-- deprecated
|
||||
while (clientSpy.count() < 8) {
|
||||
// Logging.LogEntryAdded // One for the state change we did
|
||||
// Logging.LogEntryAdded // One for the rule state change
|
||||
while (clientSpy.count() < 5) {
|
||||
clientSpy.wait();
|
||||
}
|
||||
|
||||
@ -894,7 +892,7 @@ void TestJSONRPC::ruleActiveChangedNotifications()
|
||||
|
||||
void TestJSONRPC::stateChangeEmitsNotifications()
|
||||
{
|
||||
enableNotifications({"Devices", "States", "Logging", "Events"});
|
||||
enableNotifications({"Integrations", "Logging"});
|
||||
bool found = false;
|
||||
|
||||
// Setup connection to mock client
|
||||
@ -911,9 +909,9 @@ void TestJSONRPC::stateChangeEmitsNotifications()
|
||||
if (replySpy.count() == 0) replySpy.wait();
|
||||
|
||||
// Make sure the notification contains all the stuff we expect
|
||||
QVariantList stateChangedVariants = checkNotifications(clientSpy, "Devices.StateChanged");
|
||||
QVERIFY2(!stateChangedVariants.isEmpty(), "Did not get Devices.StateChanged notification.");
|
||||
qDebug() << "got" << stateChangedVariants.count() << "Devices.StateChanged notifications";
|
||||
QVariantList stateChangedVariants = checkNotifications(clientSpy, "Integrations.StateChanged");
|
||||
QVERIFY2(!stateChangedVariants.isEmpty(), "Did not get Integrations.StateChanged notification.");
|
||||
qDebug() << "got" << stateChangedVariants.count() << "Integrations.StateChanged notifications";
|
||||
foreach (const QVariant &stateChangedVariant, stateChangedVariants) {
|
||||
if (stateChangedVariant.toMap().value("params").toMap().value("stateTypeId").toUuid() == stateTypeId) {
|
||||
found = true;
|
||||
@ -933,8 +931,8 @@ void TestJSONRPC::stateChangeEmitsNotifications()
|
||||
}
|
||||
|
||||
// Make sure the notification contains all the stuff we expect
|
||||
QVariantList eventTriggeredVariants = checkNotifications(clientSpy, "Events.EventTriggered");
|
||||
QVERIFY2(!eventTriggeredVariants.isEmpty(), "Did not get Events.EventTriggered notification.");
|
||||
QVariantList eventTriggeredVariants = checkNotifications(clientSpy, "Integrations.EventTriggered");
|
||||
QVERIFY2(!eventTriggeredVariants.isEmpty(), "Did not get Integrations.EventTriggered notification.");
|
||||
found = false;
|
||||
foreach (const QVariant &eventTriggeredVariant, eventTriggeredVariants) {
|
||||
if (eventTriggeredVariant.toMap().value("params").toMap().value("event").toMap().value("eventTypeId").toUuid() == stateTypeId) {
|
||||
@ -944,7 +942,7 @@ void TestJSONRPC::stateChangeEmitsNotifications()
|
||||
}
|
||||
}
|
||||
|
||||
QVERIFY2(found, "Could not find the corresponding Events.EventTriggered notification");
|
||||
QVERIFY2(found, "Could not find the corresponding Integrations.EventTriggered notification");
|
||||
|
||||
// Now turn off notifications
|
||||
QCOMPARE(disableNotifications(), true);
|
||||
@ -1185,8 +1183,8 @@ void TestJSONRPC::testPushButtonAuthConnectionDrop()
|
||||
|
||||
void TestJSONRPC::testInitialSetupWithPushButtonAuth()
|
||||
{
|
||||
foreach (const QString &user, NymeaCore::instance()->userManager()->users()) {
|
||||
NymeaCore::instance()->userManager()->removeUser(user);
|
||||
foreach (const UserInfo &userInfo, NymeaCore::instance()->userManager()->users()) {
|
||||
NymeaCore::instance()->userManager()->removeUser(userInfo.username());
|
||||
}
|
||||
NymeaCore::instance()->userManager()->removeUser("");
|
||||
QVERIFY(NymeaCore::instance()->userManager()->initRequired());
|
||||
@ -1256,11 +1254,11 @@ void TestJSONRPC::testInitialSetupWithPushButtonAuth()
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("initialSetupRequired").toBool(), false);
|
||||
|
||||
|
||||
// CreateUser without a token should fail now even though there are 0 users in the DB
|
||||
// CreateUser without a token should fail now that there is the push button generated user
|
||||
spy.clear();
|
||||
QSignalSpy disconnectedSpy(m_mockTcpServer, &MockTcpServer::clientDisconnected);
|
||||
qCDebug(dcTests()) << "Calling CreateUser on uninitialized instance";
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.CreateUser\", \"params\": {\"username\": \"Dummy@guh.io\", \"password\": \"DummyPW1!\"}}\n");
|
||||
qCDebug(dcTests()) << "Calling CreateUser on pushbutton initialized instance";
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.CreateUser\", \"params\": {\"username\": \"nymea\", \"password\": \"DummyPW1!\"}}\n");
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
}
|
||||
@ -1269,12 +1267,14 @@ void TestJSONRPC::testInitialSetupWithPushButtonAuth()
|
||||
response = jsonDoc.toVariant();
|
||||
qCDebug(dcTests()) << "Result:" << response.toMap().value("status").toString() << response.toMap().value("error").toString();
|
||||
QCOMPARE(response.toMap().value("status").toString(), QStringLiteral("unauthorized"));
|
||||
QCOMPARE(NymeaCore::instance()->userManager()->users().count(), 0);
|
||||
QCOMPARE(NymeaCore::instance()->userManager()->users().count(), 1);
|
||||
|
||||
// Connection should drop
|
||||
if (disconnectedSpy.isEmpty()) disconnectedSpy.wait();
|
||||
QCOMPARE(disconnectedSpy.count(), 1);
|
||||
|
||||
QTest::qWait(3200);
|
||||
|
||||
// Reconnect to not impact subsequent tests
|
||||
m_mockTcpServer->clientConnected(m_clientId);
|
||||
spy.clear();
|
||||
|
||||
@ -300,7 +300,7 @@ void TestLogging::eventLogs()
|
||||
|
||||
// Now snoop in for the events
|
||||
clearLoggingDatabase();
|
||||
enableNotifications({"Events", "Logging"});
|
||||
enableNotifications({"Integrations", "Logging"});
|
||||
QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
|
||||
|
||||
// trigger state change in mock device
|
||||
|
||||
@ -422,7 +422,6 @@ void TestRules::addRemoveRules_data()
|
||||
QVariantMap validActionNoParams;
|
||||
validActionNoParams.insert("actionTypeId", mockWithoutParamsActionTypeId);
|
||||
validActionNoParams.insert("thingId", m_mockThingId);
|
||||
validActionNoParams.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
|
||||
QVariantMap invalidAction;
|
||||
invalidAction.insert("actionTypeId", ActionTypeId("f32c7efb-38b6-4576-a496-c75bbb23132f"));
|
||||
@ -432,7 +431,6 @@ void TestRules::addRemoveRules_data()
|
||||
QVariantMap validExitActionNoParams;
|
||||
validExitActionNoParams.insert("actionTypeId", mockWithoutParamsActionTypeId);
|
||||
validExitActionNoParams.insert("thingId", m_mockThingId);
|
||||
validExitActionNoParams.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
|
||||
QVariantMap invalidExitAction;
|
||||
invalidExitAction.insert("actionTypeId", ActionTypeId("f32c7efb-38b6-4576-a496-c75bbb23132f"));
|
||||
@ -458,12 +456,10 @@ void TestRules::addRemoveRules_data()
|
||||
QVariantMap validEventDescriptor1;
|
||||
validEventDescriptor1.insert("eventTypeId", mockEvent1EventTypeId);
|
||||
validEventDescriptor1.insert("thingId", m_mockThingId);
|
||||
validEventDescriptor1.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
|
||||
QVariantMap validEventDescriptor2;
|
||||
validEventDescriptor2.insert("eventTypeId", mockEvent2EventTypeId);
|
||||
validEventDescriptor2.insert("thingId", m_mockThingId);
|
||||
validEventDescriptor2.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
|
||||
QVariantList params;
|
||||
QVariantMap param1;
|
||||
@ -476,7 +472,6 @@ void TestRules::addRemoveRules_data()
|
||||
QVariantMap validEventDescriptor3;
|
||||
validEventDescriptor3.insert("eventTypeId", mockEvent2EventTypeId);
|
||||
validEventDescriptor3.insert("thingId", m_mockThingId);
|
||||
validEventDescriptor3.insert("deviceId", m_mockThingId); // DREPECATED
|
||||
|
||||
// EventDescriptorList
|
||||
QVariantList eventDescriptorList;
|
||||
@ -491,7 +486,6 @@ void TestRules::addRemoveRules_data()
|
||||
QVariantMap validActionEventBased;
|
||||
validActionEventBased.insert("actionTypeId", mockWithParamsActionTypeId);
|
||||
validActionEventBased.insert("thingId", m_mockThingId);
|
||||
validActionEventBased.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
QVariantMap validActionEventBasedParam1;
|
||||
validActionEventBasedParam1.insert("paramTypeId", mockWithParamsActionParam1ParamTypeId);
|
||||
validActionEventBasedParam1.insert("eventTypeId", mockEvent2EventTypeId);
|
||||
@ -675,7 +669,6 @@ void TestRules::editRules_data()
|
||||
QVariantMap validActionNoParams;
|
||||
validActionNoParams.insert("actionTypeId", mockWithoutParamsActionTypeId);
|
||||
validActionNoParams.insert("thingId", m_mockThingId);
|
||||
validActionNoParams.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
|
||||
QVariantMap invalidAction;
|
||||
invalidAction.insert("actionTypeId", ActionTypeId());
|
||||
@ -685,7 +678,6 @@ void TestRules::editRules_data()
|
||||
QVariantMap validExitActionNoParams;
|
||||
validExitActionNoParams.insert("actionTypeId", mockWithoutParamsActionTypeId);
|
||||
validExitActionNoParams.insert("thingId", m_mockThingId);
|
||||
validExitActionNoParams.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
|
||||
QVariantMap invalidExitAction;
|
||||
invalidExitAction.insert("actionTypeId", ActionTypeId());
|
||||
@ -711,12 +703,10 @@ void TestRules::editRules_data()
|
||||
QVariantMap validEventDescriptor1;
|
||||
validEventDescriptor1.insert("eventTypeId", mockEvent1EventTypeId);
|
||||
validEventDescriptor1.insert("thingId", m_mockThingId);
|
||||
validEventDescriptor1.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
|
||||
QVariantMap validEventDescriptor2;
|
||||
validEventDescriptor2.insert("eventTypeId", mockEvent2EventTypeId);
|
||||
validEventDescriptor2.insert("thingId", m_mockThingId);
|
||||
validEventDescriptor2.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
QVariantList params;
|
||||
QVariantMap param1;
|
||||
param1.insert("paramTypeId", mockEvent2EventIntParamParamTypeId);
|
||||
@ -728,7 +718,6 @@ void TestRules::editRules_data()
|
||||
QVariantMap validEventDescriptor3;
|
||||
validEventDescriptor3.insert("eventTypeId", mockEvent2EventTypeId);
|
||||
validEventDescriptor3.insert("thingId", m_mockThingId);
|
||||
validEventDescriptor3.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
|
||||
// EventDescriptorList
|
||||
QVariantList eventDescriptorList;
|
||||
@ -743,7 +732,6 @@ void TestRules::editRules_data()
|
||||
QVariantMap validActionEventBased;
|
||||
validActionEventBased.insert("actionTypeId", mockWithParamsActionTypeId);
|
||||
validActionEventBased.insert("thingId", m_mockThingId);
|
||||
validActionEventBased.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
QVariantMap validActionEventBasedParam1;
|
||||
validActionEventBasedParam1.insert("paramTypeId", mockWithParamsActionParam1ParamTypeId);
|
||||
validActionEventBasedParam1.insert("eventTypeId", mockEvent2EventTypeId);
|
||||
@ -828,11 +816,9 @@ void TestRules::editRules()
|
||||
QVariantMap eventDescriptor1;
|
||||
eventDescriptor1.insert("eventTypeId", mockEvent1EventTypeId);
|
||||
eventDescriptor1.insert("thingId", m_mockThingId);
|
||||
eventDescriptor1.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
QVariantMap eventDescriptor2;
|
||||
eventDescriptor2.insert("eventTypeId", mockEvent2EventTypeId);
|
||||
eventDescriptor2.insert("thingId", m_mockThingId);
|
||||
eventDescriptor2.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
QVariantMap eventParam1;
|
||||
eventParam1.insert("paramTypeId", mockEvent2EventIntParamParamTypeId);
|
||||
eventParam1.insert("value", 3);
|
||||
@ -889,7 +875,6 @@ void TestRules::editRules()
|
||||
QVariantMap validActionEventBased;
|
||||
validActionEventBased.insert("actionTypeId", mockWithParamsActionTypeId);
|
||||
validActionEventBased.insert("thingId", m_mockThingId);
|
||||
validActionEventBased.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
QVariantMap validActionEventBasedParam1;
|
||||
validActionEventBasedParam1.insert("paramTypeId", mockWithParamsActionParam1ParamTypeId);
|
||||
validActionEventBasedParam1.insert("eventTypeId", mockEvent2EventTypeId);
|
||||
@ -903,7 +888,6 @@ void TestRules::editRules()
|
||||
QVariantMap validEventDescriptor3;
|
||||
validEventDescriptor3.insert("eventTypeId", mockEvent2EventTypeId);
|
||||
validEventDescriptor3.insert("thingId", m_mockThingId);
|
||||
validEventDescriptor3.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
validEventDescriptor3.insert("paramDescriptors", QVariantList());
|
||||
validEventDescriptors3.append(validEventDescriptor3);
|
||||
|
||||
@ -978,7 +962,7 @@ void TestRules::editRules()
|
||||
foreach (const QVariant &eventDescriptorVariant, eventDescriptorList) {
|
||||
bool found = false;
|
||||
foreach (const QVariant &replyEventDescriptorVariant, eventDescriptors) {
|
||||
if (eventDescriptorVariant.toMap().value("deviceId") == replyEventDescriptorVariant.toMap().value("deviceId") &&
|
||||
if (eventDescriptorVariant.toMap().value("thingId") == replyEventDescriptorVariant.toMap().value("thingId") &&
|
||||
eventDescriptorVariant.toMap().value("eventTypeId") == replyEventDescriptorVariant.toMap().value("eventTypeId")) {
|
||||
found = true;
|
||||
QVERIFY2(eventDescriptorVariant == replyEventDescriptorVariant, "Event descriptor doesn't match");
|
||||
@ -1135,12 +1119,10 @@ void TestRules::loadStoreConfig()
|
||||
QVariantMap eventDescriptor1;
|
||||
eventDescriptor1.insert("eventTypeId", mockEvent1EventTypeId);
|
||||
eventDescriptor1.insert("thingId", m_mockThingId);
|
||||
eventDescriptor1.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
|
||||
QVariantMap eventDescriptor2;
|
||||
eventDescriptor2.insert("eventTypeId", mockEvent2EventTypeId);
|
||||
eventDescriptor2.insert("thingId", m_mockThingId);
|
||||
eventDescriptor2.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
QVariantList eventParamDescriptors;
|
||||
QVariantMap eventParam1;
|
||||
eventParam1.insert("paramTypeId", mockEvent2EventIntParamParamTypeId);
|
||||
@ -1158,7 +1140,6 @@ void TestRules::loadStoreConfig()
|
||||
|
||||
QVariantMap stateDescriptor2;
|
||||
stateDescriptor2.insert("thingId", m_mockThingId);
|
||||
stateDescriptor2.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
stateDescriptor2.insert("operator", enumValueName(Types::ValueOperatorEquals));
|
||||
stateDescriptor2.insert("stateTypeId", mockIntStateTypeId);
|
||||
stateDescriptor2.insert("value", 1);
|
||||
@ -1168,7 +1149,6 @@ void TestRules::loadStoreConfig()
|
||||
|
||||
QVariantMap stateDescriptor3;
|
||||
stateDescriptor3.insert("thingId", m_mockThingId);
|
||||
stateDescriptor3.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
stateDescriptor3.insert("operator", enumValueName(Types::ValueOperatorEquals));
|
||||
stateDescriptor3.insert("stateTypeId", mockBoolStateTypeId);
|
||||
stateDescriptor3.insert("value", true);
|
||||
@ -1196,13 +1176,11 @@ void TestRules::loadStoreConfig()
|
||||
QVariantMap action1;
|
||||
action1.insert("actionTypeId", mockWithoutParamsActionTypeId);
|
||||
action1.insert("thingId", m_mockThingId);
|
||||
action1.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
action1.insert("ruleActionParams", QVariantList());
|
||||
|
||||
QVariantMap action2;
|
||||
action2.insert("actionTypeId", mockWithParamsActionTypeId);
|
||||
action2.insert("thingId", m_mockThingId);
|
||||
action2.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
QVariantList action2Params;
|
||||
QVariantMap action2Param1;
|
||||
action2Param1.insert("paramTypeId", mockWithParamsActionParam1ParamTypeId);
|
||||
@ -1218,7 +1196,6 @@ void TestRules::loadStoreConfig()
|
||||
QVariantMap validActionEventBased;
|
||||
validActionEventBased.insert("actionTypeId", mockWithParamsActionTypeId);
|
||||
validActionEventBased.insert("thingId", m_mockThingId);
|
||||
validActionEventBased.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
QVariantMap validActionEventBasedParam1;
|
||||
validActionEventBasedParam1.insert("paramTypeId", mockWithParamsActionParam1ParamTypeId);
|
||||
validActionEventBasedParam1.insert("eventTypeId", mockEvent2EventTypeId);
|
||||
@ -1232,7 +1209,6 @@ void TestRules::loadStoreConfig()
|
||||
QVariantMap validEventDescriptor3;
|
||||
validEventDescriptor3.insert("eventTypeId", mockEvent2EventTypeId);
|
||||
validEventDescriptor3.insert("thingId", m_mockThingId);
|
||||
validEventDescriptor3.insert("deviceId", m_mockThingId); // DEPRECATED
|
||||
validEventDescriptors3.append(validEventDescriptor3);
|
||||
|
||||
// Interface based event descriptor
|
||||
@ -1446,7 +1422,7 @@ void TestRules::loadStoreConfig()
|
||||
QVERIFY2(stateDescriptor.value("interface") == "battery", "Interface of stateDescriptor does not match");
|
||||
QVERIFY2(stateDescriptor.value("interfaceState") == "batteryCritical", "InterfaceState of stateDescriptor doesn't match");
|
||||
} else {
|
||||
QVERIFY2(false, "StateDescriptor must have either deviceId/stateTypeId or interface/interfaceState.");
|
||||
QVERIFY2(false, "StateDescriptor must have either thingId/stateTypeId or interface/interfaceState.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -99,11 +99,11 @@ void TestScripts::testScriptEventById()
|
||||
QString script = QString("import QtQuick 2.0\n"
|
||||
"import nymea 1.0\n"
|
||||
"Item {\n"
|
||||
" DeviceEvent {\n"
|
||||
" deviceId: \"%1\"\n"
|
||||
" ThingEvent {\n"
|
||||
" thingId: \"%1\"\n"
|
||||
" eventTypeId: \"%2\"\n"
|
||||
" onTriggered: {\n"
|
||||
" TestHelper.logEvent(deviceId, eventTypeId, params);\n"
|
||||
" TestHelper.logEvent(thingId, eventTypeId, params);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n").arg(m_mockThingId.toString()).arg(mockPowerEventTypeId.toString());
|
||||
@ -135,11 +135,11 @@ void TestScripts::testScriptEventByName()
|
||||
QString script = QString("import QtQuick 2.0\n"
|
||||
"import nymea 1.0\n"
|
||||
"Item {\n"
|
||||
" DeviceEvent {\n"
|
||||
" deviceId: \"%1\"\n"
|
||||
" ThingEvent {\n"
|
||||
" thingId: \"%1\"\n"
|
||||
" eventName: \"%2\"\n"
|
||||
" onTriggered: {\n"
|
||||
" TestHelper.logEvent(deviceId, eventName, params);\n"
|
||||
" TestHelper.logEvent(thingId, eventName, params);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n").arg(m_mockThingId.toString()).arg("power");
|
||||
@ -171,11 +171,11 @@ void TestScripts::testReadScriptStateById()
|
||||
QString script = QString("import QtQuick 2.0\n"
|
||||
"import nymea 1.0\n"
|
||||
"Item {\n"
|
||||
" DeviceState {\n"
|
||||
" deviceId: \"%1\"\n"
|
||||
" ThingState {\n"
|
||||
" thingId: \"%1\"\n"
|
||||
" stateTypeId: \"%2\"\n"
|
||||
" onValueChanged: {\n"
|
||||
" TestHelper.logStateChange(deviceId, stateTypeId, value);\n"
|
||||
" TestHelper.logStateChange(thingId, stateTypeId, value);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n").arg(m_mockThingId.toString()).arg(mockPowerStateTypeId.toString());
|
||||
@ -204,11 +204,11 @@ void TestScripts::testReadScriptStateByNyme()
|
||||
QString script = QString("import QtQuick 2.0\n"
|
||||
"import nymea 1.0\n"
|
||||
"Item {\n"
|
||||
" DeviceState {\n"
|
||||
" deviceId: \"%1\"\n"
|
||||
" ThingState {\n"
|
||||
" thingId: \"%1\"\n"
|
||||
" stateName: \"%2\"\n"
|
||||
" onValueChanged: {\n"
|
||||
" TestHelper.logStateChange(deviceId, stateName, value);\n"
|
||||
" TestHelper.logStateChange(thingId, stateName, value);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n").arg(m_mockThingId.toString()).arg("power");
|
||||
@ -237,15 +237,15 @@ void TestScripts::testWriteScriptStateById()
|
||||
QString script = QString("import QtQuick 2.0\n"
|
||||
"import nymea 1.0\n"
|
||||
"Item {\n"
|
||||
" DeviceState {\n"
|
||||
" id: deviceState\n"
|
||||
" deviceId: \"%1\"\n"
|
||||
" ThingState {\n"
|
||||
" id: thingState\n"
|
||||
" thingId: \"%1\"\n"
|
||||
" stateTypeId: \"%2\"\n"
|
||||
" }\n"
|
||||
" Connections {\n"
|
||||
" target: TestHelper\n"
|
||||
" onSetState: {\n"
|
||||
" deviceState.value = value\n"
|
||||
" thingState.value = value\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n").arg(m_mockThingId.toString()).arg(mockPowerStateTypeId.toString());
|
||||
@ -271,15 +271,15 @@ void TestScripts::testWriteScriptStateByName()
|
||||
QString script = QString("import QtQuick 2.0\n"
|
||||
"import nymea 1.0\n"
|
||||
"Item {\n"
|
||||
" DeviceState {\n"
|
||||
" id: deviceState\n"
|
||||
" deviceId: \"%1\"\n"
|
||||
" ThingState {\n"
|
||||
" id: thingState\n"
|
||||
" thingId: \"%1\"\n"
|
||||
" stateName: \"%2\"\n"
|
||||
" }\n"
|
||||
" Connections {\n"
|
||||
" target: TestHelper\n"
|
||||
" onSetState: {\n"
|
||||
" deviceState.value = value\n"
|
||||
" thingState.value = value\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n").arg(m_mockThingId.toString()).arg("power");
|
||||
@ -305,15 +305,15 @@ void TestScripts::testScriptActionById()
|
||||
QString script = QString("import QtQuick 2.0\n"
|
||||
"import nymea 1.0\n"
|
||||
"Item {\n"
|
||||
" DeviceAction {\n"
|
||||
" id: deviceAction\n"
|
||||
" deviceId: \"%1\"\n"
|
||||
" ThingAction {\n"
|
||||
" id: thingAction\n"
|
||||
" thingId: \"%1\"\n"
|
||||
" actionTypeId: \"%2\"\n"
|
||||
" }\n"
|
||||
" Connections {\n"
|
||||
" target: TestHelper\n"
|
||||
" onExecuteAction: {\n"
|
||||
" deviceAction.execute(params)\n"
|
||||
" thingAction.execute(params)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n").arg(m_mockThingId.toString()).arg(mockPowerActionTypeId.toString());
|
||||
@ -341,15 +341,15 @@ void TestScripts::testScriptActionByName()
|
||||
QString script = QString("import QtQuick 2.0\n"
|
||||
"import nymea 1.0\n"
|
||||
"Item {\n"
|
||||
" DeviceAction {\n"
|
||||
" id: deviceAction\n"
|
||||
" deviceId: \"%1\"\n"
|
||||
" ThingAction {\n"
|
||||
" id: thingAction\n"
|
||||
" thingId: \"%1\"\n"
|
||||
" actionName: \"%2\"\n"
|
||||
" }\n"
|
||||
" Connections {\n"
|
||||
" target: TestHelper\n"
|
||||
" onExecuteAction: {\n"
|
||||
" deviceAction.execute(params)\n"
|
||||
" thingAction.execute(params)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n").arg(m_mockThingId.toString()).arg("power");
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
include(../../../nymea.pri)
|
||||
include(../autotests.pri)
|
||||
|
||||
TARGET = states
|
||||
SOURCES += teststates.cpp
|
||||
@ -1,144 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "nymeatestbase.h"
|
||||
#include "nymeacore.h"
|
||||
#include "jsonrpc/devicehandler.h"
|
||||
|
||||
using namespace nymeaserver;
|
||||
|
||||
class TestStates: public NymeaTestBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void getStateTypes();
|
||||
|
||||
void getStateValue_data();
|
||||
void getStateValue();
|
||||
|
||||
void save_load_states();
|
||||
};
|
||||
|
||||
void TestStates::getStateTypes()
|
||||
{
|
||||
QVariantMap params;
|
||||
params.insert("deviceClassId", mockThingClassId);
|
||||
QVariant response = injectAndWait("Devices.GetStateTypes", params);
|
||||
QVERIFY(!response.isNull());
|
||||
//verifyDeviceError(response);
|
||||
}
|
||||
|
||||
void TestStates::getStateValue_data()
|
||||
{
|
||||
QList<Thing*> devices = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId);
|
||||
QVERIFY2(devices.count() > 0, "There needs to be at least one configured Mock Device for this test");
|
||||
Thing *device = devices.first();
|
||||
|
||||
QTest::addColumn<ThingId>("deviceId");
|
||||
QTest::addColumn<StateTypeId>("stateTypeId");
|
||||
QTest::addColumn<Device::DeviceError>("error");
|
||||
|
||||
QTest::newRow("existing state") << device->id() << mockIntStateTypeId << Device::DeviceErrorNoError;
|
||||
QTest::newRow("invalid device") << ThingId::createThingId() << mockIntStateTypeId << Device::DeviceErrorDeviceNotFound;
|
||||
QTest::newRow("invalid statetype") << device->id() << StateTypeId::createStateTypeId() << Device::DeviceErrorStateTypeNotFound;
|
||||
}
|
||||
|
||||
void TestStates::getStateValue()
|
||||
{
|
||||
QFETCH(ThingId, deviceId);
|
||||
QFETCH(StateTypeId, stateTypeId);
|
||||
QFETCH(Device::DeviceError, error);
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("deviceId", deviceId.toString());
|
||||
params.insert("stateTypeId", stateTypeId.toString());
|
||||
|
||||
QVariant response = injectAndWait("Devices.GetStateValue", params);
|
||||
|
||||
verifyError(response, "deviceError", enumValueName(error));
|
||||
}
|
||||
|
||||
void TestStates::save_load_states()
|
||||
{
|
||||
ThingClass mockDeviceClass = NymeaCore::instance()->thingManager()->findThingClass(mockThingClassId);
|
||||
|
||||
QVERIFY2(mockDeviceClass.getStateType(mockIntStateTypeId).cached(), "Mock int state is not cached (required to be true for this test)");
|
||||
QVERIFY2(!mockDeviceClass.getStateType(mockBoolStateTypeId).cached(), "Mock bool state is cached (required to be false for this test)");
|
||||
|
||||
Thing* device = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId).first();
|
||||
int port = device->paramValue(mockThingHttpportParamTypeId).toInt();
|
||||
QNetworkAccessManager nam;
|
||||
QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*)));
|
||||
|
||||
|
||||
// First set the state values to something that is *not* the default
|
||||
int newIntValue = mockDeviceClass.getStateType(mockIntStateTypeId).defaultValue().toInt() + 1;
|
||||
bool newBoolValue = !mockDeviceClass.getStateType(mockBoolStateTypeId).defaultValue().toBool();
|
||||
|
||||
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockIntStateTypeId.toString()).arg(newIntValue)));
|
||||
QNetworkReply *reply = nam.get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
spy.wait();
|
||||
|
||||
spy.clear();
|
||||
request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockBoolStateTypeId.toString()).arg(newBoolValue)));
|
||||
reply = nam.get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
spy.wait();
|
||||
|
||||
// For completeness, verify through JSONRPC that they were actually yet.
|
||||
QVariantMap params;
|
||||
params.insert("deviceId", device->id());
|
||||
|
||||
params["stateTypeId"] = mockIntStateTypeId;
|
||||
QVariant response = injectAndWait("Devices.GetStateValue", params);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("value").toInt(), newIntValue);
|
||||
|
||||
params["stateTypeId"] = mockBoolStateTypeId;
|
||||
response = injectAndWait("Devices.GetStateValue", params);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("value").toBool(), newBoolValue);
|
||||
|
||||
// Restart the server
|
||||
restartServer();
|
||||
|
||||
// And check if the cached int state has successfully been restored
|
||||
params["stateTypeId"] = mockIntStateTypeId;
|
||||
response = injectAndWait("Devices.GetStateValue", params);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("value").toInt(), newIntValue);
|
||||
|
||||
// and that the non-cached bool state is back to its default
|
||||
params["stateTypeId"] = mockBoolStateTypeId;
|
||||
response = injectAndWait("Devices.GetStateValue", params);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("value").toBool(), mockDeviceClass.getStateType(mockBoolStateTypeId).defaultValue().toBool());
|
||||
}
|
||||
|
||||
#include "teststates.moc"
|
||||
QTEST_MAIN(TestStates)
|
||||
@ -83,7 +83,6 @@ QVariantMap TestTags::createRuleTag(const QString &ruleId, const QString &appId,
|
||||
bool TestTags::compareThingTag(const QVariantMap &tag, const QUuid &thingId, const QString &appId, const QString &tagId, const QString &value)
|
||||
{
|
||||
return tag.value("thingId").toUuid() == thingId &&
|
||||
tag.value("deviceId").toUuid() == thingId && // backwards compatibility to < 0.19 adds deviceId along with thingId
|
||||
tag.value("appId").toString() == appId &&
|
||||
tag.value("tagId").toString() == tagId &&
|
||||
tag.value("value").toString() == value;
|
||||
|
||||
@ -141,14 +141,13 @@ private:
|
||||
|
||||
void TestTimeManager::initTestCase()
|
||||
{
|
||||
NymeaTestBase::initTestCase();
|
||||
QLoggingCategory::setFilterRules("*.debug=false\n"
|
||||
"Tests.debug=true\n"
|
||||
"RuleEngine.debug=true\n"
|
||||
// "RuleEngineDebug.debug=true\n"
|
||||
"Mock.debug=true\n"
|
||||
"JsonRpc.debug=true\n"
|
||||
"TimeManager.debug=true");
|
||||
NymeaTestBase::initTestCase("*.debug=false\n"
|
||||
"Tests.debug=true\n"
|
||||
"RuleEngine.debug=true\n"
|
||||
// "RuleEngineDebug.debug=true\n"
|
||||
"Mock.debug=true\n"
|
||||
"JsonRpc.debug=true\n"
|
||||
"TimeManager.debug=true");
|
||||
}
|
||||
|
||||
void TestTimeManager::loadSaveTimeDescriptor_data()
|
||||
@ -2028,7 +2027,7 @@ void TestTimeManager::initTimeManager()
|
||||
{
|
||||
cleanupMockHistory();
|
||||
removeAllRules();
|
||||
enableNotifications({"Rules", "Integrations", "Events"});
|
||||
enableNotifications({"Rules", "Integrations"});
|
||||
NymeaCore::instance()->timeManager()->stopTimer();
|
||||
qDebug() << NymeaCore::instance()->timeManager()->currentDateTime().toString();
|
||||
}
|
||||
@ -2183,14 +2182,14 @@ void TestTimeManager::triggerMockEvent1()
|
||||
if (eventSpy.isEmpty()) {
|
||||
eventSpy.wait();
|
||||
}
|
||||
QVariantList eventTriggerVariants = checkNotifications(eventSpy, "Events.EventTriggered");
|
||||
QVariantList eventTriggerVariants = checkNotifications(eventSpy, "Integrations.EventTriggered");
|
||||
QVERIFY2(eventTriggerVariants.count() == 1, "Did not get Events.EventTriggered notification.");
|
||||
QVERIFY2(eventTriggerVariants.first().toMap().value("params").toMap().contains("event"), "Notification Events.EventTriggered does not contain event.");
|
||||
QVariantMap eventMap = eventTriggerVariants.first().toMap().value("params").toMap().value("event").toMap();
|
||||
|
||||
QVERIFY2(eventMap.contains("thingId"), "Events.EventTriggered notification does not contain thingId");
|
||||
QVERIFY2(ThingId(eventMap.value("thingId").toString()) == m_mockThingId, "Events.EventTriggered notification does not contain the correct thingId");
|
||||
QVERIFY2(eventMap.contains("eventTypeId"), "Events.EventTriggered notification does not contain eventTypeId");
|
||||
QVERIFY2(eventMap.contains("thingId"), "Integrations.EventTriggered notification does not contain thingId");
|
||||
QVERIFY2(ThingId(eventMap.value("thingId").toString()) == m_mockThingId, "Integrations.EventTriggered notification does not contain the correct thingId");
|
||||
QVERIFY2(eventMap.contains("eventTypeId"), "Integrations.EventTriggered notification does not contain eventTypeId");
|
||||
QVERIFY2(EventTypeId(eventMap.value("eventTypeId").toString()) == mockEvent1EventTypeId, "Events.EventTriggered notification does not contain the correct eventTypeId");
|
||||
}
|
||||
|
||||
|
||||
@ -121,9 +121,7 @@ TestUsermanager::TestUsermanager(QObject *parent): NymeaTestBase(parent)
|
||||
void TestUsermanager::initTestCase()
|
||||
{
|
||||
NymeaDBusService::setBusType(QDBusConnection::SessionBus);
|
||||
NymeaTestBase::initTestCase();
|
||||
|
||||
QLoggingCategory::setFilterRules("*.debug=false\n"
|
||||
NymeaTestBase::initTestCase("*.debug=false\n"
|
||||
"Application.debug=true\n"
|
||||
"Tests.debug=true\n"
|
||||
"UserManager.debug=true\n"
|
||||
@ -134,12 +132,11 @@ void TestUsermanager::initTestCase()
|
||||
void TestUsermanager::init()
|
||||
{
|
||||
UserManager *userManager = NymeaCore::instance()->userManager();
|
||||
foreach (const QString &user, userManager->users()) {
|
||||
qCDebug(dcTests()) << "Removing user" << user;
|
||||
userManager->removeUser(user);
|
||||
foreach (const UserInfo &userInfo, userManager->users()) {
|
||||
qCDebug(dcTests()) << "Removing user" << userInfo.username();
|
||||
userManager->removeUser(userInfo.username());
|
||||
}
|
||||
userManager->removeUser("");
|
||||
|
||||
}
|
||||
|
||||
void TestUsermanager::loginValidation_data() {
|
||||
@ -151,12 +148,9 @@ void TestUsermanager::loginValidation_data() {
|
||||
QTest::newRow("foo@bar.co.uk, Bla1234*, NoError") << "foo@bar.co.uk" << "Bla1234*" << UserManager::UserErrorNoError;
|
||||
QTest::newRow("foo@bar.com.au, Bla1234*, NoError") << "foo@bar.com.au" << "Bla1234*" << UserManager::UserErrorNoError;
|
||||
|
||||
QTest::newRow("foo, Bla1234*, InvalidUserId") << "foo" << "Bla1234*" << UserManager::UserErrorInvalidUserId;
|
||||
QTest::newRow("@, Bla1234*, InvalidUserId") << "@" << "Bla1234*" << UserManager::UserErrorInvalidUserId;
|
||||
QTest::newRow("foo@, Bla1234*, InvalidUserId") << "foo@" << "Bla1234*" << UserManager::UserErrorInvalidUserId;
|
||||
QTest::newRow("foo@bar, Bla1234*, InvalidUserId") << "foo@bar" << "Bla1234*" << UserManager::UserErrorInvalidUserId;
|
||||
QTest::newRow("foo@bar., Bla1234*, InvalidUserId") << "foo@bar." << "Bla1234*" << UserManager::UserErrorInvalidUserId;
|
||||
QTest::newRow("foo@bar.co.uk.au, Bla1234*, InvalidUserId") << "foo@bar.co.uk.au" << "Bla1234*" << UserManager::UserErrorInvalidUserId;
|
||||
QTest::newRow("n, Bla1234*, InvalidUserId") << "n" << "Bla1234*" << UserManager::UserErrorInvalidUserId;
|
||||
QTest::newRow("@, Bla1234*, InvalidUserId") << "@" << "Bla1234*" << UserManager::UserErrorInvalidUserId;
|
||||
QTest::newRow("nymea, Bla1234*, InvalidUserId") << "nymea" << "Bla1234*" << UserManager::UserErrorNoError;
|
||||
|
||||
QTest::newRow("foo@bar.baz, a, BadPassword") << "foo@bar.baz" << "a" << UserManager::UserErrorBadPassword;
|
||||
QTest::newRow("foo@bar.baz, a1, BadPassword") << "foo@bar.baz" << "a1" << UserManager::UserErrorBadPassword;
|
||||
@ -184,10 +178,9 @@ void TestUsermanager::loginValidation()
|
||||
QFETCH(UserManager::UserError, expectedError);
|
||||
|
||||
UserManager *userManager = NymeaCore::instance()->userManager();
|
||||
UserManager::UserError error = userManager->createUser(username, password);
|
||||
UserManager::UserError error = userManager->createUser(username, password, "", "", Types::PermissionScopeAdmin);
|
||||
qDebug() << "Error:" << error << "Expected:" << expectedError;
|
||||
QCOMPARE(error, expectedError);
|
||||
|
||||
}
|
||||
|
||||
void TestUsermanager::createUser()
|
||||
@ -195,7 +188,7 @@ void TestUsermanager::createUser()
|
||||
QVariantMap params;
|
||||
params.insert("username", "valid@user.test");
|
||||
params.insert("password", "Bla1234*");
|
||||
QVariant response = injectAndWait("Users.CreateUser", params);
|
||||
QVariant response = injectAndWait("JSONRPC.CreateUser", params);
|
||||
|
||||
QVERIFY2(response.toMap().value("status").toString() == "success", "Error creating user");
|
||||
QVERIFY2(response.toMap().value("params").toMap().value("error").toString() == "UserErrorNoError", "Error creating user");
|
||||
@ -210,7 +203,7 @@ void TestUsermanager::authenticate()
|
||||
params.insert("username", "valid@user.test");
|
||||
params.insert("password", "Bla1234*");
|
||||
params.insert("deviceName", "autotests");
|
||||
QVariant response = injectAndWait("Users.Authenticate", params);
|
||||
QVariant response = injectAndWait("JSONRPC.Authenticate", params);
|
||||
|
||||
m_apiToken = response.toMap().value("params").toMap().value("token").toByteArray();
|
||||
|
||||
@ -225,7 +218,7 @@ void TestUsermanager::authenticatePushButton()
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("deviceName", "pbtestdevice");
|
||||
QVariant response = injectAndWait("Users.RequestPushButtonAuth", params);
|
||||
QVariant response = injectAndWait("JSONRPC.RequestPushButtonAuth", params);
|
||||
qCDebug(dcTests()) << "Pushbutton auth response:" << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
|
||||
int transactionId = response.toMap().value("params").toMap().value("transactionId").toInt();
|
||||
@ -236,7 +229,7 @@ void TestUsermanager::authenticatePushButton()
|
||||
pushButtonAgent.sendButtonPressed();
|
||||
|
||||
if (clientSpy.count() == 0) clientSpy.wait();
|
||||
QVariantMap rsp = checkNotification(clientSpy, "Users.PushButtonAuthFinished").toMap();
|
||||
QVariantMap rsp = checkNotification(clientSpy, "JSONRPC.PushButtonAuthFinished").toMap();
|
||||
|
||||
for (int i = 0; i < clientSpy.count(); i++) {
|
||||
qCDebug(dcTests()) << "Notification:" << clientSpy.at(i);
|
||||
@ -267,7 +260,7 @@ void TestUsermanager::authenticatePushButtonAuthInterrupt()
|
||||
// request push button auth for client 1 (alice) and check for OK reply
|
||||
QVariantMap params;
|
||||
params.insert("deviceName", "alice");
|
||||
QVariant response = injectAndWait("Users.RequestPushButtonAuth", params, aliceId);
|
||||
QVariant response = injectAndWait("JSONRPC.RequestPushButtonAuth", params, aliceId);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
|
||||
int transactionId1 = response.toMap().value("params").toMap().value("transactionId").toInt();
|
||||
|
||||
@ -276,7 +269,7 @@ void TestUsermanager::authenticatePushButtonAuthInterrupt()
|
||||
clientSpy.clear();
|
||||
params.clear();
|
||||
params.insert("deviceName", "mallory");
|
||||
response = injectAndWait("Users.RequestPushButtonAuth", params, malloryId);
|
||||
response = injectAndWait("JSONRPC.RequestPushButtonAuth", params, malloryId);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
|
||||
int transactionId2 = response.toMap().value("params").toMap().value("transactionId").toInt();
|
||||
|
||||
@ -292,7 +285,7 @@ void TestUsermanager::authenticatePushButtonAuthInterrupt()
|
||||
// alice should have received a failed notification. She knows something's wrong.
|
||||
QVariantMap notification = QJsonDocument::fromJson(clientSpy.first().at(1).toByteArray()).toVariant().toMap();
|
||||
QCOMPARE(clientSpy.first().first().toUuid(), aliceId);
|
||||
QCOMPARE(notification.value("notification").toString(), QLatin1String("Users.PushButtonAuthFinished"));
|
||||
QCOMPARE(notification.value("notification").toString(), QLatin1String("JSONRPC.PushButtonAuthFinished"));
|
||||
QCOMPARE(notification.value("params").toMap().value("transactionId").toInt(), transactionId1);
|
||||
QCOMPARE(notification.value("params").toMap().value("success").toBool(), false);
|
||||
|
||||
@ -306,7 +299,7 @@ void TestUsermanager::authenticatePushButtonAuthInterrupt()
|
||||
clientSpy.clear();
|
||||
params.clear();
|
||||
params.insert("deviceName", "alice");
|
||||
response = injectAndWait("Users.RequestPushButtonAuth", params, aliceId);
|
||||
response = injectAndWait("JSONRPC.RequestPushButtonAuth", params, aliceId);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
|
||||
int transactionId3 = response.toMap().value("params").toMap().value("transactionId").toInt();
|
||||
|
||||
@ -321,7 +314,7 @@ void TestUsermanager::authenticatePushButtonAuthInterrupt()
|
||||
// mallory should have received a failed notification. She knows something's wrong.
|
||||
notification = QJsonDocument::fromJson(clientSpy.first().at(1).toByteArray()).toVariant().toMap();
|
||||
QCOMPARE(clientSpy.first().first().toUuid(), malloryId);
|
||||
QCOMPARE(notification.value("notification").toString(), QLatin1String("Users.PushButtonAuthFinished"));
|
||||
QCOMPARE(notification.value("notification").toString(), QLatin1String("JSONRPC.PushButtonAuthFinished"));
|
||||
QCOMPARE(notification.value("params").toMap().value("transactionId").toInt(), transactionId2);
|
||||
QCOMPARE(notification.value("params").toMap().value("success").toBool(), false);
|
||||
|
||||
@ -345,7 +338,7 @@ void TestUsermanager::authenticatePushButtonAuthInterrupt()
|
||||
QCOMPARE(clientSpy.count(), 1);
|
||||
notification = QJsonDocument::fromJson(clientSpy.first().at(1).toByteArray()).toVariant().toMap();
|
||||
QCOMPARE(clientSpy.first().first().toUuid(), aliceId);
|
||||
QCOMPARE(notification.value("notification").toString(), QLatin1String("Users.PushButtonAuthFinished"));
|
||||
QCOMPARE(notification.value("notification").toString(), QLatin1String("JSONRPC.PushButtonAuthFinished"));
|
||||
QCOMPARE(notification.value("params").toMap().value("transactionId").toInt(), transactionId3);
|
||||
QCOMPARE(notification.value("params").toMap().value("success").toBool(), true);
|
||||
QVERIFY2(!notification.value("params").toMap().value("token").toByteArray().isEmpty(), "Token is empty while it shouldn't be");
|
||||
@ -368,7 +361,7 @@ void TestUsermanager::authenticatePushButtonAuthConnectionDrop()
|
||||
// request push button auth for client 1 (alice) and check for OK reply
|
||||
QVariantMap params;
|
||||
params.insert("deviceName", "alice");
|
||||
QVariant response = injectAndWait("Users.RequestPushButtonAuth", params, aliceId);
|
||||
QVariant response = injectAndWait("JSONRPC.RequestPushButtonAuth", params, aliceId);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
|
||||
|
||||
// Disconnect alice
|
||||
@ -385,7 +378,7 @@ void TestUsermanager::authenticatePushButtonAuthConnectionDrop()
|
||||
// request push button auth for client 2 (bob) and check for OK reply
|
||||
params.clear();
|
||||
params.insert("deviceName", "bob");
|
||||
response = injectAndWait("Users.RequestPushButtonAuth", params, bobId);
|
||||
response = injectAndWait("JSONRPC.RequestPushButtonAuth", params, bobId);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
|
||||
int transactionId = response.toMap().value("params").toMap().value("transactionId").toInt();
|
||||
|
||||
@ -402,7 +395,7 @@ void TestUsermanager::authenticatePushButtonAuthConnectionDrop()
|
||||
QCOMPARE(clientSpy.count(), 1);
|
||||
QVariantMap notification = QJsonDocument::fromJson(clientSpy.first().at(1).toByteArray()).toVariant().toMap();
|
||||
QCOMPARE(clientSpy.first().first().toUuid(), bobId);
|
||||
QCOMPARE(notification.value("notification").toString(), QLatin1String("Users.PushButtonAuthFinished"));
|
||||
QCOMPARE(notification.value("notification").toString(), QLatin1String("JSONRPC.PushButtonAuthFinished"));
|
||||
QCOMPARE(notification.value("params").toMap().value("transactionId").toInt(), transactionId);
|
||||
QCOMPARE(notification.value("params").toMap().value("success").toBool(), true);
|
||||
QVERIFY2(!notification.value("params").toMap().value("token").toByteArray().isEmpty(), "Token is empty while it shouldn't be");
|
||||
@ -469,7 +462,7 @@ void TestUsermanager::authenticateAfterPasswordChangeOK()
|
||||
params.insert("username", "valid@user.test");
|
||||
params.insert("password", "Blubb123"); // New password, should be ok
|
||||
params.insert("deviceName", "autotests");
|
||||
QVariant response = injectAndWait("Users.Authenticate", params);
|
||||
QVariant response = injectAndWait("JSONRPC.Authenticate", params);
|
||||
|
||||
m_apiToken = response.toMap().value("params").toMap().value("token").toByteArray();
|
||||
QVERIFY2(!m_apiToken.isEmpty(), "Token should not be empty");
|
||||
@ -481,16 +474,27 @@ void TestUsermanager::authenticateAfterPasswordChangeFail()
|
||||
{
|
||||
changePassword();
|
||||
|
||||
QSignalSpy disconnectedSpy(m_mockTcpServer, &MockTcpServer::clientDisconnected);
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("username", "valid@user.test");
|
||||
params.insert("password", "Bla1234*"); // Original password, should not be ok
|
||||
params.insert("deviceName", "autotests");
|
||||
QVariant response = injectAndWait("Users.Authenticate", params);
|
||||
QVariant response = injectAndWait("JSONRPC.Authenticate", params);
|
||||
|
||||
m_apiToken = response.toMap().value("params").toMap().value("token").toByteArray();
|
||||
QVERIFY2(m_apiToken.isEmpty(), "Token should be empty");
|
||||
QVERIFY2(response.toMap().value("status").toString() == "success", "Error authenticating");
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("success").toString(), QString("false"));
|
||||
|
||||
// Connection should drop
|
||||
if (disconnectedSpy.count() == 0) disconnectedSpy.wait();
|
||||
QVERIFY2(disconnectedSpy.count() == 1, "Connection should have dropped");
|
||||
|
||||
QTest::qWait(3200);
|
||||
m_mockTcpServer->clientConnected(m_clientId);
|
||||
injectAndWait("JSONRPC.Hello");
|
||||
|
||||
}
|
||||
|
||||
void TestUsermanager::getUserInfo()
|
||||
@ -522,8 +526,9 @@ void TestUsermanager::unauthenticatedCallAfterTokenRemove()
|
||||
}
|
||||
QVERIFY2(spy.count() == 1, "Connection should be terminated!");
|
||||
|
||||
// need to restart as our connection dies
|
||||
restartServer();
|
||||
QTest::qWait(3200);
|
||||
m_mockTcpServer->clientConnected(m_clientId);
|
||||
injectAndWait("JSONRPC.Hello");
|
||||
}
|
||||
|
||||
#include "testusermanager.moc"
|
||||
|
||||
@ -88,9 +88,9 @@ void NymeaTestBase::initTestCase(const QString &loggingRules)
|
||||
qCDebug(dcTests()) << "Nymea core instance initialized. Creating dummy user.";
|
||||
|
||||
// Yes, we're intentionally mixing upper/lower case email here... username should not be case sensitive
|
||||
NymeaCore::instance()->userManager()->removeUser("dummy@guh.io");
|
||||
NymeaCore::instance()->userManager()->createUser("dummy@guh.io", "DummyPW1!");
|
||||
m_apiToken = NymeaCore::instance()->userManager()->authenticate("Dummy@guh.io", "DummyPW1!", "testcase");
|
||||
NymeaCore::instance()->userManager()->removeUser("dummy");
|
||||
NymeaCore::instance()->userManager()->createUser("dummy", "DummyPW1!", "dummy@guh.io", "Dummy", Types::PermissionScopeAdmin);
|
||||
m_apiToken = NymeaCore::instance()->userManager()->authenticate("Dummy", "DummyPW1!", "testcase");
|
||||
|
||||
if (MockTcpServer::servers().isEmpty()) {
|
||||
qCWarning(dcTests) << "no mock tcp server found";
|
||||
|
||||
Reference in New Issue
Block a user