Push progress

TODO: rework RESET, probably to main.cpp again
This commit is contained in:
brindosch 2019-08-23 18:37:04 +02:00
parent 18d6345968
commit 5f48a007be
12 changed files with 193 additions and 50 deletions

View File

@ -167,7 +167,7 @@ function sendToHyperion(command, subcommand, msg)
function requestAuthorization() function requestAuthorization()
{ {
sendToHyperion("authorize","login",'"username": "Hyperion", "password": "hyperion"'); sendToHyperion("authorize","login",'"password": "hyperion"');
} }
function requestToken(comment) function requestToken(comment)

View File

@ -306,13 +306,21 @@
"v4lPriority" : 240 "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" : "network" :
{ {
"internetAccessAPI" : false, "internetAccessAPI" : false,
"restirctedInternetAccessAPI" : false, "restirctedInternetAccessAPI" : false,
"ipWhitelist" : [], "ipWhitelist" : [],
"apiAuth" : true, "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. /// Recreate and save led layouts made with web config. These values are just helpers for ui, not for Hyperion.

View File

@ -181,7 +181,8 @@
"restirctedInternetAccessAPI" : false, "restirctedInternetAccessAPI" : false,
"ipWhitelist" : [], "ipWhitelist" : [],
"apiAuth" : true, "apiAuth" : true,
"localApiAuth" : false "localApiAuth" : false,
"localAdminAuth": false
}, },
"ledConfig" : "ledConfig" :

View File

@ -75,6 +75,36 @@ public:
return false; 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 /// @brief Update 'last_use' column entry for the corresponding user
/// @param[in] user The user to search for /// @param[in] user The user to search for

View File

@ -47,13 +47,25 @@ public:
/// @brief Check authorization is required according to the user setting /// @brief Check authorization is required according to the user setting
/// @return True if authorization required else false /// @return True if authorization required else false
/// ///
bool & isAuthRequired(); const bool & isAuthRequired() { return _authRequired; };
/// ///
/// @brief Check if authorization is required for local network connections /// @brief Check if authorization is required for local network connections
/// @return True if authorization required else false /// @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 /// @brief Create a new token and skip the usual chain
@ -77,6 +89,15 @@ public:
/// ///
bool isTokenAuthorized(const QString& token); 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 /// @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 /// @param caller The QObject of the caller to deliver the reply
@ -162,6 +183,9 @@ private:
/// Reflect state of local auth /// Reflect state of local auth
bool _localAuthRequired; bool _localAuthRequired;
/// Reflect state of local admin auth
bool _localAdminAuthRequired;
/// Timer for counting against pendingRequest timeouts /// Timer for counting against pendingRequest timeouts
QTimer* _timer; QTimer* _timer;

View File

@ -10,19 +10,19 @@
"subcommand" : { "subcommand" : {
"type" : "string", "type" : "string",
"required" : true, "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" : { "tan" : {
"type" : "integer" "type" : "integer"
}, },
"username": {
"type": "string",
"minLength" : 3
},
"password": { "password": {
"type": "string", "type": "string",
"minLength" : 8 "minLength" : 8
}, },
"newPassword": {
"type": "string",
"minLength" : 8
},
"token": { "token": {
"type": "string", "type": "string",
"minLength" : 36 "minLength" : 36

View File

@ -62,6 +62,14 @@ JsonAPI::JsonAPI(QString peerAddress, Logger* log, const bool& localConnection,
if(_apiAuthRequired && localConnection) if(_apiAuthRequired && localConnection)
_authorized = !_authManager->isLocalAuthRequired(); _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 // setup auth interface
connect(_authManager, &AuthManager::newPendingTokenRequest, this, &JsonAPI::handlePendingTokenRequest); connect(_authManager, &AuthManager::newPendingTokenRequest, this, &JsonAPI::handlePendingTokenRequest);
connect(_authManager, &AuthManager::tokenResponse, this, &JsonAPI::handleTokenResponse); connect(_authManager, &AuthManager::tokenResponse, this, &JsonAPI::handleTokenResponse);
@ -729,7 +737,7 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString&
for(const auto & entry : subsArr) for(const auto & entry : subsArr)
{ {
// config callbacks just if auth is set // config callbacks just if auth is set
if(entry == "settings-update" && !_authorized) if(entry == "settings-update" && !_userAuthorized)
continue; continue;
if(!_jsonCB->subscribeFor(entry.toString())) if(!_jsonCB->subscribeFor(entry.toString()))
@ -889,17 +897,28 @@ void JsonAPI::handleConfigCommand(const QJsonObject& message, const QString& com
} }
else if (subcommand == "setconfig") 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") 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") else if (subcommand == "reload")
{ {
_hyperion->freeObjects(true); if(_userAuthorized)
Process::restartHyperion(); {
sendErrorReply("failed to restart hyperion", full_command, tan); _hyperion->freeObjects(true);
Process::restartHyperion();
sendErrorReply("failed to restart hyperion", full_command, tan);
}
else
sendErrorReply("No Authorization",command, tan);
} }
else 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) void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString &command, const int tan)
{ {
const QString& subc = message["subcommand"].toString().trimmed(); 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 // catch test if auth is required
if(subc == "required") if(subc == "required")
{ {
QJsonObject req; 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); sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan);
return; return;
} }
@ -1119,6 +1160,24 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString
return; 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 // token created from ui
if(subc == "createToken") if(subc == "createToken")
{ {
@ -1142,11 +1201,11 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString
// delete token // delete token
if(subc == "deleteToken") if(subc == "deleteToken")
{ {
const QString& did = message["id"].toString().trimmed(); // use id
// for user authorized sessions // for user authorized sessions
if(_userAuthorized) if(_userAuthorized)
{ {
_authManager->deleteToken(did); _authManager->deleteToken(id);
sendSuccessReply(command+"-"+subc, tan); sendSuccessReply(command+"-"+subc, tan);
return; return;
} }
@ -1157,8 +1216,8 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString
// catch token request // catch token request
if(subc == "requestToken") if(subc == "requestToken")
{ {
// use id
const QString& comment = message["comment"].toString().trimmed(); const QString& comment = message["comment"].toString().trimmed();
const QString& id = message["id"].toString().trimmed();
_authManager->setNewTokenRequest(this, comment, id); _authManager->setNewTokenRequest(this, comment, id);
// client should wait for answer // client should wait for answer
return; return;
@ -1190,7 +1249,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString
// accept/deny token request // accept/deny token request
if(subc == "answerRequest") if(subc == "answerRequest")
{ {
const QString& id = message["id"].toString().trimmed(); // use id
const bool& accept = message["accept"].toBool(false); const bool& accept = message["accept"].toBool(false);
if(_userAuthorized) if(_userAuthorized)
{ {
@ -1207,7 +1266,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString
// deny token request // deny token request
if(subc == "acceptRequest") if(subc == "acceptRequest")
{ {
const QString& id = message["id"].toString().trimmed(); // use id
if(_userAuthorized) if(_userAuthorized)
{ {
_authManager->acceptTokenRequest(id); _authManager->acceptTokenRequest(id);
@ -1218,7 +1277,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString
return; return;
} }
// cath get token list // catch get token list
if(subc == "getTokenList") if(subc == "getTokenList")
{ {
if(_userAuthorized) if(_userAuthorized)
@ -1266,13 +1325,12 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString
return; return;
} }
// user & password // password
const QString& user = message["username"].toString().trimmed(); // use password
const QString& password = message["password"].toString().trimmed();
if(user.count() >= 3 && password.count() >= 8) if(password.count() >= 8)
{ {
if(_authManager->isUserAuthorized(user, password)) if(_authManager->isUserAuthorized("Hyperion", password))
{ {
_authorized = true; _authorized = true;
_userAuthorized = true; _userAuthorized = true;
@ -1282,7 +1340,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString
sendErrorReply("No Authorization", command+"-"+subc, tan); sendErrorReply("No Authorization", command+"-"+subc, tan);
} }
else else
sendErrorReply("User or password string too short", command+"-"+subc, tan); sendErrorReply("Password string too short", command+"-"+subc, tan);
} }
} }

View File

@ -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 AuthManager::AuthDefinition AuthManager::createToken(const QString& comment)
{ {
const QString token = QUuid::createUuid().toString().mid(1, 36); const QString token = QUuid::createUuid().toString().mid(1, 36);
@ -87,6 +77,19 @@ bool AuthManager::isTokenAuthorized(const QString& token)
return _authTable->tokenExist(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) void AuthManager::setNewTokenRequest(QObject* caller, const QString& comment, const QString& id)
{ {
if(!_pendingRequests.contains(id)) if(!_pendingRequests.contains(id))
@ -144,6 +147,7 @@ void AuthManager::handleSettingsUpdate(const settings::type& type, const QJsonDo
const QJsonObject& obj = config.object(); const QJsonObject& obj = config.object();
_authRequired = obj["apiAuth"].toBool(true); _authRequired = obj["apiAuth"].toBool(true);
_localAuthRequired = obj["localApiAuth"].toBool(false); _localAuthRequired = obj["localApiAuth"].toBool(false);
_localAdminAuthRequired = obj["localAdminAuth"].toBool(false);
} }
} }

View File

@ -66,6 +66,14 @@
} }
}, },
"propertyOrder" : 5 "propertyOrder" : 5
},
"localAdminAuth" :
{
"type" : "boolean",
"title" : "edt_conf_net_localAdminAuth_title",
"required" : true,
"default" : false,
"propertyOrder" : 5
} }
}, },
"additionalProperties" : false "additionalProperties" : false

View File

@ -55,7 +55,7 @@
HyperionDaemon* HyperionDaemon::daemon = nullptr; 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) : QObject(parent)
, _log(Logger::getInstance("DAEMON")) , _log(Logger::getInstance("DAEMON"))
, _instanceManager(new HyperionIManager(rootPath, this)) , _instanceManager(new HyperionIManager(rootPath, this))
@ -93,6 +93,15 @@ HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bo
if(!logLvlOverwrite) if(!logLvlOverwrite)
handleSettingsUpdate(settings::LOGGER, getSetting(settings::LOGGER)); 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 // init EffectFileHandler
EffectFileHandler* efh = new EffectFileHandler(rootPath, getSetting(settings::EFFECTS), this); EffectFileHandler* efh = new EffectFileHandler(rootPath, getSetting(settings::EFFECTS), this);
connect(this, &HyperionDaemon::settingsChanged, efh, &EffectFileHandler::handleSettingsUpdate); connect(this, &HyperionDaemon::settingsChanged, efh, &EffectFileHandler::handleSettingsUpdate);

View File

@ -72,7 +72,7 @@ class HyperionDaemon : public QObject
friend SysTray; friend SysTray;
public: public:
HyperionDaemon(QString rootPath, QObject *parent, const bool& logLvlOverwrite ); HyperionDaemon(QString rootPath, QObject *parent, const bool& logLvlOverwrite, const bool& resetPassword);
~HyperionDaemon(); ~HyperionDaemon();
/// ///

View File

@ -239,14 +239,15 @@ int main(int argc, char** argv)
Parser parser("Hyperion Daemon"); Parser parser("Hyperion Daemon");
parser.addHelpOption(); parser.addHelpOption();
BooleanOption & versionOption = parser.add<BooleanOption>(0x0, "version", "Show version information"); BooleanOption & versionOption = parser.add<BooleanOption> (0x0, "version", "Show version information");
Option & userDataOption = parser.add<Option> (0x0, "userdata", "Overwrite user data path, defaults to home directory of current user (%1)", QDir::homePath() + "/.hyperion"); Option & userDataOption = parser.add<Option> (0x0, "userdata", "Overwrite user data path, defaults to home directory of current user (%1)", QDir::homePath() + "/.hyperion");
BooleanOption & silentOption = parser.add<BooleanOption>('s', "silent", "do not print any outputs"); BooleanOption & resetPassword = parser.add<BooleanOption> (0x0, "resetPassword", "Lost your password? Reset it with this option back to 'hyperion'");
BooleanOption & verboseOption = parser.add<BooleanOption>('v', "verbose", "Increase verbosity"); BooleanOption & silentOption = parser.add<BooleanOption> ('s', "silent", "do not print any outputs");
BooleanOption & debugOption = parser.add<BooleanOption>('d', "debug", "Show debug messages"); BooleanOption & verboseOption = parser.add<BooleanOption> ('v', "verbose", "Increase verbosity");
parser.add<BooleanOption>(0x0, "desktop", "show systray on desktop"); BooleanOption & debugOption = parser.add<BooleanOption> ('d', "debug", "Show debug messages");
parser.add<BooleanOption>(0x0, "service", "force hyperion to start as console service"); parser.add<BooleanOption> (0x0, "desktop", "show systray on desktop");
Option & exportEfxOption = parser.add<Option> (0x0, "export-effects", "export effects to given path"); parser.add<BooleanOption> (0x0, "service", "force hyperion to start as console service");
Option & exportEfxOption = parser.add<Option> (0x0, "export-effects", "export effects to given path");
parser.process(*qApp); parser.process(*qApp);
@ -337,7 +338,7 @@ int main(int argc, char** argv)
HyperionDaemon* hyperiond = nullptr; HyperionDaemon* hyperiond = nullptr;
try try
{ {
hyperiond = new HyperionDaemon(userDataPath, qApp, bool(logLevelCheck)); hyperiond = new HyperionDaemon(userDataPath, qApp, bool(logLevelCheck), parser.isSet(resetPassword));
} }
catch (std::exception& e) catch (std::exception& e)
{ {