Add admin methods for user management
This commit is contained in:
parent
ded99e35d4
commit
662e313bd8
@ -56,7 +56,7 @@ UsersHandler::UsersHandler(UserManager *userManager, QObject *parent):
|
||||
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);
|
||||
registerMethod("ChangePassword", description, params, returns); // TODO: PermissionScopeChangeUserInfos
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Change the password for the given user. All tokens for this user will be removed in order to force all clients to log in again.";
|
||||
@ -75,21 +75,21 @@ UsersHandler::UsersHandler(UserManager *userManager, QObject *parent):
|
||||
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, Types::PermissionScopeNone);
|
||||
registerMethod("GetTokens", description, params, returns); // TODO: PermissionScopeChangeUserInfos
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get all the tokens for the given username.";
|
||||
params.insert("username", enumValueName(String));
|
||||
returns.insert("o:tokenInfoList", objectRef<TokenInfoList>());
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("GetUserTokens", description, params, returns, Types::PermissionScopeNone);
|
||||
registerMethod("GetUserTokens", description, params, returns);
|
||||
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Revoke access for a given token. Depending on the logged in user only the own tokens can be removed. If you are logged in as admin, any token can be removed.";
|
||||
params.insert("tokenId", enumValueName(Uuid));
|
||||
returns.insert("error", enumRef<UserManager::UserError>());
|
||||
registerMethod("RemoveToken", description, params, returns, Types::PermissionScopeNone);
|
||||
registerMethod("RemoveToken", description, params, returns); // TODO: PermissionScopeChangeUserInfos
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Return a list of all users in the system.";
|
||||
@ -186,19 +186,20 @@ JsonReply *UsersHandler::CreateUser(const QVariantMap ¶ms)
|
||||
|
||||
JsonReply *UsersHandler::ChangePassword(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
QVariantMap ret;
|
||||
QVariantMap returns;
|
||||
|
||||
QByteArray currentToken = context.token();
|
||||
if (currentToken.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot change password from an unauthenticated connection";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
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);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
QString newPassword = params.value("newPassword").toString();
|
||||
@ -206,78 +207,82 @@ JsonReply *UsersHandler::ChangePassword(const QVariantMap ¶ms, const JsonCon
|
||||
TokenInfo tokenInfo = m_userManager->tokenInfo(currentToken);
|
||||
|
||||
UserManager::UserError status = m_userManager->changePassword(tokenInfo.username(), newPassword);
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(status));
|
||||
return createReply(ret);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(status));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::ChangeUserPassword(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
QVariantMap ret;
|
||||
QVariantMap returns;
|
||||
|
||||
QByteArray currentToken = context.token();
|
||||
if (currentToken.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot change a user password from an unauthenticated connection";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
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. Cannot change a user password from an unauthenticated connection";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
QString username = params.value("username").toString();;
|
||||
QString newPassword = params.value("newPassword").toString();
|
||||
|
||||
UserManager::UserError status = m_userManager->changePassword(username, newPassword);
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(status));
|
||||
return createReply(ret);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(status));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::GetUserInfo(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
QVariantMap ret;
|
||||
|
||||
QVariantMap returns;
|
||||
|
||||
QByteArray currentToken = context.token();
|
||||
if (currentToken.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot get user info from an unauthenticated connection";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
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);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
TokenInfo tokenInfo = m_userManager->tokenInfo(currentToken);
|
||||
|
||||
UserInfo userInfo = m_userManager->userInfo(tokenInfo.username());
|
||||
ret.insert("userInfo", pack(userInfo));
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorNoError));
|
||||
return createReply(ret);
|
||||
returns.insert("userInfo", pack(userInfo));
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorNoError));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::GetTokens(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
QVariantMap ret;
|
||||
|
||||
QVariantMap returns;
|
||||
|
||||
QByteArray currentToken = context.token();
|
||||
if (currentToken.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot fetch tokens for an unauthenticated connection";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
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);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
TokenInfo tokenInfo = m_userManager->tokenInfo(currentToken);
|
||||
@ -287,52 +292,87 @@ JsonReply *UsersHandler::GetTokens(const QVariantMap ¶ms, const JsonContext
|
||||
foreach (const TokenInfo &tokenInfo, tokens) {
|
||||
retList << pack(tokenInfo);
|
||||
}
|
||||
ret.insert("tokenInfoList", retList);
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorNoError));
|
||||
return createReply(ret);
|
||||
returns.insert("tokenInfoList", retList);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorNoError));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::GetUserTokens(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
QVariantMap returns;
|
||||
|
||||
QByteArray currentToken = context.token();
|
||||
if (currentToken.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot fetch tokens for an unauthenticated connection";
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
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?";
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
QString username = params.value("username").toString();;
|
||||
|
||||
qCDebug(dcJsonRpc()) << "Fetching tokens for user" << username;
|
||||
QList<TokenInfo> tokens = m_userManager->tokens(username);
|
||||
QVariantList retList;
|
||||
foreach (const TokenInfo &tokenInfo, tokens) {
|
||||
retList << pack(tokenInfo);
|
||||
}
|
||||
returns.insert("tokenInfoList", retList);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorNoError));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::RemoveToken(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
QVariantMap ret;
|
||||
QVariantMap returns;
|
||||
|
||||
QByteArray currentToken = context.token();
|
||||
if (currentToken.isEmpty()) {
|
||||
qCWarning(dcJsonRpc()) << "Cannot remove a token from an unauthenticated connection.";
|
||||
ret.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
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);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
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);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorTokenNotFound));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
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);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
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);
|
||||
returns.insert("error", enumValueName<UserManager::UserError>(error));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *UsersHandler::GetUsers(const QVariantMap ¶ms)
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
|
||||
QVariantMap reply;
|
||||
reply.insert("users", pack(m_userManager->users()));
|
||||
return createReply(reply);
|
||||
@ -372,7 +412,7 @@ JsonReply *UsersHandler::SetUserScopes(const QVariantMap ¶ms, const JsonCont
|
||||
|
||||
JsonReply *UsersHandler::SetUserInfo(const QVariantMap ¶ms, const JsonContext &context)
|
||||
{
|
||||
QVariantMap ret;
|
||||
QVariantMap returns;
|
||||
|
||||
TokenInfo callingTokenInfo = m_userManager->tokenInfo(context.token());
|
||||
QString username;
|
||||
@ -384,8 +424,8 @@ JsonReply *UsersHandler::SetUserInfo(const QVariantMap ¶ms, const JsonContex
|
||||
}
|
||||
|
||||
if (callingTokenInfo.username() != username && !m_userManager->userInfo(callingTokenInfo.username()).scopes().testFlag(Types::PermissionScopeAdmin)) {
|
||||
ret.insert("error", enumValueName(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(ret);
|
||||
returns.insert("error", enumValueName(UserManager::UserErrorPermissionDenied));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
UserInfo changedUserInfo = m_userManager->userInfo(username);
|
||||
@ -403,8 +443,8 @@ JsonReply *UsersHandler::SetUserInfo(const QVariantMap ¶ms, const JsonContex
|
||||
displayName = changedUserInfo.displayName();
|
||||
}
|
||||
UserManager::UserError status = m_userManager->setUserInfo(username, email, displayName);
|
||||
ret.insert("error", enumValueName(status));
|
||||
return createReply(ret);
|
||||
returns.insert("error", enumValueName(status));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -46,6 +46,7 @@ public:
|
||||
Q_INVOKABLE JsonReply *ChangeUserPassword(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *GetUserInfo(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *GetTokens(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *GetUserTokens(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *RemoveToken(const QVariantMap ¶ms, const JsonContext &context);
|
||||
Q_INVOKABLE JsonReply *GetUsers(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *RemoveUser(const QVariantMap ¶ms, const JsonContext &context);
|
||||
|
||||
@ -68,6 +68,7 @@ private:
|
||||
QList<ThingId> m_allowedThingIds;
|
||||
};
|
||||
|
||||
|
||||
class UserInfoList: public QList<UserInfo>
|
||||
{
|
||||
Q_GADGET
|
||||
@ -76,8 +77,10 @@ public:
|
||||
Q_INVOKABLE QVariant get(int index) const;
|
||||
Q_INVOKABLE void put(const QVariant &variant);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(nymeaserver::UserInfo);
|
||||
Q_DECLARE_METATYPE(nymeaserver::UserInfoList);
|
||||
|
||||
#endif // USERINFO_H
|
||||
|
||||
@ -173,21 +173,28 @@ UserManager::UserError UserManager::createUser(const QString &username, const QS
|
||||
// Verify thing IDs, if there is no thing with this id, we don't save it and it will not be verified.
|
||||
// We don't return an error, the thing might have dissapeared
|
||||
QList<ThingId> thingIds;
|
||||
foreach (const ThingId &thingId, allowedThingIds) {
|
||||
if (NymeaCore::instance()->thingManager()->configuredThings().findById(thingId) == nullptr) {
|
||||
qCWarning(dcUserManager()) << "Cannot set user scope for" << username << "because there is no thing with ID ";
|
||||
} else {
|
||||
thingIds.append(thingId);
|
||||
ThingManager *thingManager = NymeaCore::instance()->thingManager();
|
||||
if (!thingManager) {
|
||||
qCWarning(dcUserManager()) << "Cannot validate allowed things for user" << username
|
||||
<< "because thing manager is not available yet. Skipping validation.";
|
||||
thingIds = allowedThingIds;
|
||||
} else {
|
||||
foreach (const ThingId &thingId, allowedThingIds) {
|
||||
if (thingManager->configuredThings().findById(thingId) == nullptr) {
|
||||
qCWarning(dcUserManager()) << "Cannot set user scope for" << username << "because there is no thing with ID ";
|
||||
} else {
|
||||
thingIds.append(thingId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QSqlQuery checkForDuplicateUserQuery(m_db);
|
||||
checkForDuplicateUserQuery.prepare("SELECT * FROM users WHERE lower(username) = ?;");
|
||||
checkForDuplicateUserQuery.prepare("SELECT * FROM users WHERE lower(username) = :username;");
|
||||
checkForDuplicateUserQuery.bindValue(":username", username.toLower());
|
||||
// Note: We're using toLower() on the username mainly for the reason that in old versions the username used to be an email address
|
||||
checkForDuplicateUserQuery.addBindValue(username.toLower());
|
||||
checkForDuplicateUserQuery.exec();
|
||||
if (checkForDuplicateUserQuery.first()) {
|
||||
qCWarning(dcUserManager) << "Username already in use";
|
||||
qCWarning(dcUserManager) << "Username" << username << "already in use";
|
||||
return UserErrorDuplicateUserId;
|
||||
}
|
||||
|
||||
@ -297,11 +304,19 @@ UserManager::UserError UserManager::setUserScopes(const QString &username, Types
|
||||
// Verify thing IDs, if there is no thing with this id, we don't save it and it will not be verified.
|
||||
// We don't return an error, the thing might have dissapeared
|
||||
QList<ThingId> thingIds;
|
||||
foreach (const ThingId &thingId, allowedThingIds) {
|
||||
if (NymeaCore::instance()->thingManager()->configuredThings().findById(thingId) == nullptr) {
|
||||
qCWarning(dcUserManager()) << "The user" << username << "should have access to thing with ID" << thingId.toString() << "but there is no such thing. Ignoring value.";
|
||||
} else {
|
||||
thingIds.append(thingId);
|
||||
ThingManager *thingManager = NymeaCore::instance()->thingManager();
|
||||
if (!thingManager) {
|
||||
qCWarning(dcUserManager()) << "Cannot validate allowed things for user" << username
|
||||
<< "because thing manager is not available yet. Skipping validation.";
|
||||
thingIds = allowedThingIds;
|
||||
} else {
|
||||
foreach (const ThingId &thingId, allowedThingIds) {
|
||||
if (thingManager->configuredThings().findById(thingId) == nullptr) {
|
||||
qCWarning(dcUserManager()) << "The user" << username << "should have access to thing with ID"
|
||||
<< thingId.toString() << "but there is no such thing. Ignoring value.";
|
||||
} else {
|
||||
thingIds.append(thingId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -337,8 +352,9 @@ UserManager::UserError UserManager::setUserScopes(const QString &username, Types
|
||||
QString allowedThingIdsString = Types::thingIdsToStringList(thingIds).join(',');
|
||||
|
||||
qCDebug(dcUserManager()) << "Updating scopes of user" << username << "Scopes:" << scopes << "Allowed things:" << allowedThingIds;
|
||||
|
||||
QSqlQuery setScopesQuery(m_db);
|
||||
setScopesQuery.prepare("UPDATE users SET scopes = :scopes, allowedThingIds = :allowedThingIds WHERE username = :username");
|
||||
setScopesQuery.prepare("UPDATE users SET scopes = :scopes, allowedThingIds = :allowedThingIds WHERE username = :username;");
|
||||
setScopesQuery.bindValue(":username", username);
|
||||
setScopesQuery.bindValue(":scopes", scopesString);
|
||||
setScopesQuery.bindValue(":allowedThingIds", allowedThingIdsString);
|
||||
@ -393,13 +409,14 @@ QByteArray UserManager::authenticate(const QString &username, const QString &pas
|
||||
}
|
||||
|
||||
QSqlQuery passwordQuery(m_db);
|
||||
passwordQuery.prepare("SELECT password, salt FROM users WHERE lower(username) = ?;");
|
||||
passwordQuery.addBindValue(username.toLower());
|
||||
passwordQuery.prepare("SELECT password, salt FROM users WHERE lower(username) = :username;");
|
||||
passwordQuery.bindValue(":username", username.toLower());
|
||||
passwordQuery.exec();
|
||||
if (!passwordQuery.first()) {
|
||||
qCWarning(dcUserManager) << "No such username" << username;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QByteArray salt = passwordQuery.value("salt").toByteArray();
|
||||
QByteArray hashedPassword = passwordQuery.value("password").toByteArray();
|
||||
|
||||
@ -409,16 +426,18 @@ QByteArray UserManager::authenticate(const QString &username, const QString &pas
|
||||
}
|
||||
|
||||
QByteArray token = QCryptographicHash::hash(QUuid::createUuid().toByteArray(), QCryptographicHash::Sha256).toBase64();
|
||||
QString storeTokenQueryString = QString("INSERT INTO tokens(id, username, token, creationdate, devicename) VALUES(\"%1\", \"%2\", \"%3\", \"%4\", \"%5\");")
|
||||
.arg(QUuid::createUuid().toString())
|
||||
.arg(username.toLower())
|
||||
.arg(QString::fromUtf8(token))
|
||||
.arg(NymeaCore::instance()->timeManager()->currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
|
||||
.arg(deviceName);
|
||||
|
||||
QSqlQuery storeTokenQuery(m_db);
|
||||
if (!storeTokenQuery.exec(storeTokenQueryString)) {
|
||||
qCWarning(dcUserManager()) << "Unable to execute SQL query" << storeTokenQueryString << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
storeTokenQuery.prepare("INSERT INTO tokens (id, username, token, creationdate, devicename)"
|
||||
"VALUES (:id, :username, :token, :creationdate, :devicename)");
|
||||
storeTokenQuery.bindValue(":id", QUuid::createUuid().toString());
|
||||
storeTokenQuery.bindValue(":username", username.toLower());
|
||||
storeTokenQuery.bindValue(":token", QString::fromUtf8(token));
|
||||
storeTokenQuery.bindValue(":creationdate", NymeaCore::instance()->timeManager()->currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
|
||||
storeTokenQuery.bindValue(":devicename", deviceName);
|
||||
|
||||
if (!storeTokenQuery.exec()) {
|
||||
qCWarning(dcUserManager()) << "Unable to execute SQL query" << storeTokenQuery.lastQuery() << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
@ -469,17 +488,16 @@ void UserManager::cancelPushButtonAuth(int transactionId)
|
||||
*/
|
||||
UserInfo UserManager::userInfo(const QString &username) const
|
||||
{
|
||||
QString getUserQueryString = QString("SELECT * FROM users WHERE lower(username) = \"%1\";")
|
||||
.arg(username);
|
||||
|
||||
QSqlQuery getUserQuery(m_db);
|
||||
if (!getUserQuery.exec(getUserQueryString)) {
|
||||
qCWarning(dcUserManager()) << "Unable to execute SQL query" << getUserQueryString << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
getUserQuery.prepare("SELECT * FROM users WHERE lower(username) = :username;");
|
||||
getUserQuery.bindValue(":username", username);
|
||||
if (!getUserQuery.exec()) {
|
||||
qCWarning(dcUserManager()) << "Unable to execute SQL query" << getUserQuery.lastQuery() << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
return UserInfo();
|
||||
}
|
||||
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Query for user" << username << "failed:" << getUserQueryString << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
qCWarning(dcUserManager) << "Query for user" << username << "failed:" << getUserQuery.lastQuery() << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
return UserInfo();
|
||||
}
|
||||
|
||||
@ -499,8 +517,8 @@ QList<TokenInfo> UserManager::tokens(const QString &username) const
|
||||
QList<TokenInfo> ret;
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("SELECT id, username, creationdate, deviceName FROM tokens WHERE lower(username) = ?;");
|
||||
query.addBindValue(username.toLower());
|
||||
query.prepare("SELECT id, username, creationdate, deviceName FROM tokens WHERE lower(username) = :username;");
|
||||
query.bindValue(":username", username.toLower());
|
||||
query.exec();
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Query for tokens failed:" << query.lastError().databaseText() << query.lastError().driverText() << query.executedQuery();
|
||||
@ -520,17 +538,16 @@ TokenInfo UserManager::tokenInfo(const QByteArray &token) const
|
||||
return TokenInfo();
|
||||
}
|
||||
|
||||
QString getTokenQueryString = QString("SELECT id, username, creationdate, deviceName FROM tokens WHERE token = \"%1\";")
|
||||
.arg(QString::fromUtf8(token));
|
||||
|
||||
QSqlQuery getTokenQuery(m_db);
|
||||
if (!getTokenQuery.exec(getTokenQueryString)) {
|
||||
qCWarning(dcUserManager()) << "Unable to execute SQL query" << getTokenQueryString << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
getTokenQuery.prepare("SELECT id, username, creationdate, deviceName FROM tokens WHERE token = :token;");
|
||||
getTokenQuery.bindValue(":token", QString::fromUtf8(token));
|
||||
if (!getTokenQuery.exec()) {
|
||||
qCWarning(dcUserManager()) << "Unable to execute SQL query" << getTokenQuery.lastQuery() << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
return TokenInfo();
|
||||
}
|
||||
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Query for token failed:" << getTokenQueryString << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
qCWarning(dcUserManager) << "Query for token failed:" << getTokenQuery.lastQuery() << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
return TokenInfo();
|
||||
}
|
||||
|
||||
@ -542,17 +559,16 @@ TokenInfo UserManager::tokenInfo(const QByteArray &token) const
|
||||
|
||||
TokenInfo UserManager::tokenInfo(const QUuid &tokenId) const
|
||||
{
|
||||
QString getTokenQueryString = QString("SELECT id, username, creationdate, deviceName FROM tokens WHERE id = \"%1\";")
|
||||
.arg(tokenId.toString());
|
||||
|
||||
QSqlQuery getTokenQuery(m_db);
|
||||
if (!getTokenQuery.exec(getTokenQueryString)) {
|
||||
qCWarning(dcUserManager()) << "Unable to execute SQL query" << getTokenQueryString << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
getTokenQuery.prepare("SELECT id, username, creationdate, deviceName FROM tokens WHERE id = :id;");
|
||||
getTokenQuery.bindValue(":id", tokenId.toString());
|
||||
if (!getTokenQuery.exec()) {
|
||||
qCWarning(dcUserManager()) << "Unable to execute SQL query" << getTokenQuery.lastQuery() << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
return TokenInfo();
|
||||
}
|
||||
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Query for token failed:" << getTokenQueryString << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
qCWarning(dcUserManager) << "Query for token failed:" << getTokenQuery.lastQuery() << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
return TokenInfo();
|
||||
}
|
||||
|
||||
@ -565,21 +581,22 @@ TokenInfo UserManager::tokenInfo(const QUuid &tokenId) const
|
||||
/*! Removes the token with the given \a tokenId. Returns \l{UserError} to inform about the result. */
|
||||
UserManager::UserError UserManager::removeToken(const QUuid &tokenId)
|
||||
{
|
||||
QString removeTokenQueryString = QString("DELETE FROM tokens WHERE id = \"%1\";")
|
||||
.arg(tokenId.toString());
|
||||
|
||||
QSqlQuery removeTokenQuery(m_db);
|
||||
if (!removeTokenQuery.exec(removeTokenQueryString)) {
|
||||
qCWarning(dcUserManager()) << "Unable to execute SQL query" << removeTokenQueryString << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
removeTokenQuery.prepare("DELETE FROM tokens WHERE id = :id;");
|
||||
removeTokenQuery.bindValue(":id", tokenId.toString());
|
||||
|
||||
if (!removeTokenQuery.exec()) {
|
||||
qCWarning(dcUserManager()) << "Unable to execute SQL query" << removeTokenQuery.lastQuery() << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
return UserErrorBackendError;
|
||||
}
|
||||
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Removing token failed:" << m_db.lastError().databaseText() << m_db.lastError().driverText() << removeTokenQueryString;
|
||||
qCWarning(dcUserManager) << "Removing token failed:" << removeTokenQuery.lastQuery() << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
return UserErrorBackendError;
|
||||
}
|
||||
|
||||
if (removeTokenQuery.numRowsAffected() != 1) {
|
||||
qCWarning(dcUserManager) << "Token not found in DB";
|
||||
qCWarning(dcUserManager) << "Tried to remove token, but the token could not be found in the DB.";
|
||||
return UserErrorTokenNotFound;
|
||||
}
|
||||
|
||||
@ -594,25 +611,26 @@ bool UserManager::verifyToken(const QByteArray &token)
|
||||
qCWarning(dcUserManager) << "Token failed character validation" << token;
|
||||
return false;
|
||||
}
|
||||
QString getTokenQueryString = QString("SELECT * FROM tokens WHERE token = \"%1\";")
|
||||
.arg(QString::fromUtf8(token));
|
||||
|
||||
QSqlQuery getTokenQuery(m_db);
|
||||
if (!getTokenQuery.exec(getTokenQueryString)) {
|
||||
qCWarning(dcUserManager()) << "Unable to execute SQL query" << getTokenQueryString << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
getTokenQuery.prepare("SELECT * FROM tokens WHERE token = :token;");
|
||||
getTokenQuery.bindValue(":token", QString::fromUtf8(token));
|
||||
|
||||
if (!getTokenQuery.exec()) {
|
||||
qCWarning(dcUserManager()) << "Unable to execute SQL query" << getTokenQuery.lastQuery() << m_db.lastError().databaseText() << m_db.lastError().driverText();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Query for token failed:" << m_db.lastError().databaseText() << m_db.lastError().driverText() << getTokenQueryString;
|
||||
qCWarning(dcUserManager) << "Query for token failed:" << getTokenQuery.lastQuery() << m_db.lastError().databaseText() << m_db.lastError().driverText() << getTokenQuery.lastQuery();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!getTokenQuery.first()) {
|
||||
qCDebug(dcUserManager) << "Authorization failed for token" << token;
|
||||
return false;
|
||||
}
|
||||
|
||||
//qCDebug(dcUserManager) << "Token authorized for user" << result.value("username").toString();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -75,6 +75,7 @@ public:
|
||||
|
||||
UserError removeToken(const QUuid &tokenId);
|
||||
|
||||
|
||||
bool verifyToken(const QByteArray &token);
|
||||
|
||||
bool hasRestrictedThingAccess(const QByteArray &token) const;
|
||||
|
||||
@ -207,6 +207,8 @@ public:
|
||||
PermissionScopeConfigureRules = 0x0030,
|
||||
PermissionScopeAdmin = 0xFFFF,
|
||||
};
|
||||
// TODO: PermissionScopeChangeUserInfos = 0x0008, // Allow to change password, remove tokens, update user information (display name, email)
|
||||
|
||||
Q_ENUM(PermissionScope)
|
||||
Q_DECLARE_FLAGS(PermissionScopes, PermissionScope)
|
||||
Q_FLAG(PermissionScopes)
|
||||
|
||||
@ -1048,7 +1048,8 @@
|
||||
},
|
||||
"permissionScope": "PermissionScopeNone",
|
||||
"returns": {
|
||||
"ioConnections": "$ref:IOConnections"
|
||||
"o:ioConnections": "$ref:IOConnections",
|
||||
"thingError": "$ref:ThingError"
|
||||
}
|
||||
},
|
||||
"Integrations.GetPluginConfiguration": {
|
||||
@ -1971,6 +1972,17 @@
|
||||
"error": "$ref:UserError"
|
||||
}
|
||||
},
|
||||
"Users.ChangeUserPassword": {
|
||||
"description": "Change the password for the given user. All tokens for this user will be removed in order to force all clients to log in again.",
|
||||
"params": {
|
||||
"newPassword": "String",
|
||||
"username": "String"
|
||||
},
|
||||
"permissionScope": "PermissionScopeAdmin",
|
||||
"returns": {
|
||||
"error": "$ref:UserError"
|
||||
}
|
||||
},
|
||||
"Users.CreateUser": {
|
||||
"description": "Create a new user in the API with the given username and password. Use scopes to define the permissions for the new user. If the user has not the permission \"PermissionScopeAccessAllThings\", the list of things this user has access to can be defined in the \"allowedThingIds\" property. If no scopes are given, this user will be an admin user. Call Authenticate after this to obtain a device token for this user.",
|
||||
"params": {
|
||||
@ -2008,6 +2020,17 @@
|
||||
"o:userInfo": "$ref:UserInfo"
|
||||
}
|
||||
},
|
||||
"Users.GetUserTokens": {
|
||||
"description": "Get all the tokens for the given username.",
|
||||
"params": {
|
||||
"username": "String"
|
||||
},
|
||||
"permissionScope": "PermissionScopeAdmin",
|
||||
"returns": {
|
||||
"error": "$ref:UserError",
|
||||
"o:tokenInfoList": "$ref:TokenInfoList"
|
||||
}
|
||||
},
|
||||
"Users.GetUsers": {
|
||||
"description": "Return a list of all users in the system.",
|
||||
"params": {
|
||||
@ -2018,7 +2041,7 @@
|
||||
}
|
||||
},
|
||||
"Users.RemoveToken": {
|
||||
"description": "Revoke access for a given token.",
|
||||
"description": "Revoke access for a given token. Depending on the logged in user only the own tokens can be removed. If you are logged in as admin, any token can be removed.",
|
||||
"params": {
|
||||
"tokenId": "Uuid"
|
||||
},
|
||||
@ -3257,6 +3280,9 @@
|
||||
"sslEnabled": "Bool"
|
||||
},
|
||||
"UserInfo": {
|
||||
"r:allowedThingIds": [
|
||||
"Uuid"
|
||||
],
|
||||
"r:displayName": "String",
|
||||
"r:email": "String",
|
||||
"r:scopes": "$ref:PermissionScopes",
|
||||
|
||||
Reference in New Issue
Block a user