mirror of https://github.com/nymea/nymea.git
Add more user management features
parent
7ead4ba91e
commit
ec0aa802c5
|
|
@ -70,6 +70,7 @@
|
|||
#include "networkmanagerhandler.h"
|
||||
#include "tagshandler.h"
|
||||
#include "systemhandler.h"
|
||||
#include "usershandler.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QStringList>
|
||||
|
|
@ -194,13 +195,13 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration
|
|||
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);
|
||||
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);
|
||||
registerMethod("RemoveToken", description, params, returns, "Use Users.RemoveToken instead.");
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Sets up the cloud connection by deploying a certificate and its configuration.";
|
||||
|
|
@ -370,8 +371,8 @@ JsonReply *JsonRPCServerImplementation::Tokens(const QVariantMap ¶ms) const
|
|||
Q_UNUSED(params)
|
||||
QByteArray token = property("token").toByteArray();
|
||||
|
||||
QString username = NymeaCore::instance()->userManager()->userForToken(token);
|
||||
QList<TokenInfo> tokens = NymeaCore::instance()->userManager()->tokens(username);
|
||||
TokenInfo tokenInfo = NymeaCore::instance()->userManager()->tokenInfo(token);
|
||||
QList<TokenInfo> tokens = NymeaCore::instance()->userManager()->tokens(tokenInfo.username());
|
||||
QVariantList retList;
|
||||
foreach (const TokenInfo &tokenInfo, tokens) {
|
||||
retList << pack(tokenInfo);
|
||||
|
|
@ -569,6 +570,7 @@ void JsonRPCServerImplementation::setup()
|
|||
registerHandler(new NetworkManagerHandler(this));
|
||||
registerHandler(new TagsHandler(this));
|
||||
registerHandler(new SystemHandler(NymeaCore::instance()->platform(), this));
|
||||
registerHandler(new UsersHandler(NymeaCore::instance()->userManager(), this));
|
||||
|
||||
connect(NymeaCore::instance()->cloudManager(), &CloudManager::pairingReply, this, &JsonRPCServerImplementation::pairingFinished);
|
||||
connect(NymeaCore::instance()->cloudManager(), &CloudManager::connectionStateChanged, this, &JsonRPCServerImplementation::onCloudConnectionStateChanged);
|
||||
|
|
@ -634,19 +636,19 @@ 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 = {"Introspect", "Hello", "CreateUser", "RequestPushButtonAuth"};
|
||||
QStringList authExemptMethodsWithUser = {"Introspect", "Hello", "Authenticate", "RequestPushButtonAuth"};
|
||||
QStringList authExemptMethodsNoUser = {"JSONRPC.Introspect", "JSONRPC.Hello", "JSONRPC.RequestPushButtonAuth", "JSONRPC.CreateUser", "Users.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 special method for authentication itself
|
||||
if (NymeaCore::instance()->userManager()->initRequired()) {
|
||||
if (!(targetNamespace == "JSONRPC" && authExemptMethodsNoUser.contains(method)) && (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token))) {
|
||||
sendUnauthorizedResponse(interface, clientId, commandId, "Initial setup required. Call CreateUser first.");
|
||||
if (!authExemptMethodsNoUser.contains(targetNamespace + "." + method) && (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);
|
||||
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 (!(targetNamespace == "JSONRPC" && authExemptMethodsWithUser.contains(method)) && (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token))) {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
#include "usershandler.h"
|
||||
#include "usermanager/usermanager.h"
|
||||
#include "usermanager/userinfo.h"
|
||||
|
||||
#include "loggingcategories.h"
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
UsersHandler::UsersHandler(UserManager *userManager, QObject *parent):
|
||||
JsonHandler(parent),
|
||||
m_userManager(userManager)
|
||||
{
|
||||
registerObject<UserInfo>();
|
||||
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.";
|
||||
params.insert("username", enumValueName(String));
|
||||
params.insert("password", enumValueName(String));
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("CreateUser", description, params, returns);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Change the password for the currently logged in user.";
|
||||
params.insert("newPassword", enumValueName(String));
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("ChangePassword", description, params, returns);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
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);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get all the tokens for the current user.";
|
||||
returns.insert("o:tokenInfoList", objectRef<TokenInfoList>());
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("GetTokens", description, params, returns);
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
QString UsersHandler::name() const
|
||||
{
|
||||
return "Users";
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::CreateUser(const QVariantMap ¶ms)
|
||||
{
|
||||
QString username = params.value("username").toString();
|
||||
QString password = params.value("password").toString();
|
||||
|
||||
UserManager::UserError status = m_userManager->createUser(username, password);
|
||||
|
||||
QVariantMap returns;
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(status));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::ChangePassword(const QVariantMap ¶ms)
|
||||
{
|
||||
QVariantMap ret;
|
||||
|
||||
QByteArray currentToken = property("token").toByteArray();
|
||||
if (currentToken.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot change password from an unauthenticated connection";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
}
|
||||
if (!m_userManager->verifyToken(currentToken)) {
|
||||
// Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token
|
||||
qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
QString newPassword = params.value("newPassword").toString();
|
||||
QString username = m_userManager->userInfo(currentToken).username();
|
||||
|
||||
UserManager::UserError status = m_userManager->changePassword(username, newPassword);
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(status));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::Authenticate(const QVariantMap ¶ms)
|
||||
{
|
||||
QString username = params.value("username").toString();
|
||||
QString password = params.value("password").toString();
|
||||
QString deviceName = params.value("deviceName").toString();
|
||||
|
||||
QByteArray token = m_userManager->authenticate(username, password, deviceName);
|
||||
QVariantMap ret;
|
||||
ret.insert("success", !token.isEmpty());
|
||||
if (!token.isEmpty()) {
|
||||
ret.insert("token", token);
|
||||
}
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::RequestPushButtonAuth(const QVariantMap ¶ms)
|
||||
{
|
||||
QString deviceName = params.value("deviceName").toString();
|
||||
QUuid clientId = this->property("clientId").toUuid();
|
||||
|
||||
int transactionId = m_userManager->requestPushButtonAuth(deviceName);
|
||||
m_pushButtonTransactions.insert(transactionId, clientId);
|
||||
|
||||
QVariantMap data;
|
||||
data.insert("transactionId", transactionId);
|
||||
// TODO: return false if pushbutton auth is disabled in settings
|
||||
data.insert("success", true);
|
||||
return createReply(data);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::GetUserInfo(const QVariantMap ¶ms)
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
QVariantMap ret;
|
||||
|
||||
QByteArray currentToken = property("token").toByteArray();
|
||||
if (currentToken.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot get user info form an unauthenticated connection";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
}
|
||||
if (!m_userManager->verifyToken(currentToken)) {
|
||||
// Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token
|
||||
qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
UserInfo userInfo = m_userManager->userInfo(currentToken);
|
||||
ret.insert("userInfo", pack(userInfo));
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorNoError));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::GetTokens(const QVariantMap ¶ms)
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
QVariantMap ret;
|
||||
|
||||
QByteArray currentToken = property("token").toByteArray();
|
||||
if (currentToken.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot fetch tokens form an unauthenticated connection";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
}
|
||||
if (!m_userManager->verifyToken(currentToken)) {
|
||||
// Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token
|
||||
qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
TokenInfo tokenInfo = m_userManager->tokenInfo(currentToken);
|
||||
qCDebug(dcJsonRpc()) << "Fetching tokens for user" << tokenInfo.username();
|
||||
QList<TokenInfo> tokens = m_userManager->tokens(tokenInfo.username());
|
||||
QVariantList retList;
|
||||
foreach (const TokenInfo &tokenInfo, tokens) {
|
||||
retList << pack(tokenInfo);
|
||||
}
|
||||
ret.insert("tokenInfoList", retList);
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorNoError));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::RemoveToken(const QVariantMap ¶ms)
|
||||
{
|
||||
QVariantMap ret;
|
||||
|
||||
QByteArray currentToken = property("token").toByteArray();
|
||||
if (currentToken.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot remove a token from an unauthenticated connection.";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
}
|
||||
if (!m_userManager->verifyToken(currentToken)) {
|
||||
// Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token
|
||||
qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
}
|
||||
QUuid tokenId = params.value("tokenId").toUuid();
|
||||
TokenInfo tokenToRemove = m_userManager->tokenInfo(tokenId);
|
||||
if (tokenToRemove.id().isNull()) {
|
||||
qCWarning(dcJsonRpc()) << "Token with ID" << tokenId << "not found";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorTokenNotFound));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
TokenInfo currentTokenInfo = m_userManager->tokenInfo(currentToken);
|
||||
if (currentTokenInfo.username() != tokenToRemove.username()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot remove a token from another user!";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
qCDebug(dcJsonRpc()) << "Removing token" << tokenId << "for user" << currentTokenInfo.username();
|
||||
|
||||
UserManager::UserError error = m_userManager->removeToken(tokenId);
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(error));
|
||||
return createReply(ret);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#ifndef USERSHANDLER_H
|
||||
#define USERSHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "jsonrpc/jsonhandler.h"
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
class UserManager;
|
||||
|
||||
class UsersHandler : public JsonHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UsersHandler(UserManager *userManager, QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
|
||||
Q_INVOKABLE JsonReply *CreateUser(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *ChangePassword(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *Authenticate(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *RequestPushButtonAuth(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *GetUserInfo(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *GetTokens(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *RemoveToken(const QVariantMap ¶ms);
|
||||
|
||||
signals:
|
||||
void PushButtonAuthFinished(const QVariantMap ¶ms);
|
||||
|
||||
private:
|
||||
UserManager *m_userManager = nullptr;
|
||||
QHash<int, QUuid> m_pushButtonTransactions;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // USERSHANDLER_H
|
||||
|
|
@ -19,8 +19,6 @@ HEADERS += nymeacore.h \
|
|||
devices/devicemanagerimplementation.h \
|
||||
devices/translator.h \
|
||||
experiences/experiencemanager.h \
|
||||
jsonrpc/jsonrpcserverimplementation.h \
|
||||
jsonrpc/scriptshandler.h \
|
||||
ruleengine/ruleengine.h \
|
||||
ruleengine/rule.h \
|
||||
ruleengine/stateevaluator.h \
|
||||
|
|
@ -43,6 +41,7 @@ HEADERS += nymeacore.h \
|
|||
servers/bluetoothserver.h \
|
||||
servers/websocketserver.h \
|
||||
servers/mqttbroker.h \
|
||||
jsonrpc/jsonrpcserverimplementation.h \
|
||||
jsonrpc/jsonvalidator.h \
|
||||
jsonrpc/devicehandler.h \
|
||||
jsonrpc/ruleshandler.h \
|
||||
|
|
@ -52,6 +51,10 @@ HEADERS += nymeacore.h \
|
|||
jsonrpc/logginghandler.h \
|
||||
jsonrpc/configurationhandler.h \
|
||||
jsonrpc/networkmanagerhandler.h \
|
||||
jsonrpc/tagshandler.h \
|
||||
jsonrpc/systemhandler.h \
|
||||
jsonrpc/scriptshandler.h \
|
||||
jsonrpc/usershandler.h \
|
||||
logging/logging.h \
|
||||
logging/logengine.h \
|
||||
logging/logfilter.h \
|
||||
|
|
@ -66,6 +69,7 @@ HEADERS += nymeacore.h \
|
|||
networkmanager/networksettings.h \
|
||||
networkmanager/networkconnection.h \
|
||||
networkmanager/wirednetworkdevice.h \
|
||||
usermanager/userinfo.h \
|
||||
usermanager/usermanager.h \
|
||||
usermanager/tokeninfo.h \
|
||||
usermanager/pushbuttondbusservice.h \
|
||||
|
|
@ -90,18 +94,15 @@ HEADERS += nymeacore.h \
|
|||
debugserverhandler.h \
|
||||
tagging/tagsstorage.h \
|
||||
tagging/tag.h \
|
||||
jsonrpc/tagshandler.h \
|
||||
cloud/cloudtransport.h \
|
||||
debugreportgenerator.h \
|
||||
platform/platform.h \
|
||||
jsonrpc/systemhandler.h
|
||||
|
||||
|
||||
SOURCES += nymeacore.cpp \
|
||||
devices/devicemanagerimplementation.cpp \
|
||||
devices/translator.cpp \
|
||||
experiences/experiencemanager.cpp \
|
||||
jsonrpc/jsonrpcserverimplementation.cpp \
|
||||
jsonrpc/scriptshandler.cpp \
|
||||
ruleengine/ruleengine.cpp \
|
||||
ruleengine/rule.cpp \
|
||||
ruleengine/stateevaluator.cpp \
|
||||
|
|
@ -124,6 +125,7 @@ SOURCES += nymeacore.cpp \
|
|||
servers/websocketserver.cpp \
|
||||
servers/bluetoothserver.cpp \
|
||||
servers/mqttbroker.cpp \
|
||||
jsonrpc/jsonrpcserverimplementation.cpp \
|
||||
jsonrpc/jsonvalidator.cpp \
|
||||
jsonrpc/devicehandler.cpp \
|
||||
jsonrpc/ruleshandler.cpp \
|
||||
|
|
@ -133,6 +135,10 @@ SOURCES += nymeacore.cpp \
|
|||
jsonrpc/logginghandler.cpp \
|
||||
jsonrpc/configurationhandler.cpp \
|
||||
jsonrpc/networkmanagerhandler.cpp \
|
||||
jsonrpc/tagshandler.cpp \
|
||||
jsonrpc/systemhandler.cpp \
|
||||
jsonrpc/scriptshandler.cpp \
|
||||
jsonrpc/usershandler.cpp \
|
||||
logging/logengine.cpp \
|
||||
logging/logfilter.cpp \
|
||||
logging/logentry.cpp \
|
||||
|
|
@ -145,6 +151,7 @@ SOURCES += nymeacore.cpp \
|
|||
networkmanager/networksettings.cpp \
|
||||
networkmanager/networkconnection.cpp \
|
||||
networkmanager/wirednetworkdevice.cpp \
|
||||
usermanager/userinfo.cpp \
|
||||
usermanager/usermanager.cpp \
|
||||
usermanager/tokeninfo.cpp \
|
||||
usermanager/pushbuttondbusservice.cpp \
|
||||
|
|
@ -169,11 +176,10 @@ SOURCES += nymeacore.cpp \
|
|||
debugserverhandler.cpp \
|
||||
tagging/tagsstorage.cpp \
|
||||
tagging/tag.cpp \
|
||||
jsonrpc/tagshandler.cpp \
|
||||
cloud/cloudtransport.cpp \
|
||||
debugreportgenerator.cpp \
|
||||
platform/platform.cpp \
|
||||
jsonrpc/systemhandler.cpp
|
||||
|
||||
|
||||
versionAtLeast(QT_VERSION, 5.12.0) {
|
||||
HEADERS += \
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@
|
|||
|
||||
#include "tokeninfo.h"
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
TokenInfo::TokenInfo()
|
||||
|
|
@ -83,4 +85,14 @@ QString TokenInfo::deviceName() const
|
|||
return m_deviceName;
|
||||
}
|
||||
|
||||
QVariant TokenInfoList::get(int index) const
|
||||
{
|
||||
return QVariant::fromValue(at(index));
|
||||
}
|
||||
|
||||
void TokenInfoList::put(const QVariant &variant)
|
||||
{
|
||||
append(variant.value<TokenInfo>());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include <QUuid>
|
||||
#include <QDateTime>
|
||||
#include <QMetaType>
|
||||
#include <QVariant>
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ class TokenInfo
|
|||
Q_PROPERTY(QUuid id READ id)
|
||||
Q_PROPERTY(QString username READ username)
|
||||
Q_PROPERTY(QDateTime creationTime READ creationTime)
|
||||
Q_PROPERTY(QString deviveName READ deviceName)
|
||||
Q_PROPERTY(QString deviceName READ deviceName)
|
||||
|
||||
public:
|
||||
TokenInfo();
|
||||
|
|
@ -61,7 +62,17 @@ private:
|
|||
QString m_deviceName;
|
||||
};
|
||||
|
||||
}
|
||||
Q_DECLARE_METATYPE(nymeaserver::TokenInfo)
|
||||
|
||||
class TokenInfoList: public QList<TokenInfo>
|
||||
{
|
||||
Q_GADGET
|
||||
Q_PROPERTY(int count READ count)
|
||||
public:
|
||||
Q_INVOKABLE QVariant get(int index) const;
|
||||
Q_INVOKABLE void put(const QVariant &variant);
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(nymeaserver::TokenInfo)
|
||||
Q_DECLARE_METATYPE(nymeaserver::TokenInfoList)
|
||||
#endif // TOKENINFO_H
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
#include "userinfo.h"
|
||||
|
||||
UserInfo::UserInfo()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
UserInfo::UserInfo(const QString &username):
|
||||
m_username(username)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString UserInfo::username() const
|
||||
{
|
||||
return m_username;
|
||||
}
|
||||
|
||||
void UserInfo::setUsername(const QString &username)
|
||||
{
|
||||
m_username = username;
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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 USERINFO_H
|
||||
#define USERINFO_H
|
||||
|
||||
#include <QUuid>
|
||||
#include <QObject>
|
||||
|
||||
class UserInfo
|
||||
{
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QString username READ username)
|
||||
|
||||
public:
|
||||
UserInfo();
|
||||
UserInfo(const QString &username);
|
||||
|
||||
QString username() const;
|
||||
void setUsername(const QString &username);
|
||||
|
||||
private:
|
||||
QString m_username;
|
||||
};
|
||||
|
||||
#endif // USERINFO_H
|
||||
|
|
@ -167,7 +167,7 @@ UserManager::UserError UserManager::createUser(const QString &username, const QS
|
|||
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)
|
||||
.arg(username.toLower())
|
||||
.arg(QString::fromUtf8(hashedPassword))
|
||||
.arg(QString::fromUtf8(salt));
|
||||
m_db.exec(queryString);
|
||||
|
|
@ -178,9 +178,41 @@ UserManager::UserError UserManager::createUser(const QString &username, const QS
|
|||
return UserErrorNoError;
|
||||
}
|
||||
|
||||
/*! Remove the user with the given \a username and all of its tokens. If the \a username is empty, all anonymous
|
||||
tokens (e.g. issued by pushbutton auth) will be cleared.
|
||||
*/
|
||||
UserManager::UserError UserManager::changePassword(const QString &username, const QString &newPassword)
|
||||
{
|
||||
if (!validateUsername(username)) {
|
||||
qCWarning(dcUserManager) << "Invalid username:" << username;
|
||||
return UserErrorInvalidUserId;
|
||||
}
|
||||
|
||||
if (!validatePassword(newPassword)) {
|
||||
qCWarning(dcUserManager) << "Password failed character validation. Must contain a letter, a number and a special charactar. Minimum length: 8";
|
||||
return UserErrorBadPassword;
|
||||
}
|
||||
|
||||
QString checkForUserExistingQuery = QString("SELECT * FROM users WHERE lower(username) = \"%1\";").arg(username.toLower());
|
||||
QSqlQuery result = m_db.exec(checkForUserExistingQuery);
|
||||
if (!result.first()) {
|
||||
qCWarning(dcUserManager) << "Username does not exist.";
|
||||
return UserErrorInvalidUserId;
|
||||
}
|
||||
|
||||
// Update the password
|
||||
QByteArray salt = QUuid::createUuid().toString().remove(QRegExp("[{}]")).toUtf8();
|
||||
QByteArray hashedPassword = QCryptographicHash::hash(QString(newPassword + salt).toUtf8(), QCryptographicHash::Sha512).toBase64();
|
||||
QString queryString = QString("UPDATE users SET password = \"%1\", salt = \"%2\" WHERE lower(username) = \"%3\";")
|
||||
.arg(QString::fromUtf8(hashedPassword))
|
||||
.arg(QString::fromUtf8(salt))
|
||||
.arg(username.toLower());
|
||||
m_db.exec(queryString);
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Error updating password for user:" << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
return UserErrorBackendError;
|
||||
}
|
||||
qCDebug(dcUserManager()) << "Password updated for user" << username;
|
||||
return UserErrorNoError;
|
||||
}
|
||||
|
||||
UserManager::UserError UserManager::removeUser(const QString &username)
|
||||
{
|
||||
if (!username.isEmpty()) {
|
||||
|
|
@ -189,14 +221,11 @@ UserManager::UserError UserManager::removeUser(const QString &username)
|
|||
if (result.numRowsAffected() == 0) {
|
||||
return UserErrorInvalidUserId;
|
||||
}
|
||||
|
||||
QString dropTokensQuery = QString("DELETE FROM tokens WHERE lower(username) = \"%1\";").arg(username.toLower());
|
||||
m_db.exec(dropTokensQuery);
|
||||
} else {
|
||||
QString dropTokensQuery = QString("DELETE FROM tokens WHERE username = \"\";").arg(username.toLower());
|
||||
m_db.exec(dropTokensQuery);
|
||||
}
|
||||
|
||||
QString dropTokensQuery = QString("DELETE FROM tokens WHERE lower(username) = \"%1\";").arg(username.toLower());
|
||||
m_db.exec(dropTokensQuery);
|
||||
|
||||
return UserErrorNoError;
|
||||
}
|
||||
|
||||
|
|
@ -233,7 +262,7 @@ QByteArray UserManager::authenticate(const QString &username, const QString &pas
|
|||
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())
|
||||
.arg(username)
|
||||
.arg(username.toLower())
|
||||
.arg(QString::fromUtf8(token))
|
||||
.arg(NymeaCore::instance()->timeManager()->currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
|
||||
.arg(deviceName);
|
||||
|
|
@ -280,32 +309,32 @@ void UserManager::cancelPushButtonAuth(int transactionId)
|
|||
|
||||
}
|
||||
|
||||
/*! Returns the username for the given \a token. If the token is invalid, an empty string will be returned. */
|
||||
QString UserManager::userForToken(const QByteArray &token) const
|
||||
UserInfo UserManager::userInfo(const QByteArray &token) const
|
||||
{
|
||||
if (!validateToken(token)) {
|
||||
qCWarning(dcUserManager) << "Token failed character validation:" << token;
|
||||
return QString();
|
||||
TokenInfo tokenInfo = this->tokenInfo(token);
|
||||
|
||||
if (tokenInfo.id().isNull()) {
|
||||
qCWarning(dcUserManager) << "Cannot fetch user info for invalid token:" << token;
|
||||
return UserInfo();
|
||||
}
|
||||
QString getUserQuery = QString("SELECT * FROM tokens WHERE token = \"%1\";")
|
||||
.arg(QString::fromUtf8(token));
|
||||
|
||||
// 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());
|
||||
QSqlQuery result = m_db.exec(getUserQuery);
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Error fetching username for token:" << m_db.lastError().databaseText() << m_db.lastError().driverText() << getUserQuery;
|
||||
return QString();
|
||||
}
|
||||
if (!result.first()) {
|
||||
qCWarning(dcUserManager) << "No such token in DB:" << token;
|
||||
return QString();
|
||||
qCWarning(dcUserManager) << "Query for token failed:" << m_db.lastError().databaseText() << m_db.lastError().driverText() << getUserQuery;
|
||||
return UserInfo();
|
||||
}
|
||||
|
||||
return result.value("username").toString();
|
||||
if (!result.first()) {
|
||||
return UserInfo();
|
||||
}
|
||||
return UserInfo(result.value("username").toString());
|
||||
|
||||
}
|
||||
|
||||
/*! Returns a list of tokens for the given \a username.
|
||||
|
||||
\sa TokenInfo
|
||||
*/
|
||||
QList<TokenInfo> UserManager::tokens(const QString &username) const
|
||||
{
|
||||
QList<TokenInfo> ret;
|
||||
|
|
@ -328,6 +357,44 @@ QList<TokenInfo> UserManager::tokens(const QString &username) const
|
|||
return ret;
|
||||
}
|
||||
|
||||
TokenInfo UserManager::tokenInfo(const QByteArray &token) const
|
||||
{
|
||||
if (!validateToken(token)) {
|
||||
qCWarning(dcUserManager) << "Token did not pass validation:" << token;
|
||||
return TokenInfo();
|
||||
}
|
||||
|
||||
QString getTokenQuery = QString("SELECT id, username, creationdate, deviceName FROM tokens WHERE token = \"%1\";")
|
||||
.arg(QString::fromUtf8(token));
|
||||
QSqlQuery result = m_db.exec(getTokenQuery);
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Query for token failed:" << m_db.lastError().databaseText() << m_db.lastError().driverText() << getTokenQuery;
|
||||
return TokenInfo();
|
||||
}
|
||||
|
||||
if (!result.first()) {
|
||||
return TokenInfo();
|
||||
}
|
||||
return TokenInfo(result.value("id").toUuid(), result.value("username").toString(), result.value("creationdate").toDateTime(), result.value("devicename").toString());
|
||||
}
|
||||
|
||||
TokenInfo UserManager::tokenInfo(const QUuid &tokenId) const
|
||||
{
|
||||
|
||||
QString getTokenQuery = QString("SELECT id, username, creationdate, deviceName FROM tokens WHERE id = \"%1\";")
|
||||
.arg(tokenId.toString());
|
||||
QSqlQuery result = m_db.exec(getTokenQuery);
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Query for token failed:" << m_db.lastError().databaseText() << m_db.lastError().driverText() << getTokenQuery;
|
||||
return TokenInfo();
|
||||
}
|
||||
|
||||
if (!result.first()) {
|
||||
return TokenInfo();
|
||||
}
|
||||
return TokenInfo(result.value("id").toUuid(), result.value("username").toString(), result.value("creationdate").toDateTime(), result.value("devicename").toString());
|
||||
}
|
||||
|
||||
/*! Removes the token with the given \a tokenId. Returns \l{UserError} to inform about the result. */
|
||||
UserManager::UserError UserManager::removeToken(const QUuid &tokenId)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#define USERMANAGER_H
|
||||
|
||||
#include "tokeninfo.h"
|
||||
#include "userinfo.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QSqlDatabase>
|
||||
|
|
@ -61,6 +62,7 @@ public:
|
|||
QStringList users() const;
|
||||
|
||||
UserError createUser(const QString &username, const QString &password);
|
||||
UserError changePassword(const QString &username, const QString &newPassword);
|
||||
UserError removeUser(const QString &username);
|
||||
|
||||
bool pushButtonAuthAvailable() const;
|
||||
|
|
@ -68,9 +70,14 @@ public:
|
|||
QByteArray authenticate(const QString &username, const QString &password, const QString &deviceName);
|
||||
int requestPushButtonAuth(const QString &deviceName);
|
||||
void cancelPushButtonAuth(int transactionId);
|
||||
QString userForToken(const QByteArray &token) const;
|
||||
|
||||
UserInfo userInfo(const QByteArray &token) const;
|
||||
TokenInfo tokenInfo(const QByteArray &token) const;
|
||||
TokenInfo tokenInfo(const QUuid &tokenId) const;
|
||||
QList<TokenInfo> tokens(const QString &username) const;
|
||||
nymeaserver::UserManager::UserError removeToken(const QUuid &tokenId);
|
||||
|
||||
UserError removeToken(const QUuid &tokenId);
|
||||
|
||||
|
||||
bool verifyToken(const QByteArray &token);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p"
|
|||
|
||||
# define protocol versions
|
||||
JSON_PROTOCOL_VERSION_MAJOR=4
|
||||
JSON_PROTOCOL_VERSION_MINOR=1
|
||||
JSON_PROTOCOL_VERSION_MINOR=2
|
||||
JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}"
|
||||
LIBNYMEA_API_VERSION_MAJOR=4
|
||||
LIBNYMEA_API_VERSION_MINOR=0
|
||||
|
|
@ -105,3 +105,4 @@ coverage {
|
|||
ccache {
|
||||
message("Using ccache.")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
4.1
|
||||
4.2
|
||||
{
|
||||
"enums": {
|
||||
"BasicType": [
|
||||
|
|
@ -920,6 +920,7 @@
|
|||
}
|
||||
},
|
||||
"JSONRPC.RemoveToken": {
|
||||
"deprecated": "Use Users.RemoveToken instead.",
|
||||
"description": "Revoke access for a given token.",
|
||||
"params": {
|
||||
"tokenId": "Uuid"
|
||||
|
|
@ -974,6 +975,7 @@
|
|||
}
|
||||
},
|
||||
"JSONRPC.Tokens": {
|
||||
"deprecated": "Use Users.GetTokens instead.",
|
||||
"description": "Return a list of TokenInfo objects of all the tokens for the current user.",
|
||||
"params": {
|
||||
},
|
||||
|
|
@ -1464,6 +1466,52 @@
|
|||
"returns": {
|
||||
"tagError": "$ref:TagError"
|
||||
}
|
||||
},
|
||||
"Users.ChangePassword": {
|
||||
"description": "Change the password for the currently logged in user.",
|
||||
"params": {
|
||||
"newPassword": "String"
|
||||
},
|
||||
"returns": {
|
||||
"error": "$ref:UserError"
|
||||
}
|
||||
},
|
||||
"Users.CreateUser": {
|
||||
"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.",
|
||||
"params": {
|
||||
"password": "String",
|
||||
"username": "String"
|
||||
},
|
||||
"returns": {
|
||||
"error": "$ref:UserError"
|
||||
}
|
||||
},
|
||||
"Users.GetTokens": {
|
||||
"description": "Get all the tokens for the current user.",
|
||||
"params": {
|
||||
},
|
||||
"returns": {
|
||||
"error": "$ref:UserError",
|
||||
"o:tokenInfoList": "$ref:TokenInfoList"
|
||||
}
|
||||
},
|
||||
"Users.GetUserInfo": {
|
||||
"description": "Get info about the current token (the currently logged in user).",
|
||||
"params": {
|
||||
},
|
||||
"returns": {
|
||||
"error": "$ref:UserError",
|
||||
"o:userInfo": "$ref:UserInfo"
|
||||
}
|
||||
},
|
||||
"Users.RemoveToken": {
|
||||
"description": "Revoke access for a given token.",
|
||||
"params": {
|
||||
"tokenId": "Uuid"
|
||||
},
|
||||
"returns": {
|
||||
"error": "$ref:UserError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
|
|
@ -2147,10 +2195,16 @@
|
|||
],
|
||||
"TokenInfo": {
|
||||
"r:creationTime": "Uint",
|
||||
"r:deviveName": "String",
|
||||
"r:deviceName": "String",
|
||||
"r:id": "Uuid",
|
||||
"r:username": "String"
|
||||
},
|
||||
"TokenInfoList": [
|
||||
"$ref:TokenInfo"
|
||||
],
|
||||
"UserInfo": {
|
||||
"r:username": "String"
|
||||
},
|
||||
"Vendor": {
|
||||
"displayName": "String",
|
||||
"id": "Uuid",
|
||||
|
|
|
|||
|
|
@ -675,7 +675,7 @@ void TestJSONRPC::enableDisableNotifications_legacy()
|
|||
|
||||
QStringList expectedNamespaces;
|
||||
if (enabled == "true") {
|
||||
expectedNamespaces << "Actions" << "NetworkManager" << "Devices" << "System" << "Rules" << "States" << "Logging" << "Tags" << "JSONRPC" << "Configuration" << "Events" << "Scripts";
|
||||
expectedNamespaces << "Actions" << "NetworkManager" << "Devices" << "System" << "Rules" << "States" << "Logging" << "Tags" << "JSONRPC" << "Configuration" << "Events" << "Scripts" << "Users";
|
||||
}
|
||||
std::sort(expectedNamespaces.begin(), expectedNamespaces.end());
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include "nymeacore.h"
|
||||
#include "nymeatestbase.h"
|
||||
#include "usermanager/usermanager.h"
|
||||
#include "servers/mocktcpserver.h"
|
||||
|
||||
using namespace nymeaserver;
|
||||
|
||||
|
|
@ -44,11 +45,33 @@ public:
|
|||
TestUsermanager(QObject* parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void createUser_data();
|
||||
void init();
|
||||
|
||||
void loginValidation_data();
|
||||
void loginValidation();
|
||||
|
||||
void createUser();
|
||||
void authenticate();
|
||||
|
||||
void createDuplicateUser();
|
||||
|
||||
void getTokens();
|
||||
|
||||
void removeToken();
|
||||
|
||||
void unauthenticatedCallAfterTokenRemove();
|
||||
|
||||
void changePassword();
|
||||
|
||||
void authenticateAfterPasswordChangeOK();
|
||||
|
||||
void authenticateAfterPasswordChangeFail();
|
||||
|
||||
void getUserInfo();
|
||||
|
||||
private:
|
||||
LogEngine *engine;
|
||||
// m_apiToken is in testBase
|
||||
QUuid m_tokenId;
|
||||
};
|
||||
|
||||
TestUsermanager::TestUsermanager(QObject *parent): NymeaTestBase(parent)
|
||||
|
|
@ -56,7 +79,18 @@ TestUsermanager::TestUsermanager(QObject *parent): NymeaTestBase(parent)
|
|||
QCoreApplication::instance()->setOrganizationName("nymea-test");
|
||||
}
|
||||
|
||||
void TestUsermanager::createUser_data() {
|
||||
void TestUsermanager::init()
|
||||
{
|
||||
UserManager *userManager = NymeaCore::instance()->userManager();
|
||||
foreach (const QString &user, userManager->users()) {
|
||||
qCDebug(dcTests()) << "Removing user" << user;
|
||||
userManager->removeUser(user);
|
||||
}
|
||||
userManager->removeUser("");
|
||||
|
||||
}
|
||||
|
||||
void TestUsermanager::loginValidation_data() {
|
||||
QTest::addColumn<QString>("username");
|
||||
QTest::addColumn<QString>("password");
|
||||
QTest::addColumn<UserManager::UserError>("expectedError");
|
||||
|
|
@ -88,23 +122,163 @@ void TestUsermanager::createUser_data() {
|
|||
|
||||
}
|
||||
|
||||
void TestUsermanager::createUser()
|
||||
void TestUsermanager::loginValidation()
|
||||
{
|
||||
QFETCH(QString, username);
|
||||
QFETCH(QString, password);
|
||||
QFETCH(UserManager::UserError, expectedError);
|
||||
|
||||
UserManager *userManager = NymeaCore::instance()->userManager();
|
||||
foreach (const QString &user, userManager->users()) {
|
||||
userManager->removeUser(user);
|
||||
}
|
||||
userManager->removeUser("");
|
||||
|
||||
UserManager::UserError error = userManager->createUser(username, password);
|
||||
qDebug() << "Error:" << error << "Expected:" << expectedError;
|
||||
QCOMPARE(error, expectedError);
|
||||
|
||||
}
|
||||
|
||||
void TestUsermanager::createUser()
|
||||
{
|
||||
QVariantMap params;
|
||||
params.insert("username", "valid@user.test");
|
||||
params.insert("password", "Bla1234*");
|
||||
QVariant response = injectAndWait("Users.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");
|
||||
}
|
||||
|
||||
void TestUsermanager::authenticate()
|
||||
{
|
||||
m_apiToken.clear();
|
||||
createUser();
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("username", "valid@user.test");
|
||||
params.insert("password", "Bla1234*");
|
||||
params.insert("deviceName", "autotests");
|
||||
QVariant response = injectAndWait("JSONRPC.Authenticate", params);
|
||||
|
||||
m_apiToken = response.toMap().value("params").toMap().value("token").toByteArray();
|
||||
|
||||
QVERIFY2(response.toMap().value("status").toString() == "success", "Error authenticating");
|
||||
QVERIFY2(response.toMap().value("params").toMap().value("success").toString() == "true", "Error authenticating");
|
||||
}
|
||||
|
||||
void TestUsermanager::createDuplicateUser()
|
||||
{
|
||||
authenticate();
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("username", "valid@user.test");
|
||||
params.insert("password", "Bla1234*");
|
||||
QVariant response = injectAndWait("Users.CreateUser", params);
|
||||
|
||||
QVERIFY2(response.toMap().value("status").toString() == "success", "Unexpected error code creating duplicate user");
|
||||
QVERIFY2(response.toMap().value("params").toMap().value("error").toString() == "UserErrorDuplicateUserId", "Unexpected error creating duplicate user");
|
||||
}
|
||||
|
||||
void TestUsermanager::getTokens()
|
||||
{
|
||||
authenticate();
|
||||
|
||||
QVariant response = injectAndWait("Users.GetTokens");
|
||||
QVERIFY2(response.toMap().value("status").toString() == "success", "Unexpected error code creating duplicate user");
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("error").toString(), QString("UserErrorNoError"));
|
||||
|
||||
QVariantList tokenInfoList = response.toMap().value("params").toMap().value("tokenInfoList").toList();
|
||||
QCOMPARE(tokenInfoList.count(), 1);
|
||||
|
||||
m_tokenId = tokenInfoList.first().toMap().value("id").toUuid();
|
||||
QVERIFY2(!m_tokenId.isNull(), "Token ID should not be null");
|
||||
QCOMPARE(tokenInfoList.first().toMap().value("username").toString(), QString("valid@user.test"));
|
||||
QCOMPARE(tokenInfoList.first().toMap().value("deviceName").toString(), QString("autotests"));
|
||||
}
|
||||
|
||||
void TestUsermanager::removeToken()
|
||||
{
|
||||
getTokens();
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("tokenId", m_tokenId);
|
||||
QVariant response = injectAndWait("Users.RemoveToken", params);
|
||||
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("error").toString(), QString("UserErrorNoError"));
|
||||
}
|
||||
|
||||
void TestUsermanager::changePassword()
|
||||
{
|
||||
authenticate();
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("newPassword", "Blubb123");
|
||||
QVariant response = injectAndWait("Users.ChangePassword", params);
|
||||
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("error").toString(), QString("UserErrorNoError"));
|
||||
}
|
||||
|
||||
void TestUsermanager::authenticateAfterPasswordChangeOK()
|
||||
{
|
||||
changePassword();
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("username", "valid@user.test");
|
||||
params.insert("password", "Blubb123"); // New password, should be ok
|
||||
params.insert("deviceName", "autotests");
|
||||
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");
|
||||
QVERIFY2(response.toMap().value("status").toString() == "success", "Error authenticating");
|
||||
QVERIFY2(response.toMap().value("params").toMap().value("success").toString() == "true", "Error authenticating");
|
||||
}
|
||||
|
||||
void TestUsermanager::authenticateAfterPasswordChangeFail()
|
||||
{
|
||||
changePassword();
|
||||
|
||||
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("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"));
|
||||
}
|
||||
|
||||
void TestUsermanager::getUserInfo()
|
||||
{
|
||||
authenticate();
|
||||
|
||||
QVariant response = injectAndWait("Users.GetUserInfo");
|
||||
|
||||
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
|
||||
|
||||
QVariantMap userInfoMap = response.toMap().value("params").toMap().value("userInfo").toMap();
|
||||
|
||||
|
||||
QCOMPARE(userInfoMap.value("username").toString(), QString("valid@user.test"));
|
||||
|
||||
}
|
||||
|
||||
void TestUsermanager::unauthenticatedCallAfterTokenRemove()
|
||||
{
|
||||
removeToken();
|
||||
|
||||
QSignalSpy spy(m_mockTcpServer, &MockTcpServer::connectionTerminated);
|
||||
|
||||
QVariant response = injectAndWait("Users.GetTokens");
|
||||
QCOMPARE(response.toMap().value("status").toString(), QString("unauthorized"));
|
||||
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
}
|
||||
QVERIFY2(spy.count() == 1, "Connection should be terminated!");
|
||||
|
||||
// need to restart as our connection dies
|
||||
restartServer();
|
||||
}
|
||||
|
||||
#include "testusermanager.moc"
|
||||
QTEST_MAIN(TestUsermanager)
|
||||
|
|
|
|||
Loading…
Reference in New Issue