diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js index c13115cd..e5099942 100644 --- a/assets/webconfig/js/hyperion.js +++ b/assets/webconfig/js/hyperion.js @@ -167,7 +167,7 @@ function sendToHyperion(command, subcommand, msg) function requestAuthorization() { - sendToHyperion("authorize","login",'"username": "Hyperion", "password": "hyperion"'); + sendToHyperion("authorize","login",'"password": "hyperion"'); } function requestToken(comment) diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented index bc94c3fa..aa420683 100644 --- a/config/hyperion.config.json.commented +++ b/config/hyperion.config.json.commented @@ -306,13 +306,21 @@ "v4lPriority" : 240 }, + /// The configuration of the network security restrictions, contains the following items: + /// * internetAccessAPI : When true allows connection from internet to the API. When false it blocks all outside connections + /// * restirctedInternetAccessAPI : webui voodoo only - ignore it + /// * ipWhitelist : Whitelist ip addresses from the internet to allow access to the API + /// * apiAuth : When true the API requires authentication through tokens to use the API. Read also "localApiAuth" + /// * localApiAuth : When false connections from the local network don't require an API authentification. + /// * localAdminApiAuth : When false connections from the local network don't require an authentification for administration access. "network" : { "internetAccessAPI" : false, "restirctedInternetAccessAPI" : false, "ipWhitelist" : [], "apiAuth" : true, - "localApiAuth" : false + "localApiAuth" : false, + "localAdminAuth": false }, /// Recreate and save led layouts made with web config. These values are just helpers for ui, not for Hyperion. diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index ddc1fdde..862e95d2 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -181,7 +181,8 @@ "restirctedInternetAccessAPI" : false, "ipWhitelist" : [], "apiAuth" : true, - "localApiAuth" : false + "localApiAuth" : false, + "localAdminAuth": false }, "ledConfig" : diff --git a/include/db/AuthTable.h b/include/db/AuthTable.h index d3a99a88..76b832f6 100644 --- a/include/db/AuthTable.h +++ b/include/db/AuthTable.h @@ -75,6 +75,36 @@ public: return false; } + /// + /// @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 diff --git a/include/hyperion/AuthManager.h b/include/hyperion/AuthManager.h index 6b345d40..9349a8b0 100644 --- a/include/hyperion/AuthManager.h +++ b/include/hyperion/AuthManager.h @@ -47,13 +47,25 @@ 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 Reset Hyperion user + /// @return True on success else false + /// + bool resetHyperionUser(); /// /// @brief Create a new token and skip the usual chain @@ -77,6 +89,15 @@ public: /// bool isTokenAuthorized(const QString& token); + /// + /// @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 @@ -162,6 +183,9 @@ private: /// Reflect state of local auth bool _localAuthRequired; + /// Reflect state of local admin auth + bool _localAdminAuthRequired; + /// Timer for counting against pendingRequest timeouts QTimer* _timer; diff --git a/libsrc/api/JSONRPC_schema/schema-authorize.json b/libsrc/api/JSONRPC_schema/schema-authorize.json index 264ef991..200bc541 100644 --- a/libsrc/api/JSONRPC_schema/schema-authorize.json +++ b/libsrc/api/JSONRPC_schema/schema-authorize.json @@ -10,19 +10,19 @@ "subcommand" : { "type" : "string", "required" : true, - "enum" : ["requestToken","createToken","deleteToken","getTokenList","logout","login","required","answerRequest","getPendingRequests"] + "enum" : ["requestToken","createToken","deleteToken","getTokenList","logout","login","required","adminRequired","newPasswordRequired","newPassword","answerRequest","getPendingRequests"] }, "tan" : { "type" : "integer" }, - "username": { - "type": "string", - "minLength" : 3 - }, "password": { "type": "string", "minLength" : 8 }, + "newPassword": { + "type": "string", + "minLength" : 8 + }, "token": { "type": "string", "minLength" : 36 diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp index 17e43859..fb4091b8 100644 --- a/libsrc/api/JsonAPI.cpp +++ b/libsrc/api/JsonAPI.cpp @@ -62,6 +62,14 @@ JsonAPI::JsonAPI(QString peerAddress, Logger* log, const bool& localConnection, if(_apiAuthRequired && localConnection) _authorized = !_authManager->isLocalAuthRequired(); + // admin access is allowed, when the connection is local and the option for local admin isn't set. Con: All local connections get full access + // authorization is also granted for api based on admin result. Pro: Admin should have full access. + if(localConnection) + { + _userAuthorized = !_authManager->isLocalAdminAuthRequired(); + _authorized = _userAuthorized; + } + // setup auth interface connect(_authManager, &AuthManager::newPendingTokenRequest, this, &JsonAPI::handlePendingTokenRequest); connect(_authManager, &AuthManager::tokenResponse, this, &JsonAPI::handleTokenResponse); @@ -729,7 +737,7 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& for(const auto & entry : subsArr) { // config callbacks just if auth is set - if(entry == "settings-update" && !_authorized) + if(entry == "settings-update" && !_userAuthorized) continue; if(!_jsonCB->subscribeFor(entry.toString())) @@ -889,17 +897,28 @@ void JsonAPI::handleConfigCommand(const QJsonObject& message, const QString& com } else if (subcommand == "setconfig") { - handleConfigSetCommand(message, full_command, tan); + if(_userAuthorized) + handleConfigSetCommand(message, full_command, tan); + else + sendErrorReply("No Authorization",command, tan); } else if (subcommand == "getconfig") { - sendSuccessDataReply(QJsonDocument(_hyperion->getQJsonConfig()), full_command, tan); + if(_userAuthorized) + sendSuccessDataReply(QJsonDocument(_hyperion->getQJsonConfig()), full_command, tan); + else + sendErrorReply("No Authorization",command, tan); } else if (subcommand == "reload") { - _hyperion->freeObjects(true); - Process::restartHyperion(); - sendErrorReply("failed to restart hyperion", full_command, tan); + if(_userAuthorized) + { + _hyperion->freeObjects(true); + Process::restartHyperion(); + sendErrorReply("failed to restart hyperion", full_command, tan); + } + else + sendErrorReply("No Authorization",command, tan); } else { @@ -1101,11 +1120,33 @@ void JsonAPI::handleVideoModeCommand(const QJsonObject& message, const QString & void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString &command, const int tan) { const QString& subc = message["subcommand"].toString().trimmed(); + const QString& id = message["id"].toString().trimmed(); + const QString& password = message["password"].toString().trimmed(); + const QString& newPassword = message["newPassword"].toString().trimmed(); + // catch test if auth is required if(subc == "required") { QJsonObject req; - req["required"] = _apiAuthRequired; + req["required"] = !_authorized; + sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan); + return; + } + + // catch test if admin auth is required + if(subc == "adminRequired") + { + QJsonObject req; + req["adminRequired"] = !_userAuthorized; + sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan); + return; + } + + // default hyperion password is a security risk, replace it asap + if(subc == "newPasswordRequired") + { + QJsonObject req; + req["newPasswordRequired"] = _authManager->isUserAuthorized("Hyperion", "hyperion"); sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan); return; } @@ -1119,6 +1160,24 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString return; } + // change password + if(subc == "newPassword") + { + // use password, newPassword + if(_userAuthorized) + { + if(_authManager->updateUserPassword("Hyperion", password, newPassword)) + { + sendSuccessReply(command+"-"+subc, tan); + return; + } + sendErrorReply("Failed to update user password",command+"-"+subc, tan); + return; + } + sendErrorReply("No Authorization",command+"-"+subc, tan); + return; + } + // token created from ui if(subc == "createToken") { @@ -1142,11 +1201,11 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString // delete token if(subc == "deleteToken") { - const QString& did = message["id"].toString().trimmed(); + // use id // for user authorized sessions if(_userAuthorized) { - _authManager->deleteToken(did); + _authManager->deleteToken(id); sendSuccessReply(command+"-"+subc, tan); return; } @@ -1157,8 +1216,8 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString // catch token request if(subc == "requestToken") { + // use id const QString& comment = message["comment"].toString().trimmed(); - const QString& id = message["id"].toString().trimmed(); _authManager->setNewTokenRequest(this, comment, id); // client should wait for answer return; @@ -1190,7 +1249,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString // accept/deny token request if(subc == "answerRequest") { - const QString& id = message["id"].toString().trimmed(); + // use id const bool& accept = message["accept"].toBool(false); if(_userAuthorized) { @@ -1207,7 +1266,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString // deny token request if(subc == "acceptRequest") { - const QString& id = message["id"].toString().trimmed(); + // use id if(_userAuthorized) { _authManager->acceptTokenRequest(id); @@ -1218,7 +1277,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString return; } - // cath get token list + // catch get token list if(subc == "getTokenList") { if(_userAuthorized) @@ -1266,13 +1325,12 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString return; } - // user & password - const QString& user = message["username"].toString().trimmed(); - const QString& password = message["password"].toString().trimmed(); + // password + // use password - if(user.count() >= 3 && password.count() >= 8) + if(password.count() >= 8) { - if(_authManager->isUserAuthorized(user, password)) + if(_authManager->isUserAuthorized("Hyperion", password)) { _authorized = true; _userAuthorized = true; @@ -1282,7 +1340,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString sendErrorReply("No Authorization", command+"-"+subc, tan); } else - sendErrorReply("User or password string too short", command+"-"+subc, tan); + sendErrorReply("Password string too short", command+"-"+subc, tan); } } diff --git a/libsrc/hyperion/AuthManager.cpp b/libsrc/hyperion/AuthManager.cpp index 9119e264..0f79724e 100644 --- a/libsrc/hyperion/AuthManager.cpp +++ b/libsrc/hyperion/AuthManager.cpp @@ -34,16 +34,6 @@ AuthManager::AuthManager(QObject* parent) } } -bool & AuthManager::isAuthRequired() -{ - return _authRequired; -} - -bool & AuthManager::isLocalAuthRequired() -{ - return _localAuthRequired; -} - const AuthManager::AuthDefinition AuthManager::createToken(const QString& comment) { const QString token = QUuid::createUuid().toString().mid(1, 36); @@ -87,6 +77,19 @@ bool AuthManager::isTokenAuthorized(const QString& token) return _authTable->tokenExist(token); } +bool AuthManager::updateUserPassword(const QString& user, const QString& pw, const QString& newPw) +{ + if(isUserAuthorized(user, pw)) + return _authTable->updateUserPassword(user, newPw); + + return false; +} + +bool AuthManager::resetHyperionUser() +{ + return _authTable->resetHyperionUser(); +} + void AuthManager::setNewTokenRequest(QObject* caller, const QString& comment, const QString& id) { if(!_pendingRequests.contains(id)) @@ -144,6 +147,7 @@ void AuthManager::handleSettingsUpdate(const settings::type& type, const QJsonDo const QJsonObject& obj = config.object(); _authRequired = obj["apiAuth"].toBool(true); _localAuthRequired = obj["localApiAuth"].toBool(false); + _localAdminAuthRequired = obj["localAdminAuth"].toBool(false); } } diff --git a/libsrc/hyperion/schema/schema-network.json b/libsrc/hyperion/schema/schema-network.json index 922ba0e5..686fdf99 100644 --- a/libsrc/hyperion/schema/schema-network.json +++ b/libsrc/hyperion/schema/schema-network.json @@ -66,6 +66,14 @@ } }, "propertyOrder" : 5 + }, + "localAdminAuth" : + { + "type" : "boolean", + "title" : "edt_conf_net_localAdminAuth_title", + "required" : true, + "default" : false, + "propertyOrder" : 5 } }, "additionalProperties" : false diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 8fde0c5c..fa9a67ad 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -55,7 +55,7 @@ HyperionDaemon* HyperionDaemon::daemon = nullptr; -HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bool& logLvlOverwrite) +HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bool& logLvlOverwrite, const bool& resetPassword) : QObject(parent) , _log(Logger::getInstance("DAEMON")) , _instanceManager(new HyperionIManager(rootPath, this)) @@ -93,6 +93,15 @@ HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bo if(!logLvlOverwrite) handleSettingsUpdate(settings::LOGGER, getSetting(settings::LOGGER)); + // reset password if requested from cmd + if(resetPassword){ + if(_authManager->resetHyperionUser()){ + Info(_log, "Password successfully resetted") + } else { + Error(_log, "Failed to reset password") + } + } + // init EffectFileHandler EffectFileHandler* efh = new EffectFileHandler(rootPath, getSetting(settings::EFFECTS), this); connect(this, &HyperionDaemon::settingsChanged, efh, &EffectFileHandler::handleSettingsUpdate); diff --git a/src/hyperiond/hyperiond.h b/src/hyperiond/hyperiond.h index 67f6fcd7..2edc3789 100644 --- a/src/hyperiond/hyperiond.h +++ b/src/hyperiond/hyperiond.h @@ -72,7 +72,7 @@ class HyperionDaemon : public QObject friend SysTray; public: - HyperionDaemon(QString rootPath, QObject *parent, const bool& logLvlOverwrite ); + HyperionDaemon(QString rootPath, QObject *parent, const bool& logLvlOverwrite, const bool& resetPassword); ~HyperionDaemon(); /// diff --git a/src/hyperiond/main.cpp b/src/hyperiond/main.cpp index 2d266d22..7106a3af 100644 --- a/src/hyperiond/main.cpp +++ b/src/hyperiond/main.cpp @@ -239,14 +239,15 @@ int main(int argc, char** argv) Parser parser("Hyperion Daemon"); parser.addHelpOption(); - BooleanOption & versionOption = parser.add(0x0, "version", "Show version information"); - Option & userDataOption = parser.add