Feat: Add Admin API (#617)

* Push progress

TODO: rework RESET, probably to main.cpp again

* resetPassword rework

* enable administration restriction

* add short cmd for userdata

* Js apis

* Refactor JsonCB class

* Add userToken Auth

* Feat: Close connection if ext clients when def pw is set

* Feat: Protect db against pw/token tests

* WebUi PW Support (#9)

* Initial WebUi Password Support

* Small changes

* Initial WebUi Password Support

* Small changes

* Basic WebUi Token support

* added "removeStorage", added uiLock, updated login page

* Small improvments

* Small change

* Fix: prevent downgrade of authorization

* Add translation for localAdminAuth

* Feat: Show always save button in led layout

* Revert "Feat: Show always save button in led layout"

This reverts commit caad1dfcde.

* Feat: Password change link in notification

* Fix: body padding modal overlap

* Feat: Add instance index to response on switch

* prevent schema error

Signed-off-by: Paulchen-Panther <Paulchen-Panter@protonmail.com>

* Feat: add pw save

* Feat: callout settings/pw replaced with notification
This commit is contained in:
brindosch
2019-09-17 21:33:46 +02:00
committed by GitHub
parent 04c3bc8cc9
commit 5e559627be
28 changed files with 8047 additions and 137 deletions

View File

@@ -37,6 +37,11 @@ public:
///
void handleMessage(const QString & message, const QString& httpAuthHeader = "");
///
/// @brief Initialization steps
///
void initialize(void);
public slots:
///
/// @brief Is called whenever the current Hyperion instance pushes new led raw values (if enabled)
@@ -92,6 +97,11 @@ signals:
///
void forwardJsonMessage(QJsonObject);
///
/// @brief The API might decide to block connections for security reasons, this emitter should close the socket
///
void forceClose();
private:
/// Auth management pointer
AuthManager* _authManager;
@@ -112,6 +122,9 @@ private:
/// Log instance
Logger* _log;
/// Is this a local connection
bool _localConnection;
/// Hyperion instance manager
HyperionIManager* _instanceManager;
@@ -323,4 +336,9 @@ private:
/// @param error String describing the error
///
void sendErrorReply(const QString & error, const QString &command="", const int tan=0);
///
/// @brief Kill all signal/slot connections to stop possible data emitter
///
void stopDataConnections(void);
};

View File

@@ -23,25 +23,38 @@ class JsonCB : public QObject
Q_OBJECT
public:
JsonCB(Hyperion* hyperion, QObject* parent);
JsonCB(QObject* parent);
///
/// @brief Subscribe to future data updates given by cmd
/// @param cmd The cmd which will be subscribed for
/// @param cmd The cmd which will be subscribed for
/// @param unsubscribe Revert subscription
/// @return True on success, false if not found
///
bool subscribeFor(const QString& cmd);
bool subscribeFor(const QString& cmd, const bool & unsubscribe = false);
///
/// @brief Get all possible commands to subscribe for
/// @return The list of commands
///
QStringList getCommands() { return _availableCommands; };
///
/// @brief Get all subscribed commands
/// @return The list of commands
///
QStringList getSubscribedCommands() { return _subscribedCommands; };
///
/// @brief Reset subscriptions, disconnect all signals
///
void resetSubscriptions(void);
///
/// @brief Re-apply all current subs to a new Hyperion instance, the connections to the old instance will be dropped
///
void setSubscriptionsTo(Hyperion* hyperion);
signals:
///
/// @brief Emits whenever a new json mesage callback is ready to send

View File

@@ -16,9 +16,14 @@ class AuthTable : public DBManager
public:
/// construct wrapper with auth table
AuthTable(QObject* parent = nullptr)
AuthTable(const QString& rootPath = "", QObject* parent = nullptr)
: DBManager(parent)
{
if(!rootPath.isEmpty()){
// Init Hyperion database usage
setRootPath(rootPath);
setDatabaseName("hyperion");
}
// init Auth table
setTable("auth");
// create table columns
@@ -75,6 +80,82 @@ public:
return false;
}
///
/// @brief Test if a user token is authorized for access.
/// @param usr The user name
/// @param token The token
/// @return True on success else false
///
inline bool isUserTokenAuthorized(const QString& usr, const QString& token)
{
if(getUserToken(usr) == token.toUtf8())
{
updateUserUsed(usr);
return true;
}
return false;
}
///
/// @brief Update token of a user. It's an alternate login path which is replaced on startup. This token is NOT hashed(!)
/// @param user The user name
/// @return True on success else false
///
inline bool setUserToken(const QString& user)
{
QVariantMap map;
map["token"] = QCryptographicHash::hash(QUuid::createUuid().toByteArray(), QCryptographicHash::Sha512).toHex();
VectorPair cond;
cond.append(CPair("user", user));
return updateRecord(cond, map);
}
///
/// @brief Get token of a user. This token is NOT hashed(!)
/// @param user The user name
/// @return The token
///
inline const QByteArray getUserToken(const QString& user)
{
QVariantMap results;
VectorPair cond;
cond.append(CPair("user", user));
getRecord(cond, results, QStringList()<<"token");
return results["token"].toByteArray();
}
///
/// @brief update password of given user. The user should be tested (isUserAuthorized) to verify this change
/// @param user The user name
/// @param newPw The new password to set
/// @return True on success else false
///
inline bool updateUserPassword(const QString& user, const QString& newPw)
{
QVariantMap map;
map["password"] = calcPasswordHashOfUser(user, newPw);
VectorPair cond;
cond.append(CPair("user", user));
return updateRecord(cond, map);
}
///
/// @brief Reset password of Hyperion user !DANGER! Used in Hyperion main.cpp
/// @return True on success else false
///
inline bool resetHyperionUser()
{
QVariantMap map;
map["password"] = calcPasswordHashOfUser("Hyperion", "hyperion");
VectorPair cond;
cond.append(CPair("user", "Hyperion"));
return updateRecord(cond, map);
}
///
/// @brief Update 'last_use' column entry for the corresponding user
/// @param[in] user The user to search for

View File

@@ -47,13 +47,38 @@ public:
/// @brief Check authorization is required according to the user setting
/// @return True if authorization required else false
///
bool & isAuthRequired();
const bool & isAuthRequired() { return _authRequired; };
///
/// @brief Check if authorization is required for local network connections
/// @return True if authorization required else false
///
bool & isLocalAuthRequired();
const bool & isLocalAuthRequired() { return _localAuthRequired; };
///
/// @brief Check if authorization is required for local network connections for admin access
/// @return True if authorization required else false
///
const bool & isLocalAdminAuthRequired() { return _localAdminAuthRequired; };
///
/// @brief Check if Hyperion user has default password
/// @return True if so, else false
///
const bool hasHyperionDefaultPw() { return isUserAuthorized("Hyperion","hyperion"); };
///
/// @brief Get the current valid token for user. Make sure this call is allowed!
/// @param For the defined user
/// @return The token
///
const QString getUserToken(const QString & usr = "Hyperion");
///
/// @brief Reset Hyperion user
/// @return True on success else false
///
bool resetHyperionUser();
///
/// @brief Create a new token and skip the usual chain
@@ -77,6 +102,35 @@ public:
///
bool isTokenAuthorized(const QString& token);
///
/// @brief Check if token is authorized
/// @param usr The username
/// @param token The token
/// @return True if authorized else false
///
bool isUserTokenAuthorized(const QString& usr, const QString& token);
///
/// @brief Check if user auth is temporary blocked due to failed attempts
/// @return True on blocked and no further Auth requests will be accepted
///
bool isUserAuthBlocked(){ return (_userAuthAttempts.length() >= 10); };
///
/// @brief Check if token auth is temporary blocked due to failed attempts
/// @return True on blocked and no further Auth requests will be accepted
///
bool isTokenAuthBlocked(){ return (_tokenAuthAttempts.length() >= 25); };
///
/// @brief Change password of user
/// @param user The username
/// @param pw The CURRENT password
/// @param newPw The new password
/// @return True on success else false
///
bool updateUserPassword(const QString& user, const QString& pw, const QString& newPw);
///
/// @brief Generate a new pending token request with the provided comment and id as identifier helper
/// @param caller The QObject of the caller to deliver the reply
@@ -144,6 +198,12 @@ signals:
void tokenResponse(const bool& success, QObject* caller, const QString& token, const QString& comment, const QString& id);
private:
///
/// @brief Increment counter for token/user auth
/// @param user If true we increment USER auth instead of token
///
void setAuthBlock(const bool& user = false);
/// Database interface for auth table
AuthTable* _authTable;
@@ -162,12 +222,29 @@ private:
/// Reflect state of local auth
bool _localAuthRequired;
/// Reflect state of local admin auth
bool _localAdminAuthRequired;
/// Timer for counting against pendingRequest timeouts
QTimer* _timer;
// Timer which cleans up the block counter
QTimer* _authBlockTimer;
// Contains timestamps of failed user login attempts
QVector<uint64_t> _userAuthAttempts;
// Contains timestamps of failed token login attempts
QVector<uint64_t> _tokenAuthAttempts;
private slots:
///
/// @brief Check timeout of pending requests
///
void checkTimeout();
///
/// @brief Check if there are timeouts for failed login attempts
///
void checkAuthBlockTimeout();
};