Merge PR #425: Add support fur user permissions

This commit is contained in:
Jenkins nymea 2022-03-27 19:49:21 +02:00
commit fca9ecee20
63 changed files with 1197 additions and 5610 deletions

View File

@ -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 &params, 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 &params, 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 &params)
{
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 &params)
{
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;
}
}

View File

@ -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 &params, const JsonContext &context);
Q_INVOKABLE JsonReply *GetActionType(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *ExecuteBrowserItem(const QVariantMap &params);
Q_INVOKABLE JsonReply *ExecuteBrowserItemAction(const QVariantMap &params);
};
}
#endif // ACTIONHANDLER_H

View File

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

View File

@ -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 &notification, const QVariantMap &params, const QLocale &locale) override;
Q_INVOKABLE JsonReply *GetSupportedVendors(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *GetSupportedDevices(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *GetDiscoveredDevices(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *GetPlugins(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *GetPluginConfiguration(const QVariantMap &params) const;
Q_INVOKABLE JsonReply *SetPluginConfiguration(const QVariantMap &params);
Q_INVOKABLE JsonReply *AddConfiguredDevice(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *PairDevice(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *ConfirmPairing(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *GetConfiguredDevices(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *ReconfigureDevice(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *EditDevice(const QVariantMap &params);
Q_INVOKABLE JsonReply *RemoveConfiguredDevice(const QVariantMap &params);
Q_INVOKABLE JsonReply *SetDeviceSettings(const QVariantMap &params);
Q_INVOKABLE JsonReply *GetEventTypes(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *GetActionTypes(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *GetStateTypes(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *GetStateValue(const QVariantMap &params) const;
Q_INVOKABLE JsonReply *GetStateValues(const QVariantMap &params) const;
Q_INVOKABLE JsonReply *BrowseDevice(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *GetBrowserItem(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *ExecuteAction(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *ExecuteBrowserItem(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *ExecuteBrowserItemAction(const QVariantMap &params, const JsonContext &context);
static QVariantMap packBrowserItem(const BrowserItem &item);
signals:
void PluginConfigurationChanged(const QVariantMap &params);
void StateChanged(const QVariantMap &params);
void DeviceRemoved(const QVariantMap &params);
void DeviceAdded(const QVariantMap &params);
void DeviceChanged(const QVariantMap &params);
void DeviceSettingChanged(const QVariantMap &params);
void EventTriggered(const QVariantMap &params);
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 &paramTypeId, 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

View File

@ -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 &params, 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);
}
}

View File

@ -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 &params, const JsonContext &context) const;
signals:
void EventTriggered(const QVariantMap &params);
private slots:
void eventTriggered(const Event &event);
};
}
#endif // EVENTHANDLER_H

View File

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

View File

@ -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 &params, 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 &params) const
@ -342,15 +402,17 @@ JsonReply *JsonRPCServerImplementation::CreateUser(const QVariantMap &params)
{
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 &params)
JsonReply *JsonRPCServerImplementation::Authenticate(const QVariantMap &params, const JsonContext &context)
{
QString username = params.value("username").toString();
QString password = params.value("password").toString();
@ -361,7 +423,26 @@ JsonReply *JsonRPCServerImplementation::Authenticate(const QVariantMap &params)
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 &params, 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 &params)
{
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 &params)
{
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

View File

@ -63,10 +63,8 @@ public:
Q_INVOKABLE JsonReply *SetNotificationStatus(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *CreateUser(const QVariantMap &params);
Q_INVOKABLE JsonReply *Authenticate(const QVariantMap &params);
Q_INVOKABLE JsonReply *Authenticate(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *RequestPushButtonAuth(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *Tokens(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *RemoveToken(const QVariantMap &params);
Q_INVOKABLE JsonReply *SetupCloudConnection(const QVariantMap &params);
Q_INVOKABLE JsonReply *SetupRemoteAccess(const QVariantMap &params);
Q_INVOKABLE JsonReply *IsCloudConnected(const QVariantMap &params);
@ -90,7 +88,6 @@ private:
void sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap &params = 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;
};

View File

@ -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();

View File

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

View File

@ -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 &params)
JsonReply *RulesHandler::FindRules(const QVariantMap &params)
{
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;

View File

@ -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 &params, 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);
}
}

View File

@ -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 &params, const JsonContext &context) const;
};
}
#endif // EVENTHANDLER_H

View File

@ -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 &params) 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;
}

View File

@ -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 &params)
{
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 &params, 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 &params)
{
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 &params, 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 &params, const JsonContext &context)
{
Q_UNUSED(params)
@ -200,7 +204,7 @@ JsonReply *UsersHandler::GetUserInfo(const QVariantMap &params, 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 &params, 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 &params, 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 &params, 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 &params, const JsonContex
return createReply(ret);
}
void UsersHandler::onPushButtonAuthFinished(int transactionId, bool success, const QByteArray &token)
JsonReply *UsersHandler::GetUsers(const QVariantMap &params)
{
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 &params, 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 &params, 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 &params, 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);
}
}

View File

@ -49,17 +49,18 @@ public:
Q_INVOKABLE JsonReply *CreateUser(const QVariantMap &params);
Q_INVOKABLE JsonReply *ChangePassword(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *Authenticate(const QVariantMap &params);
Q_INVOKABLE JsonReply *RequestPushButtonAuth(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *GetUserInfo(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *GetTokens(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *RemoveToken(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *GetUsers(const QVariantMap &params);
Q_INVOKABLE JsonReply *RemoveUser(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *SetUserScopes(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *SetUserInfo(const QVariantMap &params, const JsonContext &context);
signals:
void PushButtonAuthFinished(const QUuid &clientId, const QVariantMap &params);
private slots:
void onPushButtonAuthFinished(int transactionId, bool success, const QByteArray &token);
void UserAdded(const QVariantMap &params);
void UserRemoved(const QVariantMap &params);
void UserChanged(const QVariantMap &params);
private:
UserManager *m_userManager = nullptr;

View File

@ -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 \

View File

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

View File

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

View File

@ -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:

View File

@ -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.

View File

@ -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;

View File

@ -116,6 +116,7 @@ void TcpServer::terminateClientConnection(const QUuid &clientId)
{
QTcpSocket *client = m_clientList.value(clientId);
if (client) {
client->flush();
client->close();
}
}

View File

@ -121,6 +121,7 @@ void WebSocketServer::terminateClientConnection(const QUuid &clientId)
{
QWebSocket *client = m_clientList.value(clientId);
if (client) {
client->flush();
client->close();
}
}

View File

@ -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:

View File

@ -0,0 +1,6 @@
#include "userautorizer.h"
UserAutorizer::UserAutorizer(QObject *parent) : QObject(parent)
{
}

View 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

View File

@ -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>());
}
}

View File

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

View File

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

View File

@ -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();

View File

@ -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:

View File

@ -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 &params, const QVariantMap &returns, const QString &deprecationInfo)
void JsonHandler::registerMethod(const QString &name, const QString &description, const QVariantMap &params, 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));
}
}

View File

@ -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 &params, const QVariantMap &returns, const QString &deprecationInfo = QString());
void registerMethod(const QString &name, const QString &description, const QVariantMap &params, const QVariantMap &returns, Types::PermissionScope permissionScope = Types::PermissionScopeAdmin, const QString &deprecationInfo = QString());
void registerNotification(const QString &name, const QString &description, const QVariantMap &params, const QString &deprecationInfo = QString());
JsonReply *createReply(const QVariantMap &data) const;

View File

@ -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 \

View File

@ -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:

View File

@ -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();

View File

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

View File

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

View 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;
}

View File

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

View File

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

View File

@ -1,6 +0,0 @@
TARGET = testactions
include(../../../nymea.pri)
include(../autotests.pri)
SOURCES += testactions.cpp

View File

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

View File

@ -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 \

View File

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

View File

@ -1,5 +0,0 @@
include(../../../nymea.pri)
include(../autotests.pri)
TARGET = testevents
SOURCES += testevents.cpp

View File

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

View File

@ -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());
}

View File

@ -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();

View File

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

View File

@ -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.");
}
}

View File

@ -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");

View File

@ -1,5 +0,0 @@
include(../../../nymea.pri)
include(../autotests.pri)
TARGET = states
SOURCES += teststates.cpp

View File

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

View File

@ -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;

View File

@ -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");
}

View File

@ -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"

View File

@ -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";