diff --git a/assets/webconfig/js/content_index.js b/assets/webconfig/js/content_index.js
index 25015ea2..586064f4 100644
--- a/assets/webconfig/js/content_index.js
+++ b/assets/webconfig/js/content_index.js
@@ -72,23 +72,88 @@ $(document).ready( function() {
window.showOptHelp = window.serverConfig.general.showOptHelp;
});
-
+
$(window.hyperion).on("cmd-config-setconfig", function(event) {
if (event.response.success === true) {
- $('#hyperion_config_write_success_notify').fadeIn().delay(5000).fadeOut();
+ showNotification('success', $.i18n('dashboard_alert_message_confsave_success'), $.i18n('dashboard_alert_message_confsave_success_t'))
}
});
$(window.hyperion).one("cmd-authorize-login", function(event) {
+ $("#main-nav").removeAttr('style')
+ $("#top-navbar").removeAttr('style')
+
+ if(window.defaultPasswordIsSet === true)
+ showNotification('warning', $.i18n('dashboard_message_default_password'), $.i18n('dashboard_message_default_password_t'), '
'+$.i18n('InfoDialog_changePassword_title')+'')
+ else
+ //if logged on and pw != default show option to lock ui
+ $("#btn_lock_ui").removeAttr('style')
+
+
+ if (event.response.hasOwnProperty('info'))
+ setStorage("loginToken", event.response.info.token, true);
+
requestServerConfigSchema();
});
+ $(window.hyperion).on("cmd-authorize-newPassword", function(event) {
+ if (event.response.success === true){
+ showInfoDialog("success",$.i18n('InfoDialog_changePassword_success'));
+ // not necessarily true, but better than nothing
+ window.defaultPasswordIsSet = false;
+ }
+ });
+
+ $(window.hyperion).one("cmd-authorize-newPasswordRequired", function(event) {
+ var loginToken = getStorage("loginToken", true)
+
+ if (event.response.info.newPasswordRequired == true)
+ {
+ window.defaultPasswordIsSet = true;
+
+ if(loginToken)
+ requestTokenAuthorization(loginToken)
+ else
+ requestAuthorization('hyperion');
+ }
+ else
+ {
+ $("#main-nav").attr('style', 'display:none')
+ $("#top-navbar").attr('style', 'display:none')
+
+ if(loginToken)
+ requestTokenAuthorization(loginToken)
+ else
+ loadContentTo("#page-content", "login")
+
+ }
+ });
+
+ $(window.hyperion).one("cmd-authorize-adminRequired", function(event) {
+ //Check if a admin login is required.
+ //If yes: check if default pw is set. If no: go ahead to get server config and render page
+ if (event.response.info.adminRequired === true)
+ requestRequiresDefaultPasswortChange();
+ else
+ requestServerConfigSchema();
+ });
+
$(window.hyperion).on("error",function(event){
- showInfoDialog("error","Error", event.reason);
+ //If we are getting an error "No Authorization" back with a set loginToken we will forward to new Login (Token is expired.
+ //e.g.: hyperiond was started new in the meantime)
+ if (event.reason == "No Authorization" && getStorage("loginToken", true))
+ {
+ removeStorage("loginToken", true);
+ requestRequiresAdminAuth();
+ }
+ else
+ {
+ showInfoDialog("error","Error", event.reason);
+ }
});
$(window.hyperion).on("open",function(event){
- requestAuthorization();
+ requestRequiresAdminAuth();
});
$(window.hyperion).one("ready", function(event) {
@@ -190,3 +255,8 @@ $(function(){
$(this).toggleClass('active inactive');
});
});
+
+// hotfix body padding when bs modals overlap
+$(document.body).on('hide.bs.modal,hidden.bs.modal', function () {
+ $('body').css('padding-right','0');
+});
diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js
index c13115cd..6a05d6a5 100644
--- a/assets/webconfig/js/hyperion.js
+++ b/assets/webconfig/js/hyperion.js
@@ -28,6 +28,7 @@ window.wSess = [];
window.currentHyperionInstance = 0;
window.currentHyperionInstanceName = "?";
window.comps = [];
+window.defaultPasswordIsSet = null;
tokenList = {};
function initRestart()
@@ -165,9 +166,30 @@ function sendToHyperion(command, subcommand, msg)
// -----------------------------------------------------------
// wrapped server commands
-function requestAuthorization()
+// Test if admin requires authentication
+function requestRequiresAdminAuth()
{
- sendToHyperion("authorize","login",'"username": "Hyperion", "password": "hyperion"');
+ sendToHyperion("authorize","adminRequired");
+}
+// Test if the default password needs to be changed
+function requestRequiresDefaultPasswortChange()
+{
+ sendToHyperion("authorize","newPasswordRequired");
+}
+// Change password
+function requestChangePassword(oldPw, newPw)
+{
+ sendToHyperion("authorize","newPassword",'"password": "'+oldPw+'", "newPassword":"'+newPw+'"');
+}
+
+function requestAuthorization(password)
+{
+ sendToHyperion("authorize","login",'"password": "' + password + '"');
+}
+
+function requestTokenAuthorization(token)
+{
+ sendToHyperion("authorize","login",'"token": "' + token + '"');
}
function requestToken(comment)
diff --git a/assets/webconfig/js/settings.js b/assets/webconfig/js/settings.js
index 065fd223..76dea1fe 100644
--- a/assets/webconfig/js/settings.js
+++ b/assets/webconfig/js/settings.js
@@ -4,6 +4,26 @@ var availLang = ['en','de','es','it','cs'];
var availAccess = ['default','advanced','expert'];
//$.i18n.debug = true;
+//Change Password
+function changePassword(){
+ showInfoDialog('changePassword', $.i18n('InfoDialog_changePassword_title'));
+
+ // fill default pw if default is set
+ if(window.defaultPasswordIsSet)
+ $('#oldPw').val('hyperion')
+
+ $('#id_btn_ok').off().on('click',function() {
+ var oldPw = $('#oldPw').val();
+ var newPw = $('#newPw').val();
+
+ requestChangePassword(oldPw, newPw)
+ });
+
+ $('#newPw, #oldPw').off().on('input',function(e) {
+ ($('#oldPw').val().length >= 8 && $('#newPw').val().length >= 8) ? $('#id_btn_ok').attr('disabled', false) : $('#id_btn_ok').attr('disabled', true);
+ });
+}
+
$(document).ready( function() {
//i18n
@@ -112,6 +132,17 @@ $(document).ready( function() {
$('#id_select').trigger('change');
});
+ // change pw btn
+ $('#btn_changePassword').off().on('click',function() {
+ changePassword();
+ });
+
+ //Lock Ui
+ $('#btn_lock_ui').off().on('click',function() {
+ removeStorage('loginToken', true);
+ location.replace('/');
+ });
+
//hide menu elements
if (storedAccess != 'expert')
$('#load_webconfig').toggle(false);
diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js
index bb386b11..6e088b79 100644
--- a/assets/webconfig/js/ui_utils.js
+++ b/assets/webconfig/js/ui_utils.js
@@ -297,6 +297,15 @@ function showInfoDialog(type,header,message)
$('#id_footer_rename').html('
');
$('#id_footer_rename').append('
');
}
+ else if (type == "changePassword")
+ {
+ $('#id_body_rename').html('
');
+ $('#id_body_rename').append(''+header+'
');
+ $('#id_body_rename').append('
');
+ $('#id_body_rename').append('');
+ $('#id_footer_rename').html('');
+ $('#id_footer_rename').append('');
+ }
else if (type == "checklist")
{
$('#id_body').html('');
@@ -326,7 +335,7 @@ function showInfoDialog(type,header,message)
$('#id_body').append('');
- $(type == "renInst" ? "#modal_dialog_rename" : "#modal_dialog").modal({
+ $(type == "renInst" || type == "changePassword" ? "#modal_dialog_rename" : "#modal_dialog").modal({
backdrop : "static",
keyboard: false,
show: true
@@ -534,8 +543,9 @@ function hexToRgb(hex) {
@param type Valid types are "info","success","warning","danger"
@param message The message to show
@param title A title (optional)
+ @param addhtml Add custom html to the notification end
*/
-function showNotification(type, message, title="")
+function showNotification(type, message, title="", addhtml="")
{
if(title == "")
{
@@ -564,15 +574,19 @@ function showNotification(type, message, title="")
// settings
type: type,
animate: {
- enter: 'animated fadeInRight',
- exit: 'animated fadeOutRight'
+ enter: 'animated fadeInDown',
+ exit: 'animated fadeOutUp'
+ },
+ placement:{
+ align:'center'
},
mouse_over : 'pause',
- template: '' +
+ template: '
' +
'
' +
'
' +
'
{1}
' +
'
{2}' +
+ addhtml+
'
' +
diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented
index 7e0ad5af..91c14912 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": true
},
/// 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 a71c5d35..8536e2a0 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": true
},
"ledConfig" :
diff --git a/include/api/JsonAPI.h b/include/api/JsonAPI.h
index 08a6f181..a7e2ec1f 100644
--- a/include/api/JsonAPI.h
+++ b/include/api/JsonAPI.h
@@ -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);
};
diff --git a/include/api/JsonCB.h b/include/api/JsonCB.h
index e57a8445..02cb96bc 100644
--- a/include/api/JsonCB.h
+++ b/include/api/JsonCB.h
@@ -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
diff --git a/include/db/AuthTable.h b/include/db/AuthTable.h
index d3a99a88..ddf7f3be 100644
--- a/include/db/AuthTable.h
+++ b/include/db/AuthTable.h
@@ -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
diff --git a/include/hyperion/AuthManager.h b/include/hyperion/AuthManager.h
index 6b345d40..8b12c0e0 100644
--- a/include/hyperion/AuthManager.h
+++ b/include/hyperion/AuthManager.h
@@ -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
_userAuthAttempts;
+
+ // Contains timestamps of failed token login attempts
+ QVector _tokenAuthAttempts;
+
private slots:
///
/// @brief Check timeout of pending requests
///
void checkTimeout();
+
+ ///
+ /// @brief Check if there are timeouts for failed login attempts
+ ///
+ void checkAuthBlockTimeout();
};
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 805fb46e..0c6bbaa1 100644
--- a/libsrc/api/JsonAPI.cpp
+++ b/libsrc/api/JsonAPI.cpp
@@ -48,19 +48,37 @@ JsonAPI::JsonAPI(QString peerAddress, Logger* log, const bool& localConnection,
, _noListener(noListener)
, _peerAddress(peerAddress)
, _log(log)
+ , _localConnection(localConnection)
, _instanceManager(HyperionIManager::getInstance())
, _hyperion(nullptr)
- , _jsonCB(nullptr)
+ , _jsonCB(new JsonCB(this))
, _streaming_logging_activated(false)
, _imageStreamTimer(new QTimer(this))
, _ledStreamTimer(new QTimer(this))
{
Q_INIT_RESOURCE(JSONRPC_schemas);
+}
+void JsonAPI::initialize(void)
+{
+ // For security we block external connections if default PW is set
+ if(!_localConnection && _authManager->hasHyperionDefaultPw())
+ {
+ emit forceClose();
+ }
// if this is localConnection and network allows unauth locals, set authorized flag
- if(_apiAuthRequired && 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
+ if(_localConnection)
+ {
+ _userAuthorized = !_authManager->isLocalAdminAuthRequired();
+ // just in positive direction
+ if(_userAuthorized)
+ _authorized = true;
+ }
+
// setup auth interface
connect(_authManager, &AuthManager::newPendingTokenRequest, this, &JsonAPI::handlePendingTokenRequest);
connect(_authManager, &AuthManager::tokenResponse, this, &JsonAPI::handleTokenResponse);
@@ -68,6 +86,9 @@ JsonAPI::JsonAPI(QString peerAddress, Logger* log, const bool& localConnection,
// listen for killed instances
connect(_instanceManager, &HyperionIManager::instanceStateChanged, this, &JsonAPI::handleInstanceStateChange);
+ // pipe callbacks from subscriptions to parent
+ connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
+
// init Hyperion pointer
handleInstanceSwitch(0);
@@ -91,30 +112,8 @@ bool JsonAPI::handleInstanceSwitch(const quint8& inst, const bool& forced)
// get new Hyperion pointer
_hyperion = _instanceManager->getHyperionInstance(inst);
- // the JsonCB creates json messages you can subscribe to e.g. data change events; forward them to the parent client
- QStringList cbCmds;
- if(_jsonCB != nullptr)
- {
- cbCmds = _jsonCB->getSubscribedCommands();
- delete _jsonCB;
- }
-
- _jsonCB = new JsonCB(_hyperion, this);
- connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
-
- // read subs
- for(const auto & entry : cbCmds)
- {
- _jsonCB->subscribeFor(entry);
- }
-
-// // imageStream last state
-// if(_ledcolorsImageActive)
-// connect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage, Qt::UniqueConnection);
-//
-// //ledColor stream last state
-// if(_ledcolorsLedsActive)
-// connect(_hyperion, &Hyperion::rawLedColors, this, &JsonAPI::streamLedcolorsUpdate, Qt::UniqueConnection);
+ // the JsonCB creates json messages you can subscribe to e.g. data change events
+ _jsonCB->setSubscriptionsTo(_hyperion);
return true;
}
@@ -727,7 +726,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()))
@@ -887,17 +886,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
{
@@ -1139,11 +1149,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->hasHyperionDefaultPw();
sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan);
return;
}
@@ -1153,10 +1185,30 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString
{
_authorized = false;
_userAuthorized = false;
+ // disconnect all kind of data callbacks
+ stopDataConnections();
sendSuccessReply(command+"-"+subc, tan);
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")
{
@@ -1180,11 +1232,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;
}
@@ -1195,8 +1247,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;
@@ -1228,7 +1280,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)
{
@@ -1245,7 +1297,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);
@@ -1256,7 +1308,7 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString
return;
}
- // cath get token list
+ // catch get token list
if(subc == "getTokenList")
{
if(_userAuthorized)
@@ -1283,12 +1335,27 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString
// login
if(subc == "login")
{
- // catch token auth
const QString& token = message["token"].toString().trimmed();
+ // catch token
if(!token.isEmpty())
{
- if(token.count() >= 36)
+ // userToken is longer
+ if(token.count() > 36)
+ {
+ if(_authManager->isUserTokenAuthorized("Hyperion",token))
+ {
+ _authorized = true;
+ _userAuthorized = true;
+ sendSuccessReply(command+"-"+subc, tan);
+ }
+ else
+ sendErrorReply("No Authorization", command+"-"+subc, tan);
+
+ return;
+ }
+ // usual app token is 36
+ if(token.count() == 36)
{
if(_authManager->isTokenAuthorized(token))
{
@@ -1304,23 +1371,25 @@ 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;
- sendSuccessReply(command+"-"+subc, tan);
+ // Return the current valid Hyperion user token
+ QJsonObject obj;
+ obj["token"] = _authManager->getUserToken();
+ sendSuccessDataReply(QJsonDocument(obj),command+"-"+subc, tan);
}
else
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);
}
}
@@ -1343,8 +1412,11 @@ void JsonAPI::handleInstanceCommand(const QJsonObject & message, const QString &
if(subc == "switchTo")
{
- if(handleInstanceSwitch(inst))
- sendSuccessReply(command+"-"+subc, tan);
+ if(handleInstanceSwitch(inst)){
+ QJsonObject obj;
+ obj["instance"] = inst;
+ sendSuccessDataReply(QJsonDocument(obj),command+"-"+subc, tan);
+ }
else
sendErrorReply("Selected Hyperion instance isn't running",command+"-"+subc, tan);
return;
@@ -1576,3 +1648,13 @@ void JsonAPI::handleInstanceStateChange(const instanceState& state, const quint8
break;
}
}
+
+void JsonAPI::stopDataConnections(void)
+{
+ LoggerManager::getInstance()->disconnect();
+ _streaming_logging_activated = false;
+ _jsonCB->resetSubscriptions();
+ _imageStreamTimer->stop();
+ _ledStreamTimer->stop();
+
+}
diff --git a/libsrc/api/JsonCB.cpp b/libsrc/api/JsonCB.cpp
index 4bd8aa43..972a8ef0 100644
--- a/libsrc/api/JsonCB.cpp
+++ b/libsrc/api/JsonCB.cpp
@@ -27,87 +27,138 @@
using namespace hyperion;
-JsonCB::JsonCB(Hyperion* hyperion, QObject* parent)
+JsonCB::JsonCB(QObject* parent)
: QObject(parent)
- , _hyperion(hyperion)
- , _componentRegister(& _hyperion->getComponentRegister())
+ , _hyperion(nullptr)
+ , _componentRegister(nullptr)
, _bonjour(BonjourBrowserWrapper::getInstance())
- , _prioMuxer(_hyperion->getMuxerInstance())
+ , _prioMuxer(nullptr)
{
_availableCommands << "components-update" << "sessions-update" << "priorities-update" << "imageToLedMapping-update"
<< "adjustment-update" << "videomode-update" << "effects-update" << "settings-update" << "leds-update" << "instance-update";
}
-bool JsonCB::subscribeFor(const QString& type)
+bool JsonCB::subscribeFor(const QString& type, const bool & unsubscribe)
{
if(!_availableCommands.contains(type))
return false;
+ if(unsubscribe)
+ _subscribedCommands.removeAll(type);
+ else
+ _subscribedCommands << type;
+
if(type == "components-update")
{
- _subscribedCommands << type;
- connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCB::handleComponentState, Qt::UniqueConnection);
+ if(unsubscribe)
+ disconnect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCB::handleComponentState);
+ else
+ connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCB::handleComponentState, Qt::UniqueConnection);
}
if(type == "sessions-update")
{
- _subscribedCommands << type;
- connect(_bonjour, &BonjourBrowserWrapper::browserChange, this, &JsonCB::handleBonjourChange, Qt::UniqueConnection);
+ if(unsubscribe)
+ disconnect(_bonjour, &BonjourBrowserWrapper::browserChange, this, &JsonCB::handleBonjourChange);
+ else
+ connect(_bonjour, &BonjourBrowserWrapper::browserChange, this, &JsonCB::handleBonjourChange, Qt::UniqueConnection);
}
if(type == "priorities-update")
{
- _subscribedCommands << type;
- connect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection);
- connect(_prioMuxer, &PriorityMuxer::autoSelectChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection);
+ if(unsubscribe){
+ disconnect(_prioMuxer,0 ,0 ,0);
+ } else {
+ connect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection);
+ connect(_prioMuxer, &PriorityMuxer::autoSelectChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection);
+ }
}
if(type == "imageToLedMapping-update")
{
- _subscribedCommands << type;
- connect(_hyperion, &Hyperion::imageToLedsMappingChanged, this, &JsonCB::handleImageToLedsMappingChange, Qt::UniqueConnection);
+ if(unsubscribe)
+ disconnect(_hyperion, &Hyperion::imageToLedsMappingChanged, this, &JsonCB::handleImageToLedsMappingChange);
+ else
+ connect(_hyperion, &Hyperion::imageToLedsMappingChanged, this, &JsonCB::handleImageToLedsMappingChange, Qt::UniqueConnection);
}
if(type == "adjustment-update")
{
- _subscribedCommands << type;
- connect(_hyperion, &Hyperion::adjustmentChanged, this, &JsonCB::handleAdjustmentChange, Qt::UniqueConnection);
+ if(unsubscribe)
+ disconnect(_hyperion, &Hyperion::adjustmentChanged, this, &JsonCB::handleAdjustmentChange);
+ else
+ connect(_hyperion, &Hyperion::adjustmentChanged, this, &JsonCB::handleAdjustmentChange, Qt::UniqueConnection);
}
if(type == "videomode-update")
{
- _subscribedCommands << type;
- connect(_hyperion, &Hyperion::newVideoMode, this, &JsonCB::handleVideoModeChange, Qt::UniqueConnection);
+ if(unsubscribe)
+ disconnect(_hyperion, &Hyperion::newVideoMode, this, &JsonCB::handleVideoModeChange);
+ else
+ connect(_hyperion, &Hyperion::newVideoMode, this, &JsonCB::handleVideoModeChange, Qt::UniqueConnection);
}
if(type == "effects-update")
{
- _subscribedCommands << type;
- connect(_hyperion, &Hyperion::effectListUpdated, this, &JsonCB::handleEffectListChange, Qt::UniqueConnection);
+ if(unsubscribe)
+ disconnect(_hyperion, &Hyperion::effectListUpdated, this, &JsonCB::handleEffectListChange);
+ else
+ connect(_hyperion, &Hyperion::effectListUpdated, this, &JsonCB::handleEffectListChange, Qt::UniqueConnection);
}
if(type == "settings-update")
{
- _subscribedCommands << type;
- connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleSettingsChange, Qt::UniqueConnection);
+ if(unsubscribe)
+ disconnect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleSettingsChange);
+ else
+ connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleSettingsChange, Qt::UniqueConnection);
}
if(type == "leds-update")
{
- _subscribedCommands << type;
- connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleLedsConfigChange, Qt::UniqueConnection);
+ if(unsubscribe)
+ disconnect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleLedsConfigChange);
+ else
+ connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleLedsConfigChange, Qt::UniqueConnection);
}
if(type == "instance-update")
{
- _subscribedCommands << type;
- connect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCB::handleInstanceChange, Qt::UniqueConnection);
+ if(unsubscribe)
+ disconnect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCB::handleInstanceChange);
+ else
+ connect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCB::handleInstanceChange, Qt::UniqueConnection);
}
return true;
}
+void JsonCB::resetSubscriptions(void){
+ for(const auto & entry : getSubscribedCommands()){
+ subscribeFor(entry, true);
+ }
+}
+
+void JsonCB::setSubscriptionsTo(Hyperion* hyperion){
+ // get current subs
+ QStringList currSubs(getSubscribedCommands());
+
+ // stop subs
+ resetSubscriptions();
+
+ // update pointer
+ _hyperion = hyperion;
+ _componentRegister = &_hyperion->getComponentRegister();
+ _prioMuxer = _hyperion->getMuxerInstance();
+
+ // re-apply subs
+ for(const auto & entry : currSubs)
+ {
+ subscribeFor(entry);
+ }
+}
+
void JsonCB::doCallback(const QString& cmd, const QVariant& data)
{
QJsonObject obj;
diff --git a/libsrc/hyperion/AuthManager.cpp b/libsrc/hyperion/AuthManager.cpp
index 9119e264..70bc892d 100644
--- a/libsrc/hyperion/AuthManager.cpp
+++ b/libsrc/hyperion/AuthManager.cpp
@@ -12,14 +12,16 @@ AuthManager* AuthManager::manager = nullptr;
AuthManager::AuthManager(QObject* parent)
: QObject(parent)
- , _authTable(new AuthTable(this))
+ , _authTable(new AuthTable("",this))
, _metaTable(new MetaTable(this))
, _pendingRequests()
, _authRequired(true)
, _timer(new QTimer(this))
+ , _authBlockTimer(new QTimer(this))
{
AuthManager::manager = this;
+
// get uuid
_uuid = _metaTable->getUUID();
@@ -27,21 +29,18 @@ AuthManager::AuthManager(QObject* parent)
_timer->setInterval(1000);
connect(_timer, &QTimer::timeout, this, &AuthManager::checkTimeout);
+ // setup authBlockTimer
+ _authBlockTimer->setInterval(60000);
+ connect(_authBlockTimer, &QTimer::timeout, this, &AuthManager::checkAuthBlockTimeout);
+
// init with default user and password
if(!_authTable->userExist("Hyperion"))
{
_authTable->createUser("Hyperion","hyperion");
}
-}
-bool & AuthManager::isAuthRequired()
-{
- return _authRequired;
-}
-
-bool & AuthManager::isLocalAuthRequired()
-{
- return _localAuthRequired;
+ // update Hyperion user token on startup
+ _authTable->setUserToken("Hyperion");
}
const AuthManager::AuthDefinition AuthManager::createToken(const QString& comment)
@@ -77,14 +76,69 @@ const QVector AuthManager::getTokenList()
return finalVec;
}
+const QString AuthManager::getUserToken(const QString & usr)
+{
+ return QString(_authTable->getUserToken(usr));
+}
+
+void AuthManager::setAuthBlock(const bool& user)
+{
+ // current timestamp +10 minutes
+ if(user)
+ _userAuthAttempts.append(QDateTime::currentMSecsSinceEpoch()+600000);
+ else
+ _tokenAuthAttempts.append(QDateTime::currentMSecsSinceEpoch()+600000);
+
+ QMetaObject::invokeMethod(_authBlockTimer, "start", Qt::QueuedConnection);
+}
+
bool AuthManager::isUserAuthorized(const QString& user, const QString& pw)
{
- return _authTable->isUserAuthorized(user, pw);
+ if(isUserAuthBlocked())
+ return false;
+
+ if(!_authTable->isUserAuthorized(user, pw)){
+ setAuthBlock(true);
+ return false;
+ }
+ return true;
}
bool AuthManager::isTokenAuthorized(const QString& token)
{
- return _authTable->tokenExist(token);
+ if(isTokenAuthBlocked())
+ return false;
+
+ if(!_authTable->tokenExist(token)){
+ setAuthBlock();
+ return false;
+ }
+ return true;
+}
+
+bool AuthManager::isUserTokenAuthorized(const QString& usr, const QString& token)
+{
+ if(isUserAuthBlocked())
+ return false;
+
+ if(!_authTable->isUserTokenAuthorized(usr, token)){
+ setAuthBlock(true);
+ return false;
+ }
+ return true;
+}
+
+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)
@@ -144,6 +198,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);
}
}
@@ -167,3 +222,25 @@ void AuthManager::checkTimeout()
if(_pendingRequests.isEmpty())
_timer->stop();
}
+
+void AuthManager::checkAuthBlockTimeout(){
+ // handle user auth block
+ for (auto it = _userAuthAttempts.begin(); it != _userAuthAttempts.end(); it++) {
+ // after 10 minutes, we remove the entry
+ if (*it < (uint64_t)QDateTime::currentMSecsSinceEpoch()) {
+ _userAuthAttempts.erase(it--);
+ }
+ }
+
+ // handle token auth block
+ for (auto it = _tokenAuthAttempts.begin(); it != _tokenAuthAttempts.end(); it++) {
+ // after 10 minutes, we remove the entry
+ if (*it < (uint64_t)QDateTime::currentMSecsSinceEpoch()) {
+ _tokenAuthAttempts.erase(it--);
+ }
+ }
+
+ // if the lists are empty we stop
+ if(_userAuthAttempts.empty() && _tokenAuthAttempts.empty())
+ _authBlockTimer->stop();
+}
diff --git a/libsrc/hyperion/schema/schema-network.json b/libsrc/hyperion/schema/schema-network.json
index 922ba0e5..9b85b1f4 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" : true,
+ "propertyOrder" : 5
}
},
"additionalProperties" : false
diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp
index 8ae4c372..959ec20a 100644
--- a/libsrc/jsonserver/JsonClientConnection.cpp
+++ b/libsrc/jsonserver/JsonClientConnection.cpp
@@ -17,7 +17,10 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket, const bool& local
// create a new instance of JsonAPI
_jsonAPI = new JsonAPI(socket->peerAddress().toString(), _log, localConnection, this);
// get the callback messages from JsonAPI and send it to the client
- connect(_jsonAPI,SIGNAL(callbackMessage(QJsonObject)),this,SLOT(sendMessage(QJsonObject)));
+ connect(_jsonAPI, &JsonAPI::callbackMessage, this , &JsonClientConnection::sendMessage);
+ connect(_jsonAPI, &JsonAPI::forceClose, this , [&](){ _socket->close(); } );
+
+ _jsonAPI->initialize();
}
void JsonClientConnection::readRequest()
diff --git a/libsrc/webserver/QtHttpClientWrapper.cpp b/libsrc/webserver/QtHttpClientWrapper.cpp
index 41cf5403..de337078 100644
--- a/libsrc/webserver/QtHttpClientWrapper.cpp
+++ b/libsrc/webserver/QtHttpClientWrapper.cpp
@@ -300,8 +300,8 @@ void QtHttpClientWrapper::onReplySendDataRequested (void)
void QtHttpClientWrapper::sendToClientWithReply(QtHttpReply * reply)
{
- connect (reply, &QtHttpReply::requestSendHeaders, this, &QtHttpClientWrapper::onReplySendHeadersRequested);
- connect (reply, &QtHttpReply::requestSendData, this, &QtHttpClientWrapper::onReplySendDataRequested);
+ connect (reply, &QtHttpReply::requestSendHeaders, this, &QtHttpClientWrapper::onReplySendHeadersRequested, Qt::UniqueConnection);
+ connect (reply, &QtHttpReply::requestSendData, this, &QtHttpClientWrapper::onReplySendDataRequested, Qt::UniqueConnection);
m_parsingStatus = sendReplyToClient (reply);
}
@@ -340,3 +340,19 @@ QtHttpClientWrapper::ParsingStatus QtHttpClientWrapper::sendReplyToClient (QtHtt
return AwaitingRequest;
}
+
+void QtHttpClientWrapper::closeConnection()
+{
+ // probably filter for request to follow http spec
+ if(m_currentRequest != Q_NULLPTR)
+ {
+ QtHttpReply reply(m_serverHandle);
+ reply.setStatusCode(QtHttpReply::StatusCode::Forbidden);
+
+ connect (&reply, &QtHttpReply::requestSendHeaders, this, &QtHttpClientWrapper::onReplySendHeadersRequested, Qt::UniqueConnection);
+ connect (&reply, &QtHttpReply::requestSendData, this, &QtHttpClientWrapper::onReplySendDataRequested, Qt::UniqueConnection);
+
+ m_parsingStatus = sendReplyToClient(&reply);
+ }
+ m_sockClient->close ();
+}
diff --git a/libsrc/webserver/QtHttpClientWrapper.h b/libsrc/webserver/QtHttpClientWrapper.h
index e15797d0..5f3f31c6 100644
--- a/libsrc/webserver/QtHttpClientWrapper.h
+++ b/libsrc/webserver/QtHttpClientWrapper.h
@@ -34,6 +34,11 @@ public:
/// @brief Wrapper for sendReplyToClient(), handles m_parsingStatus and signal connect
void sendToClientWithReply (QtHttpReply * reply);
+ ///
+ /// @brief close a connection with FORBIDDEN header (used from JsonAPI over HTTP)
+ ///
+ void closeConnection();
+
private slots:
void onClientDataReceived (void);
diff --git a/libsrc/webserver/WebJsonRpc.cpp b/libsrc/webserver/WebJsonRpc.cpp
index 49c5932c..0b0c5d0d 100644
--- a/libsrc/webserver/WebJsonRpc.cpp
+++ b/libsrc/webserver/WebJsonRpc.cpp
@@ -15,14 +15,20 @@ WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, const bool&
const QString client = request->getClientInfo().clientAddress.toString();
_jsonAPI = new JsonAPI(client, _log, localConnection, this, true);
connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebJsonRpc::handleCallback);
+ connect(_jsonAPI, &JsonAPI::forceClose, [&]() { _wrapper->closeConnection(); _stopHandle = true; });
+ _jsonAPI->initialize();
}
void WebJsonRpc::handleMessage(QtHttpRequest* request)
{
- QByteArray header = request->getHeader("Authorization");
- QByteArray data = request->getRawData();
- _unlocked = true;
- _jsonAPI->handleMessage(data,header);
+ // TODO better solution. If jsonAPI emits forceClose the request is deleted and the following call to this method results in segfault
+ if(!_stopHandle)
+ {
+ QByteArray header = request->getHeader("Authorization");
+ QByteArray data = request->getRawData();
+ _unlocked = true;
+ _jsonAPI->handleMessage(data,header);
+ }
}
void WebJsonRpc::handleCallback(QJsonObject obj)
diff --git a/libsrc/webserver/WebJsonRpc.h b/libsrc/webserver/WebJsonRpc.h
index 867c3bfa..a750817d 100644
--- a/libsrc/webserver/WebJsonRpc.h
+++ b/libsrc/webserver/WebJsonRpc.h
@@ -22,6 +22,7 @@ private:
Logger* _log;
JsonAPI* _jsonAPI;
+ bool _stopHandle = false;
bool _unlocked = false;
private slots:
diff --git a/libsrc/webserver/WebSocketClient.cpp b/libsrc/webserver/WebSocketClient.cpp
index a16ecf1e..0942a299 100644
--- a/libsrc/webserver/WebSocketClient.cpp
+++ b/libsrc/webserver/WebSocketClient.cpp
@@ -25,6 +25,7 @@ WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, const
// Json processor
_jsonAPI = new JsonAPI(client, _log, localConnection, this);
connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebSocketClient::sendMessage);
+ connect(_jsonAPI, &JsonAPI::forceClose, this,[this]() { this->sendClose(CLOSECODE::NORMAL); });
Debug(_log, "New connection from %s", QSTRING_CSTR(client));
@@ -40,6 +41,9 @@ WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, const
_socket->write(QSTRING_CSTR(data), data.size());
_socket->flush();
+
+ // Init JsonAPI
+ _jsonAPI->initialize();
}
void WebSocketClient::handleWebSocketFrame(void)
@@ -123,7 +127,7 @@ void WebSocketClient::handleWebSocketFrame(void)
handleBinaryMessage(_wsReceiveBuffer);
}
_wsReceiveBuffer.clear();
-
+
}
}
break;
diff --git a/src/hyperiond/hyperiond.h b/src/hyperiond/hyperiond.h
index 67f6fcd7..30fcc6ef 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);
~HyperionDaemon();
///
diff --git a/src/hyperiond/main.cpp b/src/hyperiond/main.cpp
index 2d266d22..dd89ae43 100644
--- a/src/hyperiond/main.cpp
+++ b/src/hyperiond/main.cpp
@@ -28,6 +28,7 @@
#include
#include
#include
+#include <../../include/db/AuthTable.h>
#ifdef ENABLE_X11
#include
@@ -239,14 +240,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