Add more user management features
This commit is contained in:
parent
7ead4ba91e
commit
ec0aa802c5
@ -70,6 +70,7 @@
|
|||||||
#include "networkmanagerhandler.h"
|
#include "networkmanagerhandler.h"
|
||||||
#include "tagshandler.h"
|
#include "tagshandler.h"
|
||||||
#include "systemhandler.h"
|
#include "systemhandler.h"
|
||||||
|
#include "usershandler.h"
|
||||||
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
@ -194,13 +195,13 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration
|
|||||||
params.clear(); returns.clear();
|
params.clear(); returns.clear();
|
||||||
description = "Return a list of TokenInfo objects of all the tokens for the current user.";
|
description = "Return a list of TokenInfo objects of all the tokens for the current user.";
|
||||||
returns.insert("tokenInfoList", QVariantList() << objectRef("TokenInfo"));
|
returns.insert("tokenInfoList", QVariantList() << objectRef("TokenInfo"));
|
||||||
registerMethod("Tokens", description, params, returns);
|
registerMethod("Tokens", description, params, returns, "Use Users.GetTokens instead.");
|
||||||
|
|
||||||
params.clear(); returns.clear();
|
params.clear(); returns.clear();
|
||||||
description = "Revoke access for a given token.";
|
description = "Revoke access for a given token.";
|
||||||
params.insert("tokenId", enumValueName(Uuid));
|
params.insert("tokenId", enumValueName(Uuid));
|
||||||
returns.insert("error", enumRef<UserManager::UserError>());
|
returns.insert("error", enumRef<UserManager::UserError>());
|
||||||
registerMethod("RemoveToken", description, params, returns);
|
registerMethod("RemoveToken", description, params, returns, "Use Users.RemoveToken instead.");
|
||||||
|
|
||||||
params.clear(); returns.clear();
|
params.clear(); returns.clear();
|
||||||
description = "Sets up the cloud connection by deploying a certificate and its configuration.";
|
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)
|
Q_UNUSED(params)
|
||||||
QByteArray token = property("token").toByteArray();
|
QByteArray token = property("token").toByteArray();
|
||||||
|
|
||||||
QString username = NymeaCore::instance()->userManager()->userForToken(token);
|
TokenInfo tokenInfo = NymeaCore::instance()->userManager()->tokenInfo(token);
|
||||||
QList<TokenInfo> tokens = NymeaCore::instance()->userManager()->tokens(username);
|
QList<TokenInfo> tokens = NymeaCore::instance()->userManager()->tokens(tokenInfo.username());
|
||||||
QVariantList retList;
|
QVariantList retList;
|
||||||
foreach (const TokenInfo &tokenInfo, tokens) {
|
foreach (const TokenInfo &tokenInfo, tokens) {
|
||||||
retList << pack(tokenInfo);
|
retList << pack(tokenInfo);
|
||||||
@ -569,6 +570,7 @@ void JsonRPCServerImplementation::setup()
|
|||||||
registerHandler(new NetworkManagerHandler(this));
|
registerHandler(new NetworkManagerHandler(this));
|
||||||
registerHandler(new TagsHandler(this));
|
registerHandler(new TagsHandler(this));
|
||||||
registerHandler(new SystemHandler(NymeaCore::instance()->platform(), 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::pairingReply, this, &JsonRPCServerImplementation::pairingFinished);
|
||||||
connect(NymeaCore::instance()->cloudManager(), &CloudManager::connectionStateChanged, this, &JsonRPCServerImplementation::onCloudConnectionStateChanged);
|
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
|
// check if authentication is required for this transport
|
||||||
if (m_interfaces.value(interface)) {
|
if (m_interfaces.value(interface)) {
|
||||||
QByteArray token = message.value("token").toByteArray();
|
QByteArray token = message.value("token").toByteArray();
|
||||||
QStringList authExemptMethodsNoUser = {"Introspect", "Hello", "CreateUser", "RequestPushButtonAuth"};
|
QStringList authExemptMethodsNoUser = {"JSONRPC.Introspect", "JSONRPC.Hello", "JSONRPC.RequestPushButtonAuth", "JSONRPC.CreateUser", "Users.CreateUser"};
|
||||||
QStringList authExemptMethodsWithUser = {"Introspect", "Hello", "Authenticate", "RequestPushButtonAuth"};
|
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 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 (NymeaCore::instance()->userManager()->initRequired()) {
|
||||||
if (!(targetNamespace == "JSONRPC" && authExemptMethodsNoUser.contains(method)) && (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token))) {
|
if (!authExemptMethodsNoUser.contains(targetNamespace + "." + method) && (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token))) {
|
||||||
sendUnauthorizedResponse(interface, clientId, commandId, "Initial setup required. Call CreateUser first.");
|
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.";
|
qCWarning(dcJsonRpc()) << "Initial setup required but client does not call the setup. Dropping connection.";
|
||||||
interface->terminateClientConnection(clientId);
|
interface->terminateClientConnection(clientId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ok, we have a user. if there isn't a valid token, let's fail unless this is a Authenticate, Introspect Hello call
|
// 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.");
|
sendUnauthorizedResponse(interface, clientId, commandId, "Forbidden: Invalid token.");
|
||||||
qCWarning(dcJsonRpc()) << "Client did not not present a valid token. Dropping connection.";
|
qCWarning(dcJsonRpc()) << "Client did not not present a valid token. Dropping connection.";
|
||||||
interface->terminateClientConnection(clientId);
|
interface->terminateClientConnection(clientId);
|
||||||
|
|||||||
216
libnymea-core/jsonrpc/usershandler.cpp
Normal file
216
libnymea-core/jsonrpc/usershandler.cpp
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
39
libnymea-core/jsonrpc/usershandler.h
Normal file
39
libnymea-core/jsonrpc/usershandler.h
Normal file
@ -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/devicemanagerimplementation.h \
|
||||||
devices/translator.h \
|
devices/translator.h \
|
||||||
experiences/experiencemanager.h \
|
experiences/experiencemanager.h \
|
||||||
jsonrpc/jsonrpcserverimplementation.h \
|
|
||||||
jsonrpc/scriptshandler.h \
|
|
||||||
ruleengine/ruleengine.h \
|
ruleengine/ruleengine.h \
|
||||||
ruleengine/rule.h \
|
ruleengine/rule.h \
|
||||||
ruleengine/stateevaluator.h \
|
ruleengine/stateevaluator.h \
|
||||||
@ -43,6 +41,7 @@ HEADERS += nymeacore.h \
|
|||||||
servers/bluetoothserver.h \
|
servers/bluetoothserver.h \
|
||||||
servers/websocketserver.h \
|
servers/websocketserver.h \
|
||||||
servers/mqttbroker.h \
|
servers/mqttbroker.h \
|
||||||
|
jsonrpc/jsonrpcserverimplementation.h \
|
||||||
jsonrpc/jsonvalidator.h \
|
jsonrpc/jsonvalidator.h \
|
||||||
jsonrpc/devicehandler.h \
|
jsonrpc/devicehandler.h \
|
||||||
jsonrpc/ruleshandler.h \
|
jsonrpc/ruleshandler.h \
|
||||||
@ -52,6 +51,10 @@ HEADERS += nymeacore.h \
|
|||||||
jsonrpc/logginghandler.h \
|
jsonrpc/logginghandler.h \
|
||||||
jsonrpc/configurationhandler.h \
|
jsonrpc/configurationhandler.h \
|
||||||
jsonrpc/networkmanagerhandler.h \
|
jsonrpc/networkmanagerhandler.h \
|
||||||
|
jsonrpc/tagshandler.h \
|
||||||
|
jsonrpc/systemhandler.h \
|
||||||
|
jsonrpc/scriptshandler.h \
|
||||||
|
jsonrpc/usershandler.h \
|
||||||
logging/logging.h \
|
logging/logging.h \
|
||||||
logging/logengine.h \
|
logging/logengine.h \
|
||||||
logging/logfilter.h \
|
logging/logfilter.h \
|
||||||
@ -66,6 +69,7 @@ HEADERS += nymeacore.h \
|
|||||||
networkmanager/networksettings.h \
|
networkmanager/networksettings.h \
|
||||||
networkmanager/networkconnection.h \
|
networkmanager/networkconnection.h \
|
||||||
networkmanager/wirednetworkdevice.h \
|
networkmanager/wirednetworkdevice.h \
|
||||||
|
usermanager/userinfo.h \
|
||||||
usermanager/usermanager.h \
|
usermanager/usermanager.h \
|
||||||
usermanager/tokeninfo.h \
|
usermanager/tokeninfo.h \
|
||||||
usermanager/pushbuttondbusservice.h \
|
usermanager/pushbuttondbusservice.h \
|
||||||
@ -90,18 +94,15 @@ HEADERS += nymeacore.h \
|
|||||||
debugserverhandler.h \
|
debugserverhandler.h \
|
||||||
tagging/tagsstorage.h \
|
tagging/tagsstorage.h \
|
||||||
tagging/tag.h \
|
tagging/tag.h \
|
||||||
jsonrpc/tagshandler.h \
|
|
||||||
cloud/cloudtransport.h \
|
cloud/cloudtransport.h \
|
||||||
debugreportgenerator.h \
|
debugreportgenerator.h \
|
||||||
platform/platform.h \
|
platform/platform.h \
|
||||||
jsonrpc/systemhandler.h
|
|
||||||
|
|
||||||
SOURCES += nymeacore.cpp \
|
SOURCES += nymeacore.cpp \
|
||||||
devices/devicemanagerimplementation.cpp \
|
devices/devicemanagerimplementation.cpp \
|
||||||
devices/translator.cpp \
|
devices/translator.cpp \
|
||||||
experiences/experiencemanager.cpp \
|
experiences/experiencemanager.cpp \
|
||||||
jsonrpc/jsonrpcserverimplementation.cpp \
|
|
||||||
jsonrpc/scriptshandler.cpp \
|
|
||||||
ruleengine/ruleengine.cpp \
|
ruleengine/ruleengine.cpp \
|
||||||
ruleengine/rule.cpp \
|
ruleengine/rule.cpp \
|
||||||
ruleengine/stateevaluator.cpp \
|
ruleengine/stateevaluator.cpp \
|
||||||
@ -124,6 +125,7 @@ SOURCES += nymeacore.cpp \
|
|||||||
servers/websocketserver.cpp \
|
servers/websocketserver.cpp \
|
||||||
servers/bluetoothserver.cpp \
|
servers/bluetoothserver.cpp \
|
||||||
servers/mqttbroker.cpp \
|
servers/mqttbroker.cpp \
|
||||||
|
jsonrpc/jsonrpcserverimplementation.cpp \
|
||||||
jsonrpc/jsonvalidator.cpp \
|
jsonrpc/jsonvalidator.cpp \
|
||||||
jsonrpc/devicehandler.cpp \
|
jsonrpc/devicehandler.cpp \
|
||||||
jsonrpc/ruleshandler.cpp \
|
jsonrpc/ruleshandler.cpp \
|
||||||
@ -133,6 +135,10 @@ SOURCES += nymeacore.cpp \
|
|||||||
jsonrpc/logginghandler.cpp \
|
jsonrpc/logginghandler.cpp \
|
||||||
jsonrpc/configurationhandler.cpp \
|
jsonrpc/configurationhandler.cpp \
|
||||||
jsonrpc/networkmanagerhandler.cpp \
|
jsonrpc/networkmanagerhandler.cpp \
|
||||||
|
jsonrpc/tagshandler.cpp \
|
||||||
|
jsonrpc/systemhandler.cpp \
|
||||||
|
jsonrpc/scriptshandler.cpp \
|
||||||
|
jsonrpc/usershandler.cpp \
|
||||||
logging/logengine.cpp \
|
logging/logengine.cpp \
|
||||||
logging/logfilter.cpp \
|
logging/logfilter.cpp \
|
||||||
logging/logentry.cpp \
|
logging/logentry.cpp \
|
||||||
@ -145,6 +151,7 @@ SOURCES += nymeacore.cpp \
|
|||||||
networkmanager/networksettings.cpp \
|
networkmanager/networksettings.cpp \
|
||||||
networkmanager/networkconnection.cpp \
|
networkmanager/networkconnection.cpp \
|
||||||
networkmanager/wirednetworkdevice.cpp \
|
networkmanager/wirednetworkdevice.cpp \
|
||||||
|
usermanager/userinfo.cpp \
|
||||||
usermanager/usermanager.cpp \
|
usermanager/usermanager.cpp \
|
||||||
usermanager/tokeninfo.cpp \
|
usermanager/tokeninfo.cpp \
|
||||||
usermanager/pushbuttondbusservice.cpp \
|
usermanager/pushbuttondbusservice.cpp \
|
||||||
@ -169,11 +176,10 @@ SOURCES += nymeacore.cpp \
|
|||||||
debugserverhandler.cpp \
|
debugserverhandler.cpp \
|
||||||
tagging/tagsstorage.cpp \
|
tagging/tagsstorage.cpp \
|
||||||
tagging/tag.cpp \
|
tagging/tag.cpp \
|
||||||
jsonrpc/tagshandler.cpp \
|
|
||||||
cloud/cloudtransport.cpp \
|
cloud/cloudtransport.cpp \
|
||||||
debugreportgenerator.cpp \
|
debugreportgenerator.cpp \
|
||||||
platform/platform.cpp \
|
platform/platform.cpp \
|
||||||
jsonrpc/systemhandler.cpp
|
|
||||||
|
|
||||||
versionAtLeast(QT_VERSION, 5.12.0) {
|
versionAtLeast(QT_VERSION, 5.12.0) {
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
|
|||||||
@ -42,6 +42,8 @@
|
|||||||
|
|
||||||
#include "tokeninfo.h"
|
#include "tokeninfo.h"
|
||||||
|
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
namespace nymeaserver {
|
namespace nymeaserver {
|
||||||
|
|
||||||
TokenInfo::TokenInfo()
|
TokenInfo::TokenInfo()
|
||||||
@ -83,4 +85,14 @@ QString TokenInfo::deviceName() const
|
|||||||
return m_deviceName;
|
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 <QUuid>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
namespace nymeaserver {
|
namespace nymeaserver {
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ class TokenInfo
|
|||||||
Q_PROPERTY(QUuid id READ id)
|
Q_PROPERTY(QUuid id READ id)
|
||||||
Q_PROPERTY(QString username READ username)
|
Q_PROPERTY(QString username READ username)
|
||||||
Q_PROPERTY(QDateTime creationTime READ creationTime)
|
Q_PROPERTY(QDateTime creationTime READ creationTime)
|
||||||
Q_PROPERTY(QString deviveName READ deviceName)
|
Q_PROPERTY(QString deviceName READ deviceName)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TokenInfo();
|
TokenInfo();
|
||||||
@ -61,7 +62,17 @@ private:
|
|||||||
QString m_deviceName;
|
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
|
#endif // TOKENINFO_H
|
||||||
|
|||||||
22
libnymea-core/usermanager/userinfo.cpp
Normal file
22
libnymea-core/usermanager/userinfo.cpp
Normal file
@ -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;
|
||||||
|
}
|
||||||
53
libnymea-core/usermanager/userinfo.h
Normal file
53
libnymea-core/usermanager/userinfo.h
Normal file
@ -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 salt = QUuid::createUuid().toString().remove(QRegExp("[{}]")).toUtf8();
|
||||||
QByteArray hashedPassword = QCryptographicHash::hash(QString(password + salt).toUtf8(), QCryptographicHash::Sha512).toBase64();
|
QByteArray hashedPassword = QCryptographicHash::hash(QString(password + salt).toUtf8(), QCryptographicHash::Sha512).toBase64();
|
||||||
QString queryString = QString("INSERT INTO users(username, password, salt) values(\"%1\", \"%2\", \"%3\");")
|
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(hashedPassword))
|
||||||
.arg(QString::fromUtf8(salt));
|
.arg(QString::fromUtf8(salt));
|
||||||
m_db.exec(queryString);
|
m_db.exec(queryString);
|
||||||
@ -178,9 +178,41 @@ UserManager::UserError UserManager::createUser(const QString &username, const QS
|
|||||||
return UserErrorNoError;
|
return UserErrorNoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Remove the user with the given \a username and all of its tokens. If the \a username is empty, all anonymous
|
UserManager::UserError UserManager::changePassword(const QString &username, const QString &newPassword)
|
||||||
tokens (e.g. issued by pushbutton auth) will be cleared.
|
{
|
||||||
*/
|
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)
|
UserManager::UserError UserManager::removeUser(const QString &username)
|
||||||
{
|
{
|
||||||
if (!username.isEmpty()) {
|
if (!username.isEmpty()) {
|
||||||
@ -189,14 +221,11 @@ UserManager::UserError UserManager::removeUser(const QString &username)
|
|||||||
if (result.numRowsAffected() == 0) {
|
if (result.numRowsAffected() == 0) {
|
||||||
return UserErrorInvalidUserId;
|
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;
|
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();
|
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\");")
|
QString storeTokenQuery = QString("INSERT INTO tokens(id, username, token, creationdate, devicename) VALUES(\"%1\", \"%2\", \"%3\", \"%4\", \"%5\");")
|
||||||
.arg(QUuid::createUuid().toString())
|
.arg(QUuid::createUuid().toString())
|
||||||
.arg(username)
|
.arg(username.toLower())
|
||||||
.arg(QString::fromUtf8(token))
|
.arg(QString::fromUtf8(token))
|
||||||
.arg(NymeaCore::instance()->timeManager()->currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
|
.arg(NymeaCore::instance()->timeManager()->currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
|
||||||
.arg(deviceName);
|
.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. */
|
UserInfo UserManager::userInfo(const QByteArray &token) const
|
||||||
QString UserManager::userForToken(const QByteArray &token) const
|
|
||||||
{
|
{
|
||||||
if (!validateToken(token)) {
|
TokenInfo tokenInfo = this->tokenInfo(token);
|
||||||
qCWarning(dcUserManager) << "Token failed character validation:" << token;
|
|
||||||
return QString();
|
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);
|
QSqlQuery result = m_db.exec(getUserQuery);
|
||||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||||
qCWarning(dcUserManager) << "Error fetching username for token:" << m_db.lastError().databaseText() << m_db.lastError().driverText() << getUserQuery;
|
qCWarning(dcUserManager) << "Query for token failed:" << m_db.lastError().databaseText() << m_db.lastError().driverText() << getUserQuery;
|
||||||
return QString();
|
return UserInfo();
|
||||||
}
|
|
||||||
if (!result.first()) {
|
|
||||||
qCWarning(dcUserManager) << "No such token in DB:" << token;
|
|
||||||
return QString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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> UserManager::tokens(const QString &username) const
|
||||||
{
|
{
|
||||||
QList<TokenInfo> ret;
|
QList<TokenInfo> ret;
|
||||||
@ -328,6 +357,44 @@ QList<TokenInfo> UserManager::tokens(const QString &username) const
|
|||||||
return ret;
|
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. */
|
/*! Removes the token with the given \a tokenId. Returns \l{UserError} to inform about the result. */
|
||||||
UserManager::UserError UserManager::removeToken(const QUuid &tokenId)
|
UserManager::UserError UserManager::removeToken(const QUuid &tokenId)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -32,6 +32,7 @@
|
|||||||
#define USERMANAGER_H
|
#define USERMANAGER_H
|
||||||
|
|
||||||
#include "tokeninfo.h"
|
#include "tokeninfo.h"
|
||||||
|
#include "userinfo.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
@ -61,6 +62,7 @@ public:
|
|||||||
QStringList users() const;
|
QStringList users() const;
|
||||||
|
|
||||||
UserError createUser(const QString &username, const QString &password);
|
UserError createUser(const QString &username, const QString &password);
|
||||||
|
UserError changePassword(const QString &username, const QString &newPassword);
|
||||||
UserError removeUser(const QString &username);
|
UserError removeUser(const QString &username);
|
||||||
|
|
||||||
bool pushButtonAuthAvailable() const;
|
bool pushButtonAuthAvailable() const;
|
||||||
@ -68,9 +70,14 @@ public:
|
|||||||
QByteArray authenticate(const QString &username, const QString &password, const QString &deviceName);
|
QByteArray authenticate(const QString &username, const QString &password, const QString &deviceName);
|
||||||
int requestPushButtonAuth(const QString &deviceName);
|
int requestPushButtonAuth(const QString &deviceName);
|
||||||
void cancelPushButtonAuth(int transactionId);
|
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;
|
QList<TokenInfo> tokens(const QString &username) const;
|
||||||
nymeaserver::UserManager::UserError removeToken(const QUuid &tokenId);
|
|
||||||
|
UserError removeToken(const QUuid &tokenId);
|
||||||
|
|
||||||
|
|
||||||
bool verifyToken(const QByteArray &token);
|
bool verifyToken(const QByteArray &token);
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p"
|
|||||||
|
|
||||||
# define protocol versions
|
# define protocol versions
|
||||||
JSON_PROTOCOL_VERSION_MAJOR=4
|
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}"
|
JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}"
|
||||||
LIBNYMEA_API_VERSION_MAJOR=4
|
LIBNYMEA_API_VERSION_MAJOR=4
|
||||||
LIBNYMEA_API_VERSION_MINOR=0
|
LIBNYMEA_API_VERSION_MINOR=0
|
||||||
@ -105,3 +105,4 @@ coverage {
|
|||||||
ccache {
|
ccache {
|
||||||
message("Using ccache.")
|
message("Using ccache.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
4.1
|
4.2
|
||||||
{
|
{
|
||||||
"enums": {
|
"enums": {
|
||||||
"BasicType": [
|
"BasicType": [
|
||||||
@ -920,6 +920,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"JSONRPC.RemoveToken": {
|
"JSONRPC.RemoveToken": {
|
||||||
|
"deprecated": "Use Users.RemoveToken instead.",
|
||||||
"description": "Revoke access for a given token.",
|
"description": "Revoke access for a given token.",
|
||||||
"params": {
|
"params": {
|
||||||
"tokenId": "Uuid"
|
"tokenId": "Uuid"
|
||||||
@ -974,6 +975,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"JSONRPC.Tokens": {
|
"JSONRPC.Tokens": {
|
||||||
|
"deprecated": "Use Users.GetTokens instead.",
|
||||||
"description": "Return a list of TokenInfo objects of all the tokens for the current user.",
|
"description": "Return a list of TokenInfo objects of all the tokens for the current user.",
|
||||||
"params": {
|
"params": {
|
||||||
},
|
},
|
||||||
@ -1464,6 +1466,52 @@
|
|||||||
"returns": {
|
"returns": {
|
||||||
"tagError": "$ref:TagError"
|
"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": {
|
"notifications": {
|
||||||
@ -2147,10 +2195,16 @@
|
|||||||
],
|
],
|
||||||
"TokenInfo": {
|
"TokenInfo": {
|
||||||
"r:creationTime": "Uint",
|
"r:creationTime": "Uint",
|
||||||
"r:deviveName": "String",
|
"r:deviceName": "String",
|
||||||
"r:id": "Uuid",
|
"r:id": "Uuid",
|
||||||
"r:username": "String"
|
"r:username": "String"
|
||||||
},
|
},
|
||||||
|
"TokenInfoList": [
|
||||||
|
"$ref:TokenInfo"
|
||||||
|
],
|
||||||
|
"UserInfo": {
|
||||||
|
"r:username": "String"
|
||||||
|
},
|
||||||
"Vendor": {
|
"Vendor": {
|
||||||
"displayName": "String",
|
"displayName": "String",
|
||||||
"id": "Uuid",
|
"id": "Uuid",
|
||||||
|
|||||||
@ -675,7 +675,7 @@ void TestJSONRPC::enableDisableNotifications_legacy()
|
|||||||
|
|
||||||
QStringList expectedNamespaces;
|
QStringList expectedNamespaces;
|
||||||
if (enabled == "true") {
|
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());
|
std::sort(expectedNamespaces.begin(), expectedNamespaces.end());
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,7 @@
|
|||||||
#include "nymeacore.h"
|
#include "nymeacore.h"
|
||||||
#include "nymeatestbase.h"
|
#include "nymeatestbase.h"
|
||||||
#include "usermanager/usermanager.h"
|
#include "usermanager/usermanager.h"
|
||||||
|
#include "servers/mocktcpserver.h"
|
||||||
|
|
||||||
using namespace nymeaserver;
|
using namespace nymeaserver;
|
||||||
|
|
||||||
@ -44,11 +45,33 @@ public:
|
|||||||
TestUsermanager(QObject* parent = nullptr);
|
TestUsermanager(QObject* parent = nullptr);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void createUser_data();
|
void init();
|
||||||
|
|
||||||
|
void loginValidation_data();
|
||||||
|
void loginValidation();
|
||||||
|
|
||||||
void createUser();
|
void createUser();
|
||||||
|
void authenticate();
|
||||||
|
|
||||||
|
void createDuplicateUser();
|
||||||
|
|
||||||
|
void getTokens();
|
||||||
|
|
||||||
|
void removeToken();
|
||||||
|
|
||||||
|
void unauthenticatedCallAfterTokenRemove();
|
||||||
|
|
||||||
|
void changePassword();
|
||||||
|
|
||||||
|
void authenticateAfterPasswordChangeOK();
|
||||||
|
|
||||||
|
void authenticateAfterPasswordChangeFail();
|
||||||
|
|
||||||
|
void getUserInfo();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LogEngine *engine;
|
// m_apiToken is in testBase
|
||||||
|
QUuid m_tokenId;
|
||||||
};
|
};
|
||||||
|
|
||||||
TestUsermanager::TestUsermanager(QObject *parent): NymeaTestBase(parent)
|
TestUsermanager::TestUsermanager(QObject *parent): NymeaTestBase(parent)
|
||||||
@ -56,7 +79,18 @@ TestUsermanager::TestUsermanager(QObject *parent): NymeaTestBase(parent)
|
|||||||
QCoreApplication::instance()->setOrganizationName("nymea-test");
|
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>("username");
|
||||||
QTest::addColumn<QString>("password");
|
QTest::addColumn<QString>("password");
|
||||||
QTest::addColumn<UserManager::UserError>("expectedError");
|
QTest::addColumn<UserManager::UserError>("expectedError");
|
||||||
@ -88,23 +122,163 @@ void TestUsermanager::createUser_data() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestUsermanager::createUser()
|
void TestUsermanager::loginValidation()
|
||||||
{
|
{
|
||||||
QFETCH(QString, username);
|
QFETCH(QString, username);
|
||||||
QFETCH(QString, password);
|
QFETCH(QString, password);
|
||||||
QFETCH(UserManager::UserError, expectedError);
|
QFETCH(UserManager::UserError, expectedError);
|
||||||
|
|
||||||
UserManager *userManager = NymeaCore::instance()->userManager();
|
UserManager *userManager = NymeaCore::instance()->userManager();
|
||||||
foreach (const QString &user, userManager->users()) {
|
|
||||||
userManager->removeUser(user);
|
|
||||||
}
|
|
||||||
userManager->removeUser("");
|
|
||||||
|
|
||||||
UserManager::UserError error = userManager->createUser(username, password);
|
UserManager::UserError error = userManager->createUser(username, password);
|
||||||
qDebug() << "Error:" << error << "Expected:" << expectedError;
|
qDebug() << "Error:" << error << "Expected:" << expectedError;
|
||||||
QCOMPARE(error, 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"
|
#include "testusermanager.moc"
|
||||||
QTEST_MAIN(TestUsermanager)
|
QTEST_MAIN(TestUsermanager)
|
||||||
|
|||||||
Reference in New Issue
Block a user