From 2739aec1e3ccb5694cbe793f40608385571f085c Mon Sep 17 00:00:00 2001 From: brindosch Date: Thu, 26 Mar 2020 17:59:41 +0100 Subject: [PATCH] refactor: API split (#721) * refactor: API split * refactor: cleanup hyperiond --- .editorconfig | 21 + assets/webconfig/i18n/de.json | 4 + assets/webconfig/i18n/en.json | 4 + assets/webconfig/js/content_index.js | 158 ++- assets/webconfig/js/hyperion.js | 4 + include/api/API.h | 409 ++++++ include/api/JsonAPI.h | 124 +- include/api/JsonCB.h | 7 + include/api/apiStructs.h | 36 + include/db/AuthTable.h | 16 + include/effectengine/EffectEngine.h | 14 +- include/effectengine/EffectFileHandler.h | 14 +- include/hyperion/AuthManager.h | 147 ++- include/hyperion/Hyperion.h | 263 ++-- include/hyperion/HyperionIManager.h | 1 + libsrc/api/API.cpp | 529 ++++++++ .../api/JSONRPC_schema/schema-authorize.json | 2 +- libsrc/api/JsonAPI.cpp | 1176 +++++++---------- libsrc/api/JsonCB.cpp | 24 +- libsrc/effectengine/EffectEngine.cpp | 8 +- libsrc/effectengine/EffectFileHandler.cpp | 30 +- libsrc/hyperion/AuthManager.cpp | 166 ++- libsrc/hyperion/Hyperion.cpp | 15 +- 23 files changed, 2044 insertions(+), 1128 deletions(-) create mode 100644 .editorconfig create mode 100644 include/api/API.h create mode 100644 include/api/apiStructs.h create mode 100644 libsrc/api/API.cpp diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..c0384a55 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +trim_trailing_whitespace = true +end_of_line = lf +insert_final_newline = true + +# Tab indentation (no size specified) +[Makefile] +indent_style = tab + +[*.{c,h,cpp,hpp}] +indent_style = tab +indent_size = 4 + +# js and webui +[*.{ts,js,html}] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/assets/webconfig/i18n/de.json b/assets/webconfig/i18n/de.json index 37c03a51..479123a8 100644 --- a/assets/webconfig/i18n/de.json +++ b/assets/webconfig/i18n/de.json @@ -49,6 +49,8 @@ "general_btn_next": "Weiter", "general_btn_back": "Zurück", "general_btn_iswitch": "Switch", + "general_btn_grantAccess": "Zugriff gewähren", + "general_btn_denyAccess": "Zugriff ablehnen", "general_chars_needed": "weitere Zeichen benötigt", "general_wiki_moreto": "Mehr Informationen zu \"$1\" findest du in unserem Wiki", "dashboard_label_intro": "Das Dashboard zeigt dir Informationen zum Systemstatus, ob Updates verfügbar sind, den Komponentenstatus sowie die letzten Blog-Posts vom Hyperion Team.", @@ -203,6 +205,8 @@ "conf_network_tok_diaTitle" : "Neues Token erstellt!", "conf_network_tok_diaMsg" : "Hier ist dein neues Token, welches für den Zugriff auf die Hyperion API verwendet werden kann. Aus Sicherheitsgründen können Tokens nach der Erstellung nur einmalig eingesehen werden, notiere es dir daher jetzt.", "conf_network_tok_intro" : "Hier kannst du Token zur API Authentifizierung erstellen oder löschen. Neu erstellte Token werden einmalig angezeigt.", + "conf_network_tok_grantT": "App Token angefordert", + "conf_network_tok_grantMsg": "Eine App fordert Zugriff auf die Hyperion API durch ein Token. Möchtest du dies zulassen? Bitte überprüfe die angegebenen Informationen!", "conf_logging_label_intro": "Überprüfe die Meldungen im Prokotoll um zu erfahren was Hyperion gerade beschäftigt. Je nach eingestellter Protokoll-Stufe siehst du mehr oder weniger Informationen.", "conf_logging_btn_pbupload": "Bericht für Supportanfrage hochladen", "conf_logging_btn_autoscroll": "Automatisch scrollen", diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index a35ab8be..603d8710 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -48,6 +48,8 @@ "general_btn_next" : "Next", "general_btn_back" : "Back", "general_btn_iswitch" : "Switch", + "general_btn_grantAccess": "Grant Access", + "general_btn_denyAccess": "Deny Access", "general_chars_needed": "more characters needed", "general_wiki_moreto" : "More information about \"$1\" is available at our", "dashboard_label_intro" : "This dashboard gives you a quick overview of your Hyperion installation and shows you the latest news from the Hyperion Blog.", @@ -202,6 +204,8 @@ "conf_network_tok_diaTitle" : "New Token created!", "conf_network_tok_diaMsg" : "Here is your new token which can be used to grant an application access to the Hyperion API. For security reasons you can't view it again so use/note it down.", "conf_network_tok_intro" : "Here you can create and delete tokens for API authentication. Created tokens will only be displayed once.", + "conf_network_tok_grantT": "App Requests Token", + "conf_network_tok_grantMsg": "An App requested a token to get access to the Hyperion API. Do you want to grant access? Please verify the provided information!", "conf_logging_label_intro" : "Area to check log messages, you will see more or less information depending on the set logging level .", "conf_logging_btn_pbupload" : "Upload a report for support requests", "conf_logging_btn_autoscroll" : "Auto scrolling", diff --git a/assets/webconfig/js/content_index.js b/assets/webconfig/js/content_index.js index 1357b52a..89689b43 100644 --- a/assets/webconfig/js/content_index.js +++ b/assets/webconfig/js/content_index.js @@ -1,22 +1,21 @@ var instNameInit = false -$(document).ready( function() { +$(document).ready(function () { - loadContentTo("#container_connection_lost","connection_lost"); - loadContentTo("#container_restart","restart"); + loadContentTo("#container_connection_lost", "connection_lost"); + loadContentTo("#container_restart", "restart"); initWebSocket(); - $(window.hyperion).on("cmd-serverinfo",function(event){ + $(window.hyperion).on("cmd-serverinfo", function (event) { window.serverInfo = event.response.info; // comps - window.comps = event.response.info.components + window.comps = event.response.info.components $(window.hyperion).trigger("ready"); - window.comps.forEach( function(obj) { - if (obj.name == "ALL") - { - if(obj.enabled) + window.comps.forEach(function (obj) { + if (obj.name == "ALL") { + if (obj.enabled) $("#hyperion_disabled_notify").fadeOut("fast"); else $("#hyperion_disabled_notify").fadeIn("fast"); @@ -25,14 +24,13 @@ $(document).ready( function() { // determine button visibility var running = window.serverInfo.instance.filter(entry => entry.running); - if(running.length > 1) + if (running.length > 1) $('#btn_hypinstanceswitch').toggle(true) else $('#btn_hypinstanceswitch').toggle(false) // update listing at button updateHyperionInstanceListing() - if(!instNameInit) - { + if (!instNameInit) { window.currentHyperionInstanceName = getInstanceNameByIndex(0); instNameInit = true; } @@ -40,17 +38,40 @@ $(document).ready( function() { updateSessions(); }); // end cmd-serverinfo - $(window.hyperion).on("cmd-sessions-update", function(event) { + $(window.hyperion).on("cmd-sessions-update", function (event) { window.serverInfo.sessions = event.response.data; updateSessions(); }); - $(window.hyperion).one("cmd-authorize-getTokenList", function(event) { + $(window.hyperion).on("cmd-authorize-tokenRequest cmd-authorize-getPendingTokenRequests", function (event) { + var val = event.response.info; + if (Array.isArray(event.response.info)) { + if (event.response.info.length == 0) { + return + } + val = event.response.info[0] + if (val.comment == '') + $('#modal_dialog').modal('hide'); + } + + showInfoDialog("grantToken", $.i18n('conf_network_tok_grantT'), $.i18n('conf_network_tok_grantMsg') + '
App: ' + val.comment + '
Code: ' + val.id + '') + $("#tok_grant_acc").off().on('click', function () { + tokenList.push(val) + // forward event, in case we need to rebuild the list now + $(window.hyperion).trigger({ type: "build-token-list" }); + requestHandleTokenRequest(val.id, true) + }); + $("#tok_deny_acc").off().on('click', function () { + requestHandleTokenRequest(val.id, false) + }); + }); + + $(window.hyperion).one("cmd-authorize-getTokenList", function (event) { tokenList = event.response.info; requestServerInfo(); }); - $(window.hyperion).on("cmd-sysinfo", function(event) { + $(window.hyperion).on("cmd-sysinfo", function (event) { requestServerInfo(); window.sysInfo = event.response.info; @@ -58,33 +79,34 @@ $(document).ready( function() { window.currentChannel = window.sysInfo.hyperion.channel; }); - $(window.hyperion).one("cmd-config-getschema", function(event) { + $(window.hyperion).one("cmd-config-getschema", function (event) { window.serverSchema = event.response.info; requestServerConfig(); - requestTokenInfo(); + requestTokenInfo(); + requestGetPendingTokenRequests(); window.schema = window.serverSchema.properties; }); - $(window.hyperion).on("cmd-config-getconfig", function(event) { + $(window.hyperion).on("cmd-config-getconfig", function (event) { window.serverConfig = event.response.info; requestSysInfo(); window.showOptHelp = window.serverConfig.general.showOptHelp; }); - $(window.hyperion).on("cmd-config-setconfig", function(event) { - if (event.response.success === true) { + $(window.hyperion).on("cmd-config-setconfig", function (event) { + if (event.response.success === true) { showNotification('success', $.i18n('dashboard_alert_message_confsave_success'), $.i18n('dashboard_alert_message_confsave_success_t')) - } - }); + } + }); - $(window.hyperion).on("cmd-authorize-login", function(event) { + $(window.hyperion).on("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')+'') + 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') @@ -96,32 +118,30 @@ $(document).ready( function() { requestServerConfigSchema(); }); - $(window.hyperion).on("cmd-authorize-newPassword", function(event) { - if (event.response.success === true){ - showInfoDialog("success",$.i18n('InfoDialog_changePassword_success')); + $(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).on("cmd-authorize-newPasswordRequired", function(event) { + $(window.hyperion).on("cmd-authorize-newPasswordRequired", function (event) { var loginToken = getStorage("loginToken", true) - if (event.response.info.newPasswordRequired == true) - { + if (event.response.info.newPasswordRequired == true) { window.defaultPasswordIsSet = true; - if(loginToken) + if (loginToken) requestTokenAuthorization(loginToken) else requestAuthorization('hyperion'); } - else - { + else { $("#main-nav").attr('style', 'display:none') $("#top-navbar").attr('style', 'display:none') - if(loginToken) + if (loginToken) requestTokenAuthorization(loginToken) else loadContentTo("#page-content", "login") @@ -129,7 +149,7 @@ $(document).ready( function() { } }); - $(window.hyperion).on("cmd-authorize-adminRequired", function(event) { + $(window.hyperion).on("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) @@ -138,50 +158,47 @@ $(document).ready( function() { requestServerConfigSchema(); }); - $(window.hyperion).on("error",function(event){ + $(window.hyperion).on("error", function (event) { //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)) - { + if (event.reason == "No Authorization" && getStorage("loginToken", true)) { removeStorage("loginToken", true); requestRequiresAdminAuth(); } - else - { - showInfoDialog("error","Error", event.reason); + else { + showInfoDialog("error", "Error", event.reason); } }); - $(window.hyperion).on("open",function(event){ + $(window.hyperion).on("open", function (event) { requestRequiresAdminAuth(); }); - $(window.hyperion).one("ready", function(event) { + $(window.hyperion).one("ready", function (event) { loadContent(); }); - $(window.hyperion).on("cmd-adjustment-update", function(event) { + $(window.hyperion).on("cmd-adjustment-update", function (event) { window.serverInfo.adjustment = event.response.data }); - $(window.hyperion).on("cmd-videomode-update", function(event) { + $(window.hyperion).on("cmd-videomode-update", function (event) { window.serverInfo.videomode = event.response.data.videomode }); - $(window.hyperion).on("cmd-components-update", function(event) { + $(window.hyperion).on("cmd-components-update", function (event) { let obj = event.response.data // notfication in index - if (obj.name == "ALL") - { - if(obj.enabled) + if (obj.name == "ALL") { + if (obj.enabled) $("#hyperion_disabled_notify").fadeOut("fast"); else $("#hyperion_disabled_notify").fadeIn("fast"); } window.comps.forEach((entry, index) => { - if (entry.name === obj.name){ + if (entry.name === obj.name) { window.comps[index] = obj; } }); @@ -189,7 +206,7 @@ $(document).ready( function() { $(window.hyperion).trigger("components-updated"); }); - $(window.hyperion).on("cmd-instance-update", function(event) { + $(window.hyperion).on("cmd-instance-update", function (event) { window.serverInfo.instance = event.response.data var avail = event.response.data; // notify the update @@ -197,16 +214,13 @@ $(document).ready( function() { // if our current instance is no longer available we are at instance 0 again. var isInData = false; - for(var key in avail) - { - if(avail[key].instance == currentHyperionInstance && avail[key].running) - { + for (var key in avail) { + if (avail[key].instance == currentHyperionInstance && avail[key].running) { isInData = true; } } - if(!isInData) - { + if (!isInData) { //Delete Storage information about the last used but now stopped instance if (getStorage('lastSelectedInstance', false)) removeStorage('lastSelectedInstance', false) @@ -214,14 +228,14 @@ $(document).ready( function() { currentHyperionInstance = 0; currentHyperionInstanceName = getInstanceNameByIndex(0); requestServerConfig(); - setTimeout(requestServerInfo,100) - setTimeout(requestTokenInfo,200) - setTimeout(loadContent,300, undefined, true) + setTimeout(requestServerInfo, 100) + setTimeout(requestTokenInfo, 200) + setTimeout(loadContent, 300, undefined, true) } // determine button visibility var running = serverInfo.instance.filter(entry => entry.running); - if(running.length > 1) + if (running.length > 1) $('#btn_hypinstanceswitch').toggle(true) else $('#btn_hypinstanceswitch').toggle(false) @@ -230,27 +244,27 @@ $(document).ready( function() { updateHyperionInstanceListing() }); - $(window.hyperion).on("cmd-instance-switchTo", function(event){ + $(window.hyperion).on("cmd-instance-switchTo", function (event) { requestServerConfig(); - setTimeout(requestServerInfo,200) - setTimeout(requestTokenInfo,400) - setTimeout(loadContent,400, undefined, true) + setTimeout(requestServerInfo, 200) + setTimeout(requestTokenInfo, 400) + setTimeout(loadContent, 400, undefined, true) }); - $(window.hyperion).on("cmd-effects-update", function(event){ + $(window.hyperion).on("cmd-effects-update", function (event) { window.serverInfo.effects = event.response.data.effects }); - $(".mnava").bind('click.menu', function(e){ + $(".mnava").bind('click.menu', function (e) { loadContent(e); window.scrollTo(0, 0); }); }); -$(function(){ +$(function () { var sidebar = $('#side-menu'); // cache sidebar to a variable for performance - sidebar.delegate('a.inactive','click',function(){ + sidebar.delegate('a.inactive', 'click', function () { sidebar.find('.active').toggleClass('active inactive'); $(this).toggleClass('active inactive'); }); @@ -258,5 +272,5 @@ $(function(){ // hotfix body padding when bs modals overlap $(document.body).on('hide.bs.modal,hidden.bs.modal', function () { - $('body').css('padding-right','0'); + $('body').css('padding-right', '0'); }); diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js index 2d441a97..86374fee 100644 --- a/assets/webconfig/js/hyperion.js +++ b/assets/webconfig/js/hyperion.js @@ -204,6 +204,10 @@ function requestTokenInfo() sendToHyperion("authorize","getTokenList",""); } +function requestGetPendingTokenRequests (id, state) { + sendToHyperion("authorize", "getPendingTokenRequests", ""); +} + function requestHandleTokenRequest(id, state) { sendToHyperion("authorize","answerRequest",'"id":"'+id+'", "accept":'+state); diff --git a/include/api/API.h b/include/api/API.h new file mode 100644 index 00000000..c42a97d9 --- /dev/null +++ b/include/api/API.h @@ -0,0 +1,409 @@ +#pragma once + +// hyperion includes +#include +#include +#include +#include +// auth manager +#include + +#include +#include + +// qt includes +#include + +class QTimer; +class JsonCB; +class HyperionIManager; + +const QString NO_AUTH = "No Authorization"; + +/// +/// @brief API for Hyperion to be inherted from a child class with specific protocol implementations +/// Workflow: +/// 1. create the class +/// 2. connect the forceClose signal, as the api might to close the connection for security reasons +/// 3. call Initialize() +/// 4. proceed as usual +/// + +class API : public QObject +{ + Q_OBJECT + +public: +#include + // workaround Q_ARG std::map template issues + typedef std::map MapRegister; + typedef QMap MapAuthDefs; + + /// + /// Constructor + /// + ///@ param The parent Logger + /// @param localConnection Is this a local network connection? Use utils/NetOrigin to check that + /// @param parent Parent QObject + /// + API(Logger *log, const bool &localConnection, QObject *parent); + +protected: + /// + /// @brief Initialize the API + /// This call is REQUIRED! + /// + void init(void); + + /// + /// @brief Set a single color + /// @param[in] priority The priority of the written color + /// @param[in] ledColor The color to write to the leds + /// @param[in] timeout_ms The time the leds are set to the given color [ms] + /// @param[in] origin The setter + /// + void setColor(const int &priority, const std::vector &ledColors, const int &timeout_ms = -1, const QString &origin = "API", const hyperion::Components &callerComp = hyperion::COMP_INVALID); + + /// + /// @brief Set a image + /// @param[in] data The command data + /// @param[in] comp The component that should be used + /// @param[out] replyMsg The replyMsg on failure + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// @return True on success + /// + bool setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, const hyperion::Components &callerComp = hyperion::COMP_INVALID); + + /// + /// @brief Clear a priority in the Muxer, if -1 all priorities are cleared + /// @param priority The priority to clear + /// @param replyMsg the message on failure + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// @return True on success + /// + bool clearPriority(const int &priority, QString &replyMsg, const hyperion::Components &callerComp = hyperion::COMP_INVALID); + + /// + /// @brief Set a new component state + /// @param comp The component name + /// @param compState The new state of the comp + /// @param replyMsg The reply on failure + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// @ return True on success + /// + bool setComponentState(const QString &comp, bool &compState, QString &replyMsg, const hyperion::Components &callerComp = hyperion::COMP_INVALID); + + /// + /// @brief Set a ledToImageMapping type + /// @param type mapping type string + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// + void setLedMappingType(const int &type, const hyperion::Components &callerComp = hyperion::COMP_INVALID); + + /// + /// @brief Set the 2D/3D modes type + /// @param mode The VideoMode + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// + void setVideoMode(const VideoMode &mode, const hyperion::Components &callerComp = hyperion::COMP_INVALID); + + /// + /// @brief Set an effect + /// @param dat The effect data + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// REQUIRED dat fields: effectName, priority, duration, origin + /// + void setEffect(const EffectCmdData &dat, const hyperion::Components &callerComp = hyperion::COMP_INVALID); + + /// + /// @brief Set source auto select enabled or disabled + /// @param sate The new state + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// + void setSourceAutoSelect(const bool state, const hyperion::Components &callerComp = hyperion::COMP_INVALID); + + /// + /// @brief Set the visible priority to given priority + /// @param priority The priority to set + /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF + /// + void setVisiblePriority(const int &priority, const hyperion::Components &callerComp = hyperion::COMP_INVALID); + + /// + /// @brief Register a input or update the meta data of a previous register call + /// ATTENTION: Check unregisterInput() method description !!! + /// @param[in] priority The priority of the channel + /// @param[in] component The component of the channel + /// @param[in] origin Who set the channel (CustomString@IP) + /// @param[in] owner Specific owner string, might be empty + /// @param[in] callerComp The component that call this (e.g. PROTO/FLAT) + /// + void registerInput(const int &priority, const hyperion::Components &component, const QString &origin, const QString &owner, const hyperion::Components &callerComp); + + /// + /// @brief Revoke a registerInput() call by priority. We maintain all registered priorities in this scope + /// ATTENTION: This is MANDATORY if you change (priority change) or stop(clear/timeout) DURING lifetime. If this class destructs it's not needed + /// @param priority The priority to unregister + /// + void unregisterInput(const int &priority); + + /// + /// @brief Handle the instance switching + /// @param inst The requested instance + /// @return True on success else false + /// + bool setHyperionInstance(const quint8 &inst); + + /// + /// @brief Get all contrable components and their state + /// + std::map getAllComponents(); + + /// + /// @brief Check if Hyperion ist enabled + /// @return True when enabled else false + /// + bool isHyperionEnabled(); + + /// + /// @brief Get all instances data + /// @return The instance data + /// + QVector getAllInstanceData(void); + + /// + /// @brief Start instance + /// @param index The instance index + /// + void startInstance(const quint8 &index); + + /// + /// @brief Stop instance + /// @param index The instance index + /// + void stopInstance(const quint8 &index); + + ////////////////////////////////// + /// AUTH / ADMINISTRATION METHODS + ////////////////////////////////// + + /// + /// @brief Delete instance. Requires ADMIN ACCESS + /// @param index The instance index + /// @param replyMsg The reply Msg + /// @return False with reply + /// + bool deleteInstance(const quint8 &index, QString &replyMsg); + + /// + /// @brief Create instance. Requires ADMIN ACCESS + /// @param name With given name + /// @return False with reply + /// + QString createInstance(const QString &name); + + /// + /// @brief Rename an instance. Requires ADMIN ACCESS + /// @param index The instance index + /// @param name With given name + /// @return False with reply + /// + QString setInstanceName(const quint8 &index, const QString &name); + + /// + /// @brief Delete an effect. Requires ADMIN ACCESS + /// @param name The effect name + /// @return True on success else false + /// + QString deleteEffect(const QString &name); + + /// + /// @brief Delete an effect. Requires ADMIN ACCESS + /// @param name The effect name + /// @return True on success else false + /// + QString saveEffect(const QJsonObject &data); + + /// + /// @brief Save settings object. Requires ADMIN ACCESS + /// @param data The data object + /// + void saveSettings(const QJsonObject &data); + + /// + /// @brief Test if we are authorized to use the interface + /// @return The result + /// + bool isAuthorized() { return _authorized; }; + + /// + /// @brief Test if we are authorized to use the admin interface + /// @return The result + /// + bool isAdminAuthorized() { return _adminAuthorized; }; + + /// + /// @brief Update the Password of Hyperion. Requires ADMIN ACCESS + /// @param password Old password + /// @param newPassword New password + /// @return True on success else false + /// + bool updateHyperionPassword(const QString &password, const QString &newPassword); + + /// + /// @brief Get a new token from AuthManager. Requires ADMIN ACCESS + /// @param comment The comment of the request + /// @param def The final definition + /// @return Empty string on success else error message + /// + QString createToken(const QString &comment, AuthManager::AuthDefinition &def); + + /// + /// @brief Rename a token by given id. Requires ADMIN ACCESS + /// @param id The id of the token + /// @param comment The new comment + /// @return Empty string on success else error message + /// + QString renameToken(const QString &id, const QString &comment); + + /// + /// @brief Delete a token by given id. Requires ADMIN ACCESS + /// @param id The id of the token + /// @return Empty string on success else error message + /// + QString deleteToken(const QString &id); + + /// + /// @brief Set a new token request + /// @param comment The comment + /// @param id The id + /// + void setNewTokenRequest(const QString &comment, const QString &id); + + /// + /// @brief Cancel new token request + /// @param comment The comment + /// @param id The id + /// + void cancelNewTokenRequest(const QString &comment, const QString &id); + + /// + /// @brief Handle a pending token request. Requires ADMIN ACCESS + /// @param id The id fo the request + /// @param accept True when it should be accepted, else false + /// @return True on success + bool handlePendingTokenRequest(const QString &id, const bool accept); + + /// + /// @brief Get the current List of Tokens. Requires ADMIN ACCESS + /// @param def returns the defintions + /// @return True on success + /// + bool getTokenList(QVector &def); + + /// + /// @brief Get all current pending token requests. Requires ADMIN ACCESS + /// @return True on success + /// + bool getPendingTokenRequests(QVector &map); + + /// + /// @brief Is User Token Authorized. On success this will grant acces to API and ADMIN API + /// @param userToken The user Token + /// @return True on succes + /// + bool isUserTokenAuthorized(const QString &userToken); + + /// + /// @brief Get the current User Token (session token). Requires ADMIN ACCESS + /// @param userToken The user Token + /// @return True on success + /// + bool getUserToken(QString &userToken); + + /// + /// @brief Is a token authrized. On success this will grant acces to the API (NOT ADMIN API) + /// @param token The user Token + /// @return True on succes + /// + bool isTokenAuthorized(const QString &token); + + /// + /// @brief Is User authorized. On success this will grant acces to the API and ADMIN API + /// @param password The password of the User + /// @return True if authorized + /// + bool isUserAuthorized(const QString &password); + + /// + /// @brief Test if Hyperion has the default PW + /// @return The result + /// + bool hasHyperionDefaultPw(); + + /// + /// @brief Logout revokes all authorizations + /// + void logout(); + + /// Reflect auth status of this client + bool _authorized; + bool _adminAuthorized; + + /// Is this a local connection + bool _localConnection; + + AuthManager *_authManager; + HyperionIManager *_instanceManager; + + Logger *_log; + Hyperion *_hyperion; + +signals: + /// + /// @brief The API might decide to block connections for security reasons, this emitter should close the socket + /// + void forceClose(); + + /// + /// @brief Emits whenever a new Token request is pending. This signal is just active when ADMIN ACCESS has been granted + /// @param id The id of the request + /// @param comment The comment of the request; If the commen is EMPTY the request has been revoked by the caller. So remove it from the pending list + /// + void onPendingTokenRequest(const QString &id, const QString &comment); + + /// + /// @brief Handle emits from AuthManager of accepted/denied/timeouts token request, just if QObject matches with this instance it will emit. + /// @param success If true the request was accepted else false and no token was created + /// @param token The new token that is now valid + /// @param comment The comment that was part of the request + /// @param id The id that was part of the request + /// + void onTokenResponse(const bool &success, const QString &token, const QString &comment, const QString &id); + +private slots: + /// + /// @brief Is called whenever a Hyperion instance wants the current register list + /// @param callerInstance The instance should be returned in the answer call + /// + void requestActiveRegister(QObject *callerInstance); + + /// + /// @brief See onTokenResponse(). Here we validate the caller instance and on success we will emit onTokenResponse() + /// @param success If true the request was accepted else false and no token was created + /// @param caller The origin caller instance who requested this token + /// @param token The new token that is now valid + /// @param comment The comment that was part of the request + /// @param id The id that was part of the request + /// + void checkTokenResponse(const bool &success, QObject *caller, const QString &token, const QString &comment, const QString &id); + +private: + void stopDataConnectionss(); + + // Contains all active register call data + std::map _activeRegisters; + + // current instance index + quint8 _currInstanceIndex; +}; \ No newline at end of file diff --git a/include/api/JsonAPI.h b/include/api/JsonAPI.h index a7e2ec1f..03872699 100644 --- a/include/api/JsonAPI.h +++ b/include/api/JsonAPI.h @@ -1,7 +1,9 @@ #pragma once +// parent class +#include + // hyperion includes -#include #include #include #include @@ -14,7 +16,7 @@ class QTimer; class JsonCB; class AuthManager; -class JsonAPI : public QObject +class JsonAPI : public API { Q_OBJECT @@ -28,14 +30,14 @@ public: /// @param localConnection True when the sender has origin home network /// @param noListener if true, this instance won't listen for hyperion push events /// - JsonAPI(QString peerAddress, Logger* log, const bool& localConnection, QObject* parent, bool noListener = false); + JsonAPI(QString peerAddress, Logger *log, const bool &localConnection, QObject *parent, bool noListener = false); /// /// Handle an incoming JSON message /// /// @param message the incoming message as string /// - void handleMessage(const QString & message, const QString& httpAuthHeader = ""); + void handleMessage(const QString &message, const QString &httpAuthHeader = ""); /// /// @brief Initialization steps @@ -47,36 +49,35 @@ public slots: /// @brief Is called whenever the current Hyperion instance pushes new led raw values (if enabled) /// @param ledColors The current led colors /// - void streamLedcolorsUpdate(const std::vector& ledColors); + void streamLedcolorsUpdate(const std::vector &ledColors); /// /// @brief Push images whenever hyperion emits (if enabled) /// @param image The current image /// - void setImage(const Image & image); + void setImage(const Image &image); /// /// @brief Process and push new log messages from logger (if enabled) /// - void incommingLogMessage(const Logger::T_LOG_MESSAGE&); + void incommingLogMessage(const Logger::T_LOG_MESSAGE &); private slots: /// - /// @brief Handle emits from AuthManager of new request, just _userAuthorized sessions are allowed to handle them + /// @brief Handle emits from API of a new Token request. /// @param id The id of the request /// @param The comment which needs to be accepted /// - void handlePendingTokenRequest(const QString& id, const QString& comment); + void newPendingTokenRequest(const QString &id, const QString &comment); /// /// @brief Handle emits from AuthManager of accepted/denied/timeouts token request, just if QObject matches with this instance we are allowed to send response. /// @param success If true the request was accepted else false and no token was created - /// @param caller The origin caller instance who requested this token /// @param token The new token that is now valid /// @param comment The comment that was part of the request /// @param id The id that was part of the request /// - void handleTokenResponse(const bool& success, QObject* caller, const QString& token, const QString& comment, const QString& id); + void handleTokenResponse(const bool &success, const QString &token, const QString &comment, const QString &id); /// /// @brief Handle whenever the state of a instance (HyperionIManager) changes according to enum instanceState @@ -84,7 +85,7 @@ private slots: /// @param instance The index of instance /// @param name The name of the instance, just available with H_CREATED /// - void handleInstanceStateChange(const instanceState& state, const quint8& instance, const QString& name = QString()); + void handleInstanceStateChange(const instanceState &state, const quint8 &instance, const QString &name = QString()); signals: /// @@ -97,42 +98,15 @@ 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; - - /// Reflect auth status of this client - bool _authorized; - bool _userAuthorized; - - /// Reflect auth required - bool _apiAuthRequired; - // true if further callbacks are forbidden (http) bool _noListener; /// The peer address of the client QString _peerAddress; - /// Log instance - Logger* _log; - - /// Is this a local connection - bool _localConnection; - - /// Hyperion instance manager - HyperionIManager* _instanceManager; - - /// Hyperion instance - Hyperion* _hyperion; - // The JsonCB instance which handles data subscription/notifications - JsonCB* _jsonCB; + JsonCB *_jsonCB; // streaming buffers QJsonObject _streaming_leds_reply; @@ -142,17 +116,8 @@ private: /// flag to determine state of log streaming bool _streaming_logging_activated; - /// timer for live video refresh - QTimer* _imageStreamTimer; - - /// image stream connection handle - QMetaObject::Connection _imageStreamConnection; - - /// the current streaming image - Image _currentImage; - /// timer for led color refresh - QTimer* _ledStreamTimer; + QTimer *_ledStreamTimer; /// led stream connection handle QMetaObject::Connection _ledStreamConnection; @@ -166,21 +131,21 @@ private: /// @param forced indicate if it was a forced switch by system /// @return true on success. false if not found /// - bool handleInstanceSwitch(const quint8& instance = 0, const bool& forced = false); + bool handleInstanceSwitch(const quint8 &instance = 0, const bool &forced = false); /// /// Handle an incoming JSON Color message /// /// @param message the incoming message /// - void handleColorCommand(const QJsonObject & message, const QString &command, const int tan); + void handleColorCommand(const QJsonObject &message, const QString &command, const int tan); /// /// Handle an incoming JSON Image message /// /// @param message the incoming message /// - void handleImageCommand(const QJsonObject & message, const QString &command, const int tan); + void handleImageCommand(const QJsonObject &message, const QString &command, const int tan); /// /// Handle an incoming JSON Effect message @@ -194,126 +159,117 @@ private: /// /// @param message the incoming message /// - void handleCreateEffectCommand(const QJsonObject & message, const QString &command, const int tan); + void handleCreateEffectCommand(const QJsonObject &message, const QString &command, const int tan); /// /// Handle an incoming JSON Effect message (Delete JSON Effect) /// /// @param message the incoming message /// - void handleDeleteEffectCommand(const QJsonObject & message, const QString &command, const int tan); + void handleDeleteEffectCommand(const QJsonObject &message, const QString &command, const int tan); /// /// Handle an incoming JSON System info message /// /// @param message the incoming message /// - void handleSysInfoCommand(const QJsonObject & message, const QString &command, const int tan); + void handleSysInfoCommand(const QJsonObject &message, const QString &command, const int tan); /// /// Handle an incoming JSON Server info message /// /// @param message the incoming message /// - void handleServerInfoCommand(const QJsonObject & message, const QString &command, const int tan); + void handleServerInfoCommand(const QJsonObject &message, const QString &command, const int tan); /// /// Handle an incoming JSON Clear message /// /// @param message the incoming message /// - void handleClearCommand(const QJsonObject & message, const QString &command, const int tan); + void handleClearCommand(const QJsonObject &message, const QString &command, const int tan); /// /// Handle an incoming JSON Clearall message /// /// @param message the incoming message /// - void handleClearallCommand(const QJsonObject & message, const QString &command, const int tan); + void handleClearallCommand(const QJsonObject &message, const QString &command, const int tan); /// /// Handle an incoming JSON Adjustment message /// /// @param message the incoming message /// - void handleAdjustmentCommand(const QJsonObject & message, const QString &command, const int tan); + void handleAdjustmentCommand(const QJsonObject &message, const QString &command, const int tan); /// /// Handle an incoming JSON SourceSelect message /// /// @param message the incoming message /// - void handleSourceSelectCommand(const QJsonObject & message, const QString &command, const int tan); + void handleSourceSelectCommand(const QJsonObject &message, const QString &command, const int tan); /// Handle an incoming JSON GetConfig message and check subcommand /// /// @param message the incoming message /// - void handleConfigCommand(const QJsonObject & message, const QString &command, const int tan); + void handleConfigCommand(const QJsonObject &message, const QString &command, const int tan); /// Handle an incoming JSON GetConfig message from handleConfigCommand() /// /// @param message the incoming message /// - void handleSchemaGetCommand(const QJsonObject & message, const QString &command, const int tan); + void handleSchemaGetCommand(const QJsonObject &message, const QString &command, const int tan); /// Handle an incoming JSON SetConfig message from handleConfigCommand() /// /// @param message the incoming message /// - void handleConfigSetCommand(const QJsonObject & message, const QString &command, const int tan); + void handleConfigSetCommand(const QJsonObject &message, const QString &command, const int tan); /// /// Handle an incoming JSON Component State message /// /// @param message the incoming message /// - void handleComponentStateCommand(const QJsonObject & message, const QString &command, const int tan); + void handleComponentStateCommand(const QJsonObject &message, const QString &command, const int tan); /// Handle an incoming JSON Led Colors message /// /// @param message the incoming message /// - void handleLedColorsCommand(const QJsonObject & message, const QString &command, const int tan); + void handleLedColorsCommand(const QJsonObject &message, const QString &command, const int tan); /// Handle an incoming JSON Logging message /// /// @param message the incoming message /// - void handleLoggingCommand(const QJsonObject & message, const QString &command, const int tan); + void handleLoggingCommand(const QJsonObject &message, const QString &command, const int tan); /// Handle an incoming JSON Proccessing message /// /// @param message the incoming message /// - void handleProcessingCommand(const QJsonObject & message, const QString &command, const int tan); + void handleProcessingCommand(const QJsonObject &message, const QString &command, const int tan); /// Handle an incoming JSON VideoMode message /// /// @param message the incoming message /// - void handleVideoModeCommand(const QJsonObject & message, const QString &command, const int tan); + void handleVideoModeCommand(const QJsonObject &message, const QString &command, const int tan); /// Handle an incoming JSON plugin message /// /// @param message the incoming message /// - void handleAuthorizeCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle HTTP on-the-fly token authorization - /// @param command The command - /// @param tan The tan - /// @param token The token to verify - /// @return True on succcess else false (pushes failed client feedback) - /// - bool handleHTTPAuth(const QString& command, const int& tan, const QString& token); + void handleAuthorizeCommand(const QJsonObject &message, const QString &command, const int tan); /// Handle an incoming JSON instance message /// /// @param message the incoming message /// - void handleInstanceCommand(const QJsonObject & message, const QString &command, const int tan); + void handleInstanceCommand(const QJsonObject &message, const QString &command, const int tan); /// /// Handle an incoming JSON message of unknown type @@ -323,22 +279,22 @@ private: /// /// Send a standard reply indicating success /// - void sendSuccessReply(const QString &command="", const int tan=0); + void sendSuccessReply(const QString &command = "", const int tan = 0); /// /// Send a standard reply indicating success with data /// - void sendSuccessDataReply(const QJsonDocument &doc, const QString &command="", const int &tan=0); + void sendSuccessDataReply(const QJsonDocument &doc, const QString &command = "", const int &tan = 0); /// /// Send an error message back to the client /// /// @param error String describing the error /// - void sendErrorReply(const QString & error, const QString &command="", const int tan=0); + 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); -}; +}; \ No newline at end of file diff --git a/include/api/JsonCB.h b/include/api/JsonCB.h index 02cb96bc..3c4c7ff5 100644 --- a/include/api/JsonCB.h +++ b/include/api/JsonCB.h @@ -12,6 +12,8 @@ #include // settings #include +// AuthManager +#include class Hyperion; class ComponentRegister; @@ -119,6 +121,11 @@ private slots: /// void handleInstanceChange(); + /// + /// @brief Handle AuthManager token changes + /// + void handleTokenChange(const QVector &def); + private: /// pointer of Hyperion instance Hyperion* _hyperion; diff --git a/include/api/apiStructs.h b/include/api/apiStructs.h new file mode 100644 index 00000000..1e417afb --- /dev/null +++ b/include/api/apiStructs.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +struct ImageCmdData +{ + int priority; + QString origin; + int64_t duration; + int width; + int height; + int scale; + QString format; + QString imgName; + QByteArray data; +}; + +struct EffectCmdData +{ + int priority; + int duration; + QString pythonScript; + QString origin; + QString effectName; + QString data; + QJsonObject args; +}; + +struct registerData +{ + const hyperion::Components component; + const QString origin; + const QString owner; + const hyperion::Components callerComp; +}; diff --git a/include/db/AuthTable.h b/include/db/AuthTable.h index ddf7f3be..13508850 100644 --- a/include/db/AuthTable.h +++ b/include/db/AuthTable.h @@ -222,6 +222,22 @@ public: return deleteRecord(cond); } + /// + /// @brief Rename token record by id + /// @param[in] id The token id + /// @param[in] comment The new comment + /// @return true on success else false + /// + inline bool renameToken(const QString &id, const QString &comment) + { + QVariantMap map; + map["comment"] = comment; + + VectorPair cond; + cond.append(CPair("id", id)); + return updateRecord(cond, map); + } + /// /// @brief Get all 'comment', 'last_use' and 'id' column entries /// @return A vector of all lists diff --git a/include/effectengine/EffectEngine.h b/include/effectengine/EffectEngine.h index 58d9b6ea..e657d523 100644 --- a/include/effectengine/EffectEngine.h +++ b/include/effectengine/EffectEngine.h @@ -41,19 +41,17 @@ public: /// /// @brief Save an effect with EffectFileHandler - /// @param obj The effect args - /// @param[out] resultMsg The feedback message - /// @return True on success else false + /// @param obj The effect args + /// @return If not empty, it contains the error /// - bool saveEffect(const QJsonObject& obj, QString& resultMsg); + QString saveEffect(const QJsonObject& obj); /// /// @brief Delete an effect by name. - /// @param[in] effectName The effect name to delete - /// @param[out] resultMsg The message on error - /// @return True on success else false + /// @param effectName The effect name to delete + /// @return If not empty, it contains the error /// - bool deleteEffect(const QString& effectName, QString& resultMsg); + QString deleteEffect(const QString& effectName); /// /// @brief Get all init data of the running effects and stop them diff --git a/include/effectengine/EffectFileHandler.h b/include/effectengine/EffectFileHandler.h index dbc3a853..8fbf72f3 100644 --- a/include/effectengine/EffectFileHandler.h +++ b/include/effectengine/EffectFileHandler.h @@ -29,19 +29,17 @@ public: /// /// @brief Save an effect - /// @param obj The effect args - /// @param[out] resultMsg The feedback message - /// @return True on success else false + /// @param obj The effect args + /// @return If not empty, it contains the error /// - bool saveEffect(const QJsonObject& obj, QString& resultMsg); + QString saveEffect(const QJsonObject& obj); /// /// @brief Delete an effect by name. - /// @param[in] effectName The effect name to delete - /// @param[out] resultMsg The message on error - /// @return True on success else false + /// @param effectName The effect name to delete + /// @return If not empty, it contains the error /// - bool deleteEffect(const QString& effectName, QString& resultMsg); + QString deleteEffect(const QString& effectName); public slots: /// diff --git a/include/hyperion/AuthManager.h b/include/hyperion/AuthManager.h index 8b12c0e0..fb4607d8 100644 --- a/include/hyperion/AuthManager.h +++ b/include/hyperion/AuthManager.h @@ -20,13 +20,14 @@ class AuthManager : public QObject private: friend class HyperionDaemon; /// constructor is private, can be called from HyperionDaemon - AuthManager(QObject* parent = 0); + AuthManager(QObject *parent = 0); public: - struct AuthDefinition{ + struct AuthDefinition + { QString id; QString comment; - QObject* caller; + QObject *caller; uint64_t timeoutTime; QString token; QString lastUse; @@ -36,43 +37,25 @@ public: /// @brief Get the unique id (imported from removed class 'Stats') /// @return The unique id /// - const QString & getID() { return _uuid; }; - - /// - /// @brief Get all available token entries - /// - const QVector getTokenList(); + const QString &getID() { return _uuid; }; /// /// @brief Check authorization is required according to the user setting /// @return True if authorization required else false /// - const bool & isAuthRequired() { return _authRequired; }; + const bool &isAuthRequired() { return _authRequired; }; /// /// @brief Check if authorization is required for local network connections /// @return True if authorization required else false /// - const bool & isLocalAuthRequired() { return _localAuthRequired; }; + 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"); + const bool &isLocalAdminAuthRequired() { return _localAdminAuthRequired; }; /// /// @brief Reset Hyperion user @@ -81,11 +64,23 @@ public: bool resetHyperionUser(); /// - /// @brief Create a new token and skip the usual chain - /// @param comment The comment that should be used for - /// @return The new Auth definition + /// @brief Check if user auth is temporary blocked due to failed attempts + /// @return True on blocked and no further Auth requests will be accepted /// - const AuthDefinition createToken(const QString& comment); + 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); }; + + /// Pointer of this instance + static AuthManager *manager; + /// Get Pointer of this instance + static AuthManager *getInstance() { return manager; }; + +public slots: /// /// @brief Check if user is authorized @@ -93,14 +88,14 @@ public: /// @param pw The password /// @return True if authorized else false /// - bool isUserAuthorized(const QString& user, const QString& pw); + bool isUserAuthorized(const QString &user, const QString &pw); /// /// @brief Check if token is authorized /// @param token The token /// @return True if authorized else false /// - bool isTokenAuthorized(const QString& token); + bool isTokenAuthorized(const QString &token); /// /// @brief Check if token is authorized @@ -108,19 +103,29 @@ public: /// @param token The token /// @return True if authorized else false /// - bool isUserTokenAuthorized(const QString& usr, const QString& token); + 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 + /// @brief Create a new token and skip the usual chain + /// @param comment The comment that should be used for + /// @return The new Auth definition /// - bool isUserAuthBlocked(){ return (_userAuthAttempts.length() >= 10); }; + AuthManager::AuthDefinition createToken(const QString &comment); /// - /// @brief Check if token auth is temporary blocked due to failed attempts - /// @return True on blocked and no further Auth requests will be accepted + /// @brief Rename a token by id + /// @param id The token id + /// @param comment The new comment + /// @return True on success else false (or not found) /// - bool isTokenAuthBlocked(){ return (_tokenAuthAttempts.length() >= 25); }; + bool renameToken(const QString &id, const QString &comment); + + /// + /// @brief Delete a token by id + /// @param id The token id + /// @return True on success else false (or not found) + /// + bool deleteToken(const QString &id); /// /// @brief Change password of user @@ -129,7 +134,7 @@ public: /// @param newPw The new password /// @return True on success else false /// - bool updateUserPassword(const QString& user, const QString& pw, const QString& newPw); + 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 @@ -137,55 +142,55 @@ public: /// @param comment The comment as ident helper /// @param id The id created by the caller /// - void setNewTokenRequest(QObject* caller, const QString& comment, const QString& id); + void setNewTokenRequest(QObject *caller, const QString &comment, const QString &id); /// - /// @brief Accept a token request by id, generate token and inform token caller - /// @param id The id of the request - /// @return True on success, false if not found + /// @brief Cancel a pending token request with the provided comment and id as identifier helper + /// @param caller The QObject of the caller to deliver the reply + /// @param comment The comment as ident helper + /// @param id The id created by the caller /// - bool acceptTokenRequest(const QString& id); + void cancelNewTokenRequest(QObject *caller, const QString &comment, const QString &id); /// - /// @brief Deny a token request by id, inform the requester + /// @brief Handle a token request by id, generate token and inform token caller or deny /// @param id The id of the request - /// @return True on success, false if not found + /// @param accept The accept or deny the request /// - bool denyTokenRequest(const QString& id); + void handlePendingTokenRequest(const QString &id, const bool &accept); /// /// @brief Get pending requests /// @return All pending requests /// - const QMap getPendingRequests(); + QVector getPendingRequests(); /// - /// @brief Delete a token by id - /// @param id The token id - /// @return True on success else false (or not found) + /// @brief Get the current valid token for user. Make sure this call is allowed! + /// @param usr the defined user + /// @return The token /// - bool deleteToken(const QString& id); + const QString getUserToken(const QString &usr = "Hyperion"); - /// Pointer of this instance - static AuthManager* manager; - /// Get Pointer of this instance - static AuthManager* getInstance() { return manager; }; + /// + /// @brief Get all available token entries + /// + QVector getTokenList(); -public slots: /// /// @brief Handle settings update from Hyperion Settingsmanager emit /// @param type settings type from enum /// @param config configuration object /// - void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + void handleSettingsUpdate(const settings::type &type, const QJsonDocument &config); signals: /// /// @brief Emits whenever a new token Request has been created along with the id and comment /// @param id The id of the request - /// @param comment The comment of the request + /// @param comment The comment of the request; If the comment is EMPTY, it's a revoke of the caller! /// - void newPendingTokenRequest(const QString& id, const QString& comment); + void newPendingTokenRequest(const QString &id, const QString &comment); /// /// @brief Emits when the user has accepted or denied a token @@ -195,26 +200,32 @@ signals: /// @param comment The comment that was part of the request /// @param id The id that was part of the request /// - void tokenResponse(const bool& success, QObject* caller, const QString& token, const QString& comment, const QString& id); + void tokenResponse(const bool &success, QObject *caller, const QString &token, const QString &comment, const QString &id); + + /// + /// @brief Emits whenever the token list changes + /// @param data The full list of tokens + /// + void tokenChange(QVector); 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); + void setAuthBlock(const bool &user = false); /// Database interface for auth table - AuthTable* _authTable; + AuthTable *_authTable; /// Database interface for meta table - MetaTable* _metaTable; + MetaTable *_metaTable; /// Unique ID (imported from removed class 'Stats') QString _uuid; /// All pending requests - QMap _pendingRequests; + QMap _pendingRequests; /// Reflect state of global auth bool _authRequired; @@ -226,10 +237,10 @@ private: bool _localAdminAuthRequired; /// Timer for counting against pendingRequest timeouts - QTimer* _timer; + QTimer *_timer; // Timer which cleans up the block counter - QTimer* _authBlockTimer; + QTimer *_authBlockTimer; // Contains timestamps of failed user login attempts QVector _userAuthAttempts; @@ -247,4 +258,4 @@ private slots: /// @brief Check if there are timeouts for failed login attempts /// void checkAuthBlockTimeout(); -}; +}; \ No newline at end of file diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index e4c585d6..6eb16712 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -78,35 +78,8 @@ public: /// void freeObjects(bool emitCloseSignal=false); - /// - /// @brief Get a pointer to the effect engine - /// @return EffectEngine instance pointer - /// - EffectEngine* getEffectEngineInstance() { return _effectEngine; }; - - /// - /// @brief Get a pointer to the priorityMuxer instance - /// @return PriorityMuxer instance pointer - /// - PriorityMuxer* getMuxerInstance() { return &_muxer; }; - ImageProcessor* getImageProcessor() { return _imageProcessor; }; - /// - /// @brief Get a setting by settings::type from SettingsManager - /// @param type The settingsType from enum - /// @return Data Document - /// - QJsonDocument getSetting(const settings::type& type); - - /// - /// @brief Save a complete json config - /// @param config The entire config object - /// @param correct If true will correct json against schema before save - /// @return True on success else false - /// - bool saveSettings(QJsonObject config, const bool& correct = false); - /// /// @brief Get instance index of this instance /// @return The index of this instance @@ -123,103 +96,6 @@ public: /// QSize getLedGridSize() const { return _ledGridSize; }; - /// - /// Returns the current priority - /// - /// @return The current priority - /// - int getCurrentPriority() const; - - /// - /// Returns true if current priority is given priority - /// - /// @return bool - /// - bool isCurrentPriority(const int priority) const; - - /// - /// Returns a list of all registered priorities - /// - /// @return The list with priorities - /// - QList getActivePriorities() const; - - /// - /// Returns the information of a specific priorrity channel - /// - /// @param[in] priority The priority channel - /// - /// @return The information of the given, a not found priority will return lowest priority as fallback - /// - const InputInfo getPriorityInfo(const int priority) const; - - /// - /// @brief Save an effect - /// @param obj The effect args - /// @param[out] resultMsg The feedback message - /// @return True on success else false - /// - bool saveEffect(const QJsonObject& obj, QString& resultMsg); - - /// - /// @brief Delete an effect by name. - /// @param[in] effectName The effect name to delete - /// @param[out] resultMsg The message on error - /// @return True on success else false - /// - bool deleteEffect(const QString& effectName, QString& resultMsg); - - /// Get the list of available effects - /// @return The list of available effects - const std::list &getEffects() const; - - /// Get the list of active effects - /// @return The list of active effects - const std::list &getActiveEffects(); - - /// Get the list of available effect schema files - /// @return The list of available effect schema files - const std::list &getEffectSchemas(); - - /// gets the current json config object from SettingsManager - /// @return json config - const QJsonObject& getQJsonConfig(); - - /// enable/disable automatic/priorized source selection - /// @param enabled the state - void setSourceAutoSelectEnabled(bool enabled); - - /// set current input source to visible - /// @param priority the priority channel which should be vidible - /// @return true if success, false on error - bool setCurrentSourcePriority(int priority ); - - /// gets current state of automatic/priorized source selection - /// @return the state - bool sourceAutoSelectEnabled(); - - /// - /// @brief Called from components to update their current state. DO NOT CALL FROM USERS - /// @param[in] component The component from enum - /// @param[in] state The state of the component [true | false] - /// - void setNewComponentState(const hyperion::Components& component, const bool& state); - - /// - /// @brief Get a list of all contrable components and their current state - /// @return list of components - /// - std::map getAllComponents(); - - /// - /// @brief Test if a component is enabled - /// @param The component to test - /// @return Component state - /// - int isComponentEnabled(const hyperion::Components& comp); - - ComponentRegister& getComponentRegister() { return _componentRegister; }; - /// gets the methode how image is maped to leds const int & getLedMappingType(); @@ -244,6 +120,7 @@ public: LedDevice * getActiveDevice() const; public slots: + /// /// @brief Register a new input by priority, the priority is not active (timeout -100 isn't muxer recognized) until you start to update the data with setInput() /// A repeated call to update the base data of a known priority won't overwrite their current timeout @@ -322,6 +199,28 @@ public slots: /// bool clear(const int priority, bool forceClearAll=false); + /// ############# + // EFFECTENGINE + /// + /// @brief Get a pointer to the effect engine + /// @return EffectEngine instance pointer + /// + + EffectEngine* getEffectEngineInstance() { return _effectEngine; }; + /// + /// @brief Save an effect + /// @param obj The effect args + /// @return Empty on success else error message + /// + QString saveEffect(const QJsonObject& obj); + + /// + /// @brief Delete an effect by name. + /// @param effectName The effect name to delete + /// @return Empty on success else error message + /// + QString deleteEffect(const QString& effectName); + /// Run the specified effect on the given priority channel and optionally specify a timeout /// @param effectName Name of the effec to run /// @param priority The priority channel of the effect @@ -342,6 +241,122 @@ public slots: , const QString &imageData = "" ); + /// Get the list of available effects + /// @return The list of available effects + const std::list &getEffects() const; + + /// Get the list of active effects + /// @return The list of active effects + const std::list &getActiveEffects(); + + /// Get the list of available effect schema files + /// @return The list of available effect schema files + const std::list &getEffectSchemas(); + + /// ############# + /// PRIORITYMUXER + /// + /// @brief Get a pointer to the priorityMuxer instance + /// @return PriorityMuxer instance pointer + /// + PriorityMuxer* getMuxerInstance() { return &_muxer; }; + + /// + /// @brief enable/disable automatic/priorized source selection + /// @param state The new state + /// + void setSourceAutoSelect(const bool state); + + /// + /// @brief set current input source to visible + /// @param priority the priority channel which should be vidible + /// @return true if success, false on error + /// + bool setVisiblePriority(const int& priority); + + /// gets current state of automatic/priorized source selection + /// @return the state + bool sourceAutoSelectEnabled(); + + /// + /// Returns the current priority + /// + /// @return The current priority + /// + int getCurrentPriority() const; + + /// + /// Returns true if current priority is given priority + /// + /// @return bool + /// + bool isCurrentPriority(const int priority) const; + + /// + /// Returns a list of all registered priorities + /// + /// @return The list with priorities + /// + QList getActivePriorities() const; + + /// + /// Returns the information of a specific priorrity channel + /// + /// @param[in] priority The priority channel + /// + /// @return The information of the given, a not found priority will return lowest priority as fallback + /// + const InputInfo getPriorityInfo(const int priority) const; + + /// ############# + /// SETTINGSMANAGER + /// + /// @brief Get a setting by settings::type from SettingsManager + /// @param type The settingsType from enum + /// @return Data Document + /// + QJsonDocument getSetting(const settings::type& type); + + /// gets the current json config object from SettingsManager + /// @return json config + const QJsonObject& getQJsonConfig(); + + /// + /// @brief Save a complete json config + /// @param config The entire config object + /// @param correct If true will correct json against schema before save + /// @return True on success else false + /// + bool saveSettings(QJsonObject config, const bool& correct = false); + + /// ############ + /// COMPONENTREGISTER + /// + /// @brief Get the component Register + /// return Component register pointer + /// + ComponentRegister& getComponentRegister() { return _componentRegister; }; + + /// + /// @brief Called from components to update their current state. DO NOT CALL FROM USERS + /// @param[in] component The component from enum + /// @param[in] state The state of the component [true | false] + /// + void setNewComponentState(const hyperion::Components& component, const bool& state); + + /// + /// @brief Get a list of all contrable components and their current state + /// @return list of components + /// + std::map getAllComponents(); + + /// + /// @brief Test if a component is enabled + /// @param The component to test + /// @return Component state + /// + int isComponentEnabled(const hyperion::Components& comp); + /// sets the methode how image is maped to leds at ImageProcessor void setLedMappingType(const int& mappingType); diff --git a/include/hyperion/HyperionIManager.h b/include/hyperion/HyperionIManager.h index 1c36f3a6..0e88bdad 100644 --- a/include/hyperion/HyperionIManager.h +++ b/include/hyperion/HyperionIManager.h @@ -32,6 +32,7 @@ public: static HyperionIManager* getInstance() { return HIMinstance; }; static HyperionIManager* HIMinstance; +public slots: /// /// @brief Is given instance running? /// @param inst The instance to check diff --git a/libsrc/api/API.cpp b/libsrc/api/API.cpp new file mode 100644 index 00000000..f3356056 --- /dev/null +++ b/libsrc/api/API.cpp @@ -0,0 +1,529 @@ +// project includes +#include + +// stl includes +#include +#include + +// Qt includes +#include +#include +#include +#include +#include +#include +#include + +// hyperion includes +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +// bonjour wrapper +#include + +// ledmapping int <> string transform methods +#include + +// api includes +#include + +using namespace hyperion; + +API::API(Logger *log, const bool &localConnection, QObject *parent) + : QObject(parent) +{ + qRegisterMetaType("int64_t"); + qRegisterMetaType("VideoMode"); + qRegisterMetaType>("std::map"); + + // Init + _log = log; + _authManager = AuthManager::getInstance(); + _instanceManager = HyperionIManager::getInstance(); + _localConnection = localConnection; + + _authorized = false; + _adminAuthorized = false; + + _hyperion = _instanceManager->getHyperionInstance(0); + _currInstanceIndex = 0; + // TODO FIXME + // report back current registers when a Hyperion instance request it + //connect(ApiSync::getInstance(), &ApiSync::requestActiveRegister, this, &API::requestActiveRegister, Qt::QueuedConnection); + + // connect to possible token responses that has been requested + connect(_authManager, &AuthManager::tokenResponse, this, &API::checkTokenResponse); +} + +void API::init(void) +{ + bool apiAuthRequired = _authManager->isAuthRequired(); + + // For security we block external connections if default PW is set + if (!_localConnection && API::hasHyperionDefaultPw()) + { + emit forceClose(); + } + // if this is localConnection and network allows unauth locals, set authorized flag + 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) + { + _adminAuthorized = !_authManager->isLocalAdminAuthRequired(); + // just in positive direction + if (_adminAuthorized) + _authorized = true; + } +} + +void API::setColor(const int &priority, const std::vector &ledColors, const int &timeout_ms, const QString &origin, const hyperion::Components &callerComp) +{ + std::vector fledColors; + if (ledColors.size() % 3 == 0) + { + for (unsigned i = 0; i < ledColors.size(); i += 3) + { + fledColors.emplace_back(ColorRgb{ledColors[i], ledColors[i + 1], ledColors[i + 2]}); + } + QMetaObject::invokeMethod(_hyperion, "setColor", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(std::vector, fledColors), Q_ARG(int, timeout_ms), Q_ARG(QString, origin)); + //QMetaObject::invokeMethod(ApiSync::getInstance(), "setColor", Qt::QueuedConnection, Q_ARG(QObject *, _hyperion), Q_ARG(int, priority), Q_ARG(std::vector, fledColors), Q_ARG(int, timeout_ms), Q_ARG(QString, origin), Q_ARG(hyperion::Components, callerComp)); + } +} + +bool API::setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, const hyperion::Components &callerComp) +{ + // truncate name length + data.imgName.truncate(16); + + if (data.format == "auto") + { + QImage img = QImage::fromData(data.data); + if (img.isNull()) + { + replyMsg = "Failed to parse picture, the file might be corrupted"; + return false; + } + + // check for requested scale + if (data.scale > 24) + { + if (img.height() > data.scale) + { + img = img.scaledToHeight(data.scale); + } + if (img.width() > data.scale) + { + img = img.scaledToWidth(data.scale); + } + } + + // check if we need to force a scale + if (img.width() > 2000 || img.height() > 2000) + { + data.scale = 2000; + if (img.height() > data.scale) + { + img = img.scaledToHeight(data.scale); + } + if (img.width() > data.scale) + { + img = img.scaledToWidth(data.scale); + } + } + + data.width = img.width(); + data.height = img.height(); + + // extract image + img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); + data.data.clear(); + data.data.reserve(img.width() * img.height() * 3); + for (int i = 0; i < img.height(); ++i) + { + const QRgb *scanline = reinterpret_cast(img.scanLine(i)); + for (int j = 0; j < img.width(); ++j) + { + data.data.append((char)qRed(scanline[j])); + data.data.append((char)qGreen(scanline[j])); + data.data.append((char)qBlue(scanline[j])); + } + } + } + else + { + // check consistency of the size of the received data + if (data.data.size() != data.width * data.height * 3) + { + replyMsg = "Size of image data does not match with the width and height"; + return false; + } + } + + // copy image + Image image(data.width, data.height); + memcpy(image.memptr(), data.data.data(), data.data.size()); + + QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(hyperion::Components, comp), Q_ARG(QString, data.origin), Q_ARG(QString, data.imgName)); + QMetaObject::invokeMethod(_hyperion, "setInputImage", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(Image, image), Q_ARG(int64_t, data.duration)); + + //QMetaObject::invokeMethod(ApiSync::getInstance(), "registerInput", Qt::QueuedConnection, Q_ARG(QObject *, _hyperion), Q_ARG(int, data.priority), Q_ARG(hyperion::Components, comp), Q_ARG(QString, data.origin), Q_ARG(QString, data.imgName), Q_ARG(hyperion::Components, callerComp)); + //QMetaObject::invokeMethod(ApiSync::getInstance(), "setInputImage", Qt::QueuedConnection, Q_ARG(QObject *, _hyperion), Q_ARG(int, data.priority), Q_ARG(Image, image), Q_ARG(int64_t, data.duration), Q_ARG(hyperion::Components, comp), Q_ARG(hyperion::Components, callerComp)); + + return true; +} + +bool API::clearPriority(const int &priority, QString &replyMsg, const hyperion::Components &callerComp) +{ + if (priority < 0 || (priority > 0 && priority < 254)) + { + QMetaObject::invokeMethod(_hyperion, "clear", Qt::QueuedConnection, Q_ARG(int, priority)); + //QMetaObject::invokeMethod(ApiSync::getInstance(), "clearPriority", Qt::QueuedConnection, Q_ARG(QObject *, _hyperion), Q_ARG(int, priority), Q_ARG(hyperion::Components, callerComp)); + } + else + { + replyMsg = QString("Priority %1 is not allowed to be cleared").arg(priority); + return false; + } + return true; +} + +bool API::setComponentState(const QString &comp, bool &compState, QString &replyMsg, const hyperion::Components &callerComp) +{ + Components component = stringToComponent(comp); + + if (component != COMP_INVALID) + { + QMetaObject::invokeMethod(_hyperion, "compStateChangeRequest", Qt::QueuedConnection, Q_ARG(hyperion::Components, component), Q_ARG(bool, compState)); + //QMetaObject::invokeMethod(ApiSync::getInstance(), "compStateChangeRequest", Qt::QueuedConnection, Q_ARG(QObject *, _hyperion), Q_ARG(hyperion::Components, component), Q_ARG(bool, compState), Q_ARG(hyperion::Components, callerComp)); + return true; + } + replyMsg = QString("Unknown component name: %1").arg(comp); + return false; +} + +void API::setLedMappingType(const int &type, const hyperion::Components &callerComp) +{ + QMetaObject::invokeMethod(_hyperion, "setLedMappingType", Qt::QueuedConnection, Q_ARG(int, type)); + //QMetaObject::invokeMethod(ApiSync::getInstance(), "setLedMappingType", Qt::QueuedConnection, Q_ARG(QObject *, _hyperion), Q_ARG(int, type), Q_ARG(hyperion::Components, callerComp)); +} + +void API::setVideoMode(const VideoMode &mode, const hyperion::Components &callerComp) +{ + QMetaObject::invokeMethod(_hyperion, "setVideoMode", Qt::QueuedConnection, Q_ARG(VideoMode, mode)); + //QMetaObject::invokeMethod(ApiSync::getInstance(), "setVideoMode", Qt::QueuedConnection, Q_ARG(QObject *, _hyperion), Q_ARG(VideoMode, mode), Q_ARG(hyperion::Components, callerComp)); +} + +void API::setEffect(const EffectCmdData &dat, const hyperion::Components &callerComp) +{ + if (!dat.args.isEmpty()) + { + QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::QueuedConnection, Q_ARG(QString, dat.effectName), Q_ARG(QJsonObject, dat.args), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.pythonScript), Q_ARG(QString, dat.origin), Q_ARG(QString, dat.data)); + } + else + { + QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::QueuedConnection, Q_ARG(QString, dat.effectName), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.origin)); + //QMetaObject::invokeMethod(ApiSync::getInstance(), "setEffect", Qt::QueuedConnection, Q_ARG(QObject *, _hyperion), Q_ARG(QString, dat.effectName), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.origin), Q_ARG(hyperion::Components, callerComp)); + } +} + +void API::setSourceAutoSelect(const bool state, const hyperion::Components &callerComp) +{ + QMetaObject::invokeMethod(_hyperion, "setSourceAutoSelect", Qt::QueuedConnection, Q_ARG(bool, state)); + //QMetaObject::invokeMethod(ApiSync::getInstance(), "setSourceAutoSelect", Qt::QueuedConnection, Q_ARG(QObject *, _hyperion), Q_ARG(bool, state), Q_ARG(hyperion::Components, callerComp)); +} + +void API::setVisiblePriority(const int &priority, const hyperion::Components &callerComp) +{ + QMetaObject::invokeMethod(_hyperion, "setVisiblePriority", Qt::QueuedConnection, Q_ARG(int, priority)); + //QMetaObject::invokeMethod(ApiSync::getInstance(), "setVisiblePriority", Qt::QueuedConnection, Q_ARG(QObject *, _hyperion), Q_ARG(int, priority), Q_ARG(hyperion::Components, callerComp)); +} + +void API::registerInput(const int &priority, const hyperion::Components &component, const QString &origin, const QString &owner, const hyperion::Components &callerComp) +{ + if (_activeRegisters.count(priority)) + _activeRegisters.erase(priority); + + _activeRegisters.insert({priority, registerData{component, origin, owner, callerComp}}); + + QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(hyperion::Components, component), Q_ARG(QString, origin), Q_ARG(QString, owner)); + //QMetaObject::invokeMethod(ApiSync::getInstance(), "registerInput", Qt::QueuedConnection, Q_ARG(QObject *, _hyperion), Q_ARG(int, priority), Q_ARG(hyperion::Components, component), Q_ARG(QString, origin), Q_ARG(QString, owner), Q_ARG(hyperion::Components, callerComp)); +} + +void API::unregisterInput(const int &priority) +{ + if (_activeRegisters.count(priority)) + _activeRegisters.erase(priority); +} + +bool API::setHyperionInstance(const quint8 &inst) +{ + if (_currInstanceIndex == inst) + return true; + bool isRunning; + QMetaObject::invokeMethod(_instanceManager, "IsInstanceRunning", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isRunning), Q_ARG(quint8, inst)); + if (!isRunning) + return false; + + disconnect(_hyperion, 0, this, 0); + QMetaObject::invokeMethod(_instanceManager, "getHyperionInstance", Qt::BlockingQueuedConnection, Q_RETURN_ARG(Hyperion *, _hyperion), Q_ARG(quint8, inst)); + _currInstanceIndex = inst; + return true; +} + +std::map API::getAllComponents() +{ + std::map comps; + //QMetaObject::invokeMethod(_hyperion, "getAllComponents", Qt::BlockingQueuedConnection, Q_RETURN_ARG(std::map, comps)); + return comps; +} + +bool API::isHyperionEnabled() +{ + int res; + QMetaObject::invokeMethod(_hyperion, "isComponentEnabled", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, res), Q_ARG(hyperion::Components, hyperion::COMP_ALL)); + return res > 0; +} + +QVector API::getAllInstanceData(void) +{ + QVector vec; + QMetaObject::invokeMethod(_instanceManager, "getInstanceData", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, vec)); + return vec; +} + +void API::startInstance(const quint8 &index) +{ + QMetaObject::invokeMethod(_instanceManager, "startInstance", Qt::QueuedConnection, Q_ARG(quint8, index)); +} + +void API::stopInstance(const quint8 &index) +{ + QMetaObject::invokeMethod(_instanceManager, "stopInstance", Qt::QueuedConnection, Q_ARG(quint8, index)); +} + +void API::requestActiveRegister(QObject *callerInstance) +{ + // TODO FIXME + //if (_activeRegisters.size()) + // QMetaObject::invokeMethod(ApiSync::getInstance(), "answerActiveRegister", Qt::QueuedConnection, Q_ARG(QObject *, callerInstance), Q_ARG(MapRegister, _activeRegisters)); +} + +bool API::deleteInstance(const quint8 &index, QString &replyMsg) +{ + if (_adminAuthorized) + { + QMetaObject::invokeMethod(_instanceManager, "deleteInstance", Qt::QueuedConnection, Q_ARG(quint8, index)); + return true; + } + replyMsg = NO_AUTH; + return false; +} + +QString API::createInstance(const QString &name) +{ + if (_adminAuthorized) + { + bool success; + QMetaObject::invokeMethod(_instanceManager, "createInstance", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success), Q_ARG(QString, name)); + if (!success) + return QString("Instance name '%1' is already in use").arg(name); + + return ""; + } + return NO_AUTH; +} + +QString API::setInstanceName(const quint8 &index, const QString &name) +{ + if (_adminAuthorized) + { + QMetaObject::invokeMethod(_instanceManager, "saveName", Qt::QueuedConnection, Q_ARG(quint8, index), Q_ARG(QString, name)); + return ""; + } + return NO_AUTH; +} + +QString API::deleteEffect(const QString &name) +{ + if (_adminAuthorized) + { + QString res; + QMetaObject::invokeMethod(_hyperion, "deleteEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QString, name)); + return res; + } + return NO_AUTH; +} + +QString API::saveEffect(const QJsonObject &data) +{ + if (_adminAuthorized) + { + QString res; + QMetaObject::invokeMethod(_hyperion, "saveEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QJsonObject, data)); + return res; + } + return NO_AUTH; +} + +void API::saveSettings(const QJsonObject &data) +{ + if (!_adminAuthorized) + return; + QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::QueuedConnection, Q_ARG(QJsonObject, data), Q_ARG(bool, true)); +} + +bool API::updateHyperionPassword(const QString &password, const QString &newPassword) +{ + if (!_adminAuthorized) + return false; + bool res; + QMetaObject::invokeMethod(_authManager, "updateUserPassword", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, "Hyperion"), Q_ARG(QString, password), Q_ARG(QString, newPassword)); + return res; +} + +QString API::createToken(const QString &comment, AuthManager::AuthDefinition &def) +{ + if (!_adminAuthorized) + return NO_AUTH; + if (comment.isEmpty()) + return "comment is empty"; + QMetaObject::invokeMethod(_authManager, "createToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(AuthManager::AuthDefinition, def), Q_ARG(QString, comment)); + return ""; +} + +QString API::renameToken(const QString &id, const QString &comment) +{ + if (!_adminAuthorized) + return NO_AUTH; + if (comment.isEmpty() || id.isEmpty()) + return "Empty comment or id"; + + QMetaObject::invokeMethod(_authManager, "renameToken", Qt::QueuedConnection, Q_ARG(QString, id), Q_ARG(QString, comment)); + return ""; +} + +QString API::deleteToken(const QString &id) +{ + if (!_adminAuthorized) + return NO_AUTH; + if (id.isEmpty()) + return "Empty id"; + + QMetaObject::invokeMethod(_authManager, "deleteToken", Qt::QueuedConnection, Q_ARG(QString, id)); + return ""; +} + +void API::setNewTokenRequest(const QString &comment, const QString &id) +{ + QMetaObject::invokeMethod(_authManager, "setNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, id)); +} + +void API::cancelNewTokenRequest(const QString &comment, const QString &id) +{ + QMetaObject::invokeMethod(_authManager, "cancelNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, id)); +} + +bool API::handlePendingTokenRequest(const QString &id, const bool accept) +{ + if (!_adminAuthorized) + return false; + QMetaObject::invokeMethod(_authManager, "handlePendingTokenRequest", Qt::QueuedConnection, Q_ARG(QString, id), Q_ARG(bool, accept)); + return true; +} + +bool API::getTokenList(QVector &def) +{ + if (!_adminAuthorized) + return false; + QMetaObject::invokeMethod(_authManager, "getTokenList", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, def)); + return true; +} + +bool API::getPendingTokenRequests(QVector &map) +{ + if (!_adminAuthorized) + return false; + QMetaObject::invokeMethod(_authManager, "getPendingRequests", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, map)); + return true; +} + +bool API::isUserTokenAuthorized(const QString &userToken) +{ + bool res; + QMetaObject::invokeMethod(_authManager, "isUserTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, "Hyperion"), Q_ARG(QString, userToken)); + if (res) + { + _authorized = true; + _adminAuthorized = true; + // Listen for ADMIN ACCESS protected signals + connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest, Qt::UniqueConnection); + } + return res; +} + +bool API::getUserToken(QString &userToken) +{ + if (!_adminAuthorized) + return false; + QMetaObject::invokeMethod(_authManager, "getUserToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, userToken)); + return true; +} + +bool API::isTokenAuthorized(const QString &token) +{ + bool res; + QMetaObject::invokeMethod(_authManager, "isTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, token)); + if (res) + _authorized = true; + + return res; +} + +bool API::isUserAuthorized(const QString &password) +{ + bool res; + QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, "Hyperion"), Q_ARG(QString, password)); + if (res) + { + _authorized = true; + _adminAuthorized = true; + // Listen for ADMIN ACCESS protected signals + connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest, Qt::UniqueConnection); + } + return res; +} + +bool API::hasHyperionDefaultPw() +{ + bool res; + QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, "Hyperion"), Q_ARG(QString, "hyperion")); + return res; +} + +void API::logout() +{ + _authorized = false; + _adminAuthorized = false; + // Stop listenig for ADMIN ACCESS protected signals + disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest); + stopDataConnectionss(); +} + +void API::checkTokenResponse(const bool &success, QObject *caller, const QString &token, const QString &comment, const QString &id) +{ + if (this == caller) + emit onTokenResponse(success, token, comment, id); +} + +void API::stopDataConnectionss() +{ +} diff --git a/libsrc/api/JSONRPC_schema/schema-authorize.json b/libsrc/api/JSONRPC_schema/schema-authorize.json index 200bc541..3a308c2c 100644 --- a/libsrc/api/JSONRPC_schema/schema-authorize.json +++ b/libsrc/api/JSONRPC_schema/schema-authorize.json @@ -10,7 +10,7 @@ "subcommand" : { "type" : "string", "required" : true, - "enum" : ["requestToken","createToken","deleteToken","getTokenList","logout","login","required","adminRequired","newPasswordRequired","newPassword","answerRequest","getPendingRequests"] + "enum" : ["requestToken","createToken","renameToken","deleteToken","getTokenList","logout","login","tokenRequired","adminRequired","newPasswordRequired","newPassword","answerRequest","getPendingTokenRequests"] }, "tan" : { "type" : "integer" diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp index bb94ebef..9d628901 100644 --- a/libsrc/api/JsonAPI.cpp +++ b/libsrc/api/JsonAPI.cpp @@ -39,49 +39,48 @@ using namespace hyperion; -JsonAPI::JsonAPI(QString peerAddress, Logger* log, const bool& localConnection, QObject* parent, bool noListener) - : QObject(parent) - , _authManager(AuthManager::getInstance()) +JsonAPI::JsonAPI(QString peerAddress, Logger *log, const bool &localConnection, QObject *parent, bool noListener) + : API(log, localConnection, parent) +/* , _authManager(AuthManager::getInstance()) // moved to API , _authorized(false) - , _userAuthorized(false) + , _adminAuthorized(false) , _apiAuthRequired(_authManager->isAuthRequired()) , _noListener(noListener) , _peerAddress(peerAddress) - , _log(log) + , _log(log) // moved to API , _localConnection(localConnection) , _instanceManager(HyperionIManager::getInstance()) - , _hyperion(nullptr) + , _hyperion(nullptr) // moved , _jsonCB(new JsonCB(this)) , _streaming_logging_activated(false) - , _imageStreamTimer(new QTimer(this)) - , _ledStreamTimer(new QTimer(this)) + , _ledStreamTimer(new QTimer(this)) */ { + //_authManager = AuthManager::getInstance(); // moved to API init + //_authorized = false; // moved INIT api + //_adminAuthorized = false; // moved INIT api + //_apiAuthRequired = _authManager->isAuthRequired(); + _noListener = noListener; + _peerAddress = peerAddress; + //_log = log; // moved to API + // _localConnection = localConnection; moved init ti api + //_instanceManager = HyperionIManager::getInstance(); + //_hyperion = nullptr; // moved + _jsonCB = new JsonCB(this); + _streaming_logging_activated = false; + _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) - _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; - } + // init API, REQUIRED! + API::init(); + // REMOVE when jsonCB is migrated + handleInstanceSwitch(0); // setup auth interface - connect(_authManager, &AuthManager::newPendingTokenRequest, this, &JsonAPI::handlePendingTokenRequest); - connect(_authManager, &AuthManager::tokenResponse, this, &JsonAPI::handleTokenResponse); + connect(this, &API::onPendingTokenRequest, this, &JsonAPI::newPendingTokenRequest); + connect(this, &API::onTokenResponse, this, &JsonAPI::handleTokenResponse); // listen for killed instances connect(_instanceManager, &HyperionIManager::instanceStateChanged, this, &JsonAPI::handleInstanceStateChange); @@ -89,50 +88,35 @@ void JsonAPI::initialize(void) // pipe callbacks from subscriptions to parent connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage); - // init Hyperion pointer - handleInstanceSwitch(0); - // notify hyperion about a jsonMessageForward connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage); } -bool JsonAPI::handleInstanceSwitch(const quint8& inst, const bool& forced) +bool JsonAPI::handleInstanceSwitch(const quint8 &inst, const bool &forced) { - // check if we are already on the requested instance - if(_hyperion != nullptr && _hyperion->getInstanceIndex() == inst) - return true; - - if(_instanceManager->IsInstanceRunning(inst)) + if (API::setHyperionInstance(inst)) { - Debug(_log,"Client '%s' switch to Hyperion instance %d", QSTRING_CSTR(_peerAddress), inst); - // cut all connections between hyperion / plugins and this - if(_hyperion != nullptr) - disconnect(_hyperion, 0, this, 0); - - // get new Hyperion pointer - _hyperion = _instanceManager->getHyperionInstance(inst); - + Debug(_log, "Client '%s' switch to Hyperion instance %d", QSTRING_CSTR(_peerAddress), inst); // the JsonCB creates json messages you can subscribe to e.g. data change events _jsonCB->setSubscriptionsTo(_hyperion); - return true; } return false; } -void JsonAPI::handleMessage(const QString& messageString, const QString& httpAuthHeader) +void JsonAPI::handleMessage(const QString &messageString, const QString &httpAuthHeader) { - const QString ident = "JsonRpc@"+_peerAddress; + const QString ident = "JsonRpc@" + _peerAddress; QJsonObject message; // parse the message - if(!JsonUtils::parse(ident, messageString, message, _log)) + if (!JsonUtils::parse(ident, messageString, message, _log)) { sendErrorReply("Errors during message parsing, please consult the Hyperion Log."); return; } // check basic message - if(!JsonUtils::validate(ident, message, ":schema", _log)) + if (!JsonUtils::validate(ident, message, ":schema", _log)) { sendErrorReply("Errors during message validation, please consult the Hyperion Log."); return; @@ -140,9 +124,9 @@ void JsonAPI::handleMessage(const QString& messageString, const QString& httpAut // check specific message const QString command = message["command"].toString(); - if(!JsonUtils::validate(ident, message, QString(":schema-%1").arg(command), _log)) + if (!JsonUtils::validate(ident, message, QString(":schema-%1").arg(command), _log)) { - sendErrorReply("Errors during specific message validation, please consult the Hyperion Log"); + sendErrorReply("Errors during specific message validation, please consult the Hyperion Log", command); return; } @@ -155,40 +139,55 @@ void JsonAPI::handleMessage(const QString& messageString, const QString& httpAut return; } - // on the fly auth available for http from http Auth header, on failure we return and auth handler sends a failure - if(_noListener && _apiAuthRequired && !_authorized) - { - // extract token from http header - QString cToken = httpAuthHeader.mid(5).trimmed(); - if(!handleHTTPAuth(command, tan, cToken)) - return; - } - - // on strong api auth you need a auth for all cmds - if(_apiAuthRequired && !_authorized) + // check auth state + if (!API::isAuthorized()) { + // on the fly auth available for http from http Auth header + if (_noListener) + { + QString cToken = httpAuthHeader.mid(5).trimmed(); + if (API::isTokenAuthorized(cToken)) + goto proceed; + } sendErrorReply("No Authorization", command, tan); return; } - +proceed: // switch over all possible commands and handle them - if (command == "color") handleColorCommand (message, command, tan); - else if (command == "image") handleImageCommand (message, command, tan); - else if (command == "effect") handleEffectCommand (message, command, tan); - else if (command == "create-effect") handleCreateEffectCommand (message, command, tan); - else if (command == "delete-effect") handleDeleteEffectCommand (message, command, tan); - else if (command == "sysinfo") handleSysInfoCommand (message, command, tan); - else if (command == "serverinfo") handleServerInfoCommand (message, command, tan); - else if (command == "clear") handleClearCommand (message, command, tan); - else if (command == "adjustment") handleAdjustmentCommand (message, command, tan); - else if (command == "sourceselect") handleSourceSelectCommand (message, command, tan); - else if (command == "config") handleConfigCommand (message, command, tan); - else if (command == "componentstate") handleComponentStateCommand(message, command, tan); - else if (command == "ledcolors") handleLedColorsCommand (message, command, tan); - else if (command == "logging") handleLoggingCommand (message, command, tan); - else if (command == "processing") handleProcessingCommand (message, command, tan); - else if (command == "videomode") handleVideoModeCommand (message, command, tan); - else if (command == "instance") handleInstanceCommand (message, command, tan); + if (command == "color") + handleColorCommand(message, command, tan); + else if (command == "image") + handleImageCommand(message, command, tan); + else if (command == "effect") + handleEffectCommand(message, command, tan); + else if (command == "create-effect") + handleCreateEffectCommand(message, command, tan); + else if (command == "delete-effect") + handleDeleteEffectCommand(message, command, tan); + else if (command == "sysinfo") + handleSysInfoCommand(message, command, tan); + else if (command == "serverinfo") + handleServerInfoCommand(message, command, tan); + else if (command == "clear") + handleClearCommand(message, command, tan); + else if (command == "adjustment") + handleAdjustmentCommand(message, command, tan); + else if (command == "sourceselect") + handleSourceSelectCommand(message, command, tan); + else if (command == "config") + handleConfigCommand(message, command, tan); + else if (command == "componentstate") + handleComponentStateCommand(message, command, tan); + else if (command == "ledcolors") + handleLedColorsCommand(message, command, tan); + else if (command == "logging") + handleLoggingCommand(message, command, tan); + else if (command == "processing") + handleProcessingCommand(message, command, tan); + else if (command == "videomode") + handleVideoModeCommand(message, command, tan); + else if (command == "instance") + handleInstanceCommand(message, command, tan); // BEGIN | The following commands are derecated but used to ensure backward compatibility with hyperion Classic remote control else if (command == "clearall") @@ -198,10 +197,11 @@ void JsonAPI::handleMessage(const QString& messageString, const QString& httpAut // END // handle not implemented commands - else handleNotImplemented(); + else + handleNotImplemented(); } -void JsonAPI::handleColorCommand(const QJsonObject& message, const QString& command, const int tan) +void JsonAPI::handleColorCommand(const QJsonObject &message, const QString &command, const int tan) { emit forwardJsonMessage(message); int priority = message["priority"].toInt(); @@ -216,112 +216,31 @@ void JsonAPI::handleColorCommand(const QJsonObject& message, const QString& comm colors.emplace_back(uint8_t(entry.toInt())); } - std::vector fledColors; - if (colors.size() % 3 == 0) - { - for (unsigned i = 0; i < colors.size(); i += 3) - { - fledColors.emplace_back(ColorRgb{colors[i], colors[i + 1], colors[i + 2]}); - } - } - - // set color - _hyperion->setColor(priority, fledColors, duration, origin); - - // send reply + API::setColor(priority, colors, duration, origin); sendSuccessReply(command, tan); } -void JsonAPI::handleImageCommand(const QJsonObject& message, const QString& command, const int tan) +void JsonAPI::handleImageCommand(const QJsonObject &message, const QString &command, const int tan) { emit forwardJsonMessage(message); - // extract parameters - int priority = message["priority"].toInt(); - const QString origin = message["origin"].toString("JsonRpc") + "@"+_peerAddress; - int duration = message["duration"].toInt(-1); - int width = message["imagewidth"].toInt(); - int height = message["imageheight"].toInt(); - int scale = message["scale"].toInt(-1); - QString format = message["format"].toString(); - QString imgName = message["name"].toString(""); - QByteArray data = QByteArray::fromBase64(QByteArray(message["imagedata"].toString().toUtf8())); + API::ImageCmdData idata; + idata.priority = message["priority"].toInt(); + idata.origin = message["origin"].toString("JsonRpc") + "@" + _peerAddress; + idata.duration = message["duration"].toInt(-1); + idata.width = message["imagewidth"].toInt(); + idata.height = message["imageheight"].toInt(); + idata.scale = message["scale"].toInt(-1); + idata.format = message["format"].toString(); + idata.imgName = message["name"].toString(""); + idata.data = QByteArray::fromBase64(QByteArray(message["imagedata"].toString().toUtf8())); + QString replyMsg; - // truncate name length - imgName.truncate(16); - - if(format == "auto") + if (!API::setImage(idata, COMP_IMAGE, replyMsg)) { - QImage img = QImage::fromData(data); - if(img.isNull()) - { - sendErrorReply("Failed to parse picture, the file might be corrupted", command, tan); - return; - } - - // check for requested scale - if(scale > 24) - { - if(img.height() > scale) - { - img = img.scaledToHeight(scale); - } - if(img.width() > scale) - { - img = img.scaledToWidth(scale); - } - } - - // check if we need to force a scale - if(img.width() > 2000 || img.height() > 2000) - { - scale = 2000; - if(img.height() > scale) - { - img = img.scaledToHeight(scale); - } - if(img.width() > scale) - { - img = img.scaledToWidth(scale); - } - } - - width = img.width(); - height = img.height(); - - // extract image - img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); - data.clear(); - data.reserve(img.width() * img.height() * 3); - for (int i = 0; i < img.height(); ++i) - { - const QRgb * scanline = reinterpret_cast(img.scanLine(i)); - for (int j = 0; j < img.width(); ++j) - { - data.append((char) qRed(scanline[j])); - data.append((char) qGreen(scanline[j])); - data.append((char) qBlue(scanline[j])); - } - } + sendErrorReply(replyMsg, command, tan); + return; } - else - { - // check consistency of the size of the received data - if (data.size() != width*height*3) - { - sendErrorReply("Size of image data does not match with the width and height", command, tan); - return; - } - } - - // copy image - Image image(width, height); - memcpy(image.memptr(), data.data(), data.size()); - - _hyperion->registerInput(priority, hyperion::COMP_IMAGE, origin, imgName); - _hyperion->setInputImage(priority, image, duration); - - // send reply sendSuccessReply(command, tan); } @@ -329,43 +248,33 @@ void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &com { emit forwardJsonMessage(message); - // extract parameters - int priority = message["priority"].toInt(); - int duration = message["duration"].toInt(-1); - QString pythonScript = message["pythonScript"].toString(); - QString origin = message["origin"].toString("JsonRpc") + "@"+_peerAddress; - const QJsonObject & effect = message["effect"].toObject(); - const QString & effectName = effect["name"].toString(); - const QString & data = message["imageData"].toString("").toUtf8(); + EffectCmdData dat; + dat.priority = message["priority"].toInt(); + dat.duration = message["duration"].toInt(-1); + dat.pythonScript = message["pythonScript"].toString(); + dat.origin = message["origin"].toString("JsonRpc") + "@" + _peerAddress; + dat.effectName = message["effect"].toObject()["name"].toString(); + dat.data = message["imageData"].toString("").toUtf8(); + dat.args = message["effect"].toObject()["args"].toObject(); - // set output - (effect.contains("args")) - ? _hyperion->setEffect(effectName, effect["args"].toObject(), priority, duration, pythonScript, origin, data) - : _hyperion->setEffect(effectName, priority, duration, origin); + API::setEffect(dat); - // send reply sendSuccessReply(command, tan); } -void JsonAPI::handleCreateEffectCommand(const QJsonObject& message, const QString &command, const int tan) +void JsonAPI::handleCreateEffectCommand(const QJsonObject &message, const QString &command, const int tan) { - QString resultMsg; - if(_hyperion->saveEffect(message, resultMsg)) - sendSuccessReply(command, tan); - else - sendErrorReply(resultMsg, command, tan); + const QString resultMsg = API::saveEffect(message); + resultMsg.isEmpty() ? sendSuccessReply(command, tan) : sendErrorReply(resultMsg, command, tan); } -void JsonAPI::handleDeleteEffectCommand(const QJsonObject& message, const QString& command, const int tan) +void JsonAPI::handleDeleteEffectCommand(const QJsonObject &message, const QString &command, const int tan) { - QString resultMsg; - if(_hyperion->deleteEffect(message["name"].toString(), resultMsg)) - sendSuccessReply(command, tan); - else - sendErrorReply(resultMsg, command, tan); + const QString res = API::deleteEffect(message["name"].toString()); + res.isEmpty() ? sendSuccessReply(command, tan) : sendErrorReply(res, command, tan); } -void JsonAPI::handleSysInfoCommand(const QJsonObject&, const QString& command, const int tan) +void JsonAPI::handleSysInfoCommand(const QJsonObject &, const QString &command, const int tan) { // create result QJsonObject result; @@ -376,32 +285,31 @@ void JsonAPI::handleSysInfoCommand(const QJsonObject&, const QString& command, c SysInfo::HyperionSysInfo data = SysInfo::get(); QJsonObject system; - system["kernelType" ] = data.kernelType; - system["kernelVersion" ] = data.kernelVersion; - system["architecture" ] = data.architecture; - system["wordSize" ] = data.wordSize; - system["productType" ] = data.productType; + system["kernelType"] = data.kernelType; + system["kernelVersion"] = data.kernelVersion; + system["architecture"] = data.architecture; + system["wordSize"] = data.wordSize; + system["productType"] = data.productType; system["productVersion"] = data.productVersion; - system["prettyName" ] = data.prettyName; - system["hostName" ] = data.hostName; - system["domainName" ] = data.domainName; + system["prettyName"] = data.prettyName; + system["hostName"] = data.hostName; + system["domainName"] = data.domainName; info["system"] = system; QJsonObject hyperion; - hyperion["jsonrpc_version" ] = QString(HYPERION_JSON_VERSION); - hyperion["version" ] = QString(HYPERION_VERSION); - hyperion["build" ] = QString(HYPERION_BUILD_ID); - hyperion["gitremote" ] = QString(HYPERION_GIT_REMOTE); - hyperion["time" ] = QString(__DATE__ " " __TIME__); - hyperion["id" ] = _authManager->getID(); + hyperion["version"] = QString(HYPERION_VERSION); + hyperion["build"] = QString(HYPERION_BUILD_ID); + hyperion["gitremote"] = QString(HYPERION_GIT_REMOTE); + hyperion["time"] = QString(__DATE__ " " __TIME__); + hyperion["id"] = _authManager->getID(); info["hyperion"] = hyperion; // send the result - result["info" ] = info; + result["info"] = info; emit callbackMessage(result); } -void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& command, const int tan) +void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString &command, const int tan) { QJsonObject info; @@ -412,15 +320,16 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& activePriorities.removeAll(255); int currentPriority = _hyperion->getCurrentPriority(); - foreach (int priority, activePriorities) { - const Hyperion::InputInfo & priorityInfo = _hyperion->getPriorityInfo(priority); + foreach (int priority, activePriorities) + { + const Hyperion::InputInfo &priorityInfo = _hyperion->getPriorityInfo(priority); QJsonObject item; item["priority"] = priority; - if (priorityInfo.timeoutTime_ms > 0 ) + if (priorityInfo.timeoutTime_ms > 0) item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now); // owner has optional informations to the component - if(!priorityInfo.owner.isEmpty()) + if (!priorityInfo.owner.isEmpty()) item["owner"] = priorityInfo.owner; item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); @@ -428,7 +337,7 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& item["active"] = (priorityInfo.timeoutTime_ms >= -1); item["visible"] = (priority == currentPriority); - if(priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty()) + if (priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty()) { QJsonObject LEDcolor; @@ -445,9 +354,9 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& // add HSL Value to Array QJsonArray HSLValue; ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red, - priorityInfo.ledColors.begin()->green, - priorityInfo.ledColors.begin()->blue, - Hue, Saturation, Luminace); + priorityInfo.ledColors.begin()->green, + priorityInfo.ledColors.begin()->blue, + Hue, Saturation, Luminace); HSLValue.append(Hue); HSLValue.append(Saturation); @@ -465,9 +374,9 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& // collect adjustment information QJsonArray adjustmentArray; - for (const QString& adjustmentId : _hyperion->getAdjustmentIds()) + for (const QString &adjustmentId : _hyperion->getAdjustmentIds()) { - const ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId); + const ColorAdjustment *colorAdjustment = _hyperion->getAdjustment(adjustmentId); if (colorAdjustment == nullptr) { Error(_log, "Incorrect color adjustment id: %s", QSTRING_CSTR(adjustmentId)); @@ -520,12 +429,12 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& adjustment.insert("yellow", yellowAdjust); adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold(); - adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored(); + adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored(); adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness(); adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation(); - adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR(); + adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR(); adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG(); - adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB(); + adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB(); adjustmentArray.append(adjustment); } @@ -534,8 +443,8 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& // collect effect info QJsonArray effects; - const std::list & effectsDefinitions = _hyperion->getEffects(); - for (const EffectDefinition & effectDefinition : effectsDefinitions) + const std::list &effectsDefinitions = _hyperion->getEffects(); + for (const EffectDefinition &effectDefinition : effectsDefinitions) { QJsonObject effect; effect["name"] = effectDefinition.name; @@ -550,7 +459,7 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& // get available led devices QJsonObject ledDevices; QJsonArray availableLedDevices; - for (auto dev: LedDeviceWrapper::getDeviceMap()) + for (auto dev : LedDeviceWrapper::getDeviceMap()) { availableLedDevices.append(dev.first); } @@ -563,19 +472,19 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& #if defined(ENABLE_DISPMANX) || defined(ENABLE_V4L2) || defined(ENABLE_FB) || defined(ENABLE_AMLOGIC) || defined(ENABLE_OSX) || defined(ENABLE_X11) // get available grabbers //grabbers["active"] = ????; - for (auto grabber: GrabberWrapper::availableGrabbers()) + for (auto grabber : GrabberWrapper::availableGrabbers()) { availableGrabbers.append(grabber); } #endif grabbers["available"] = availableGrabbers; info["videomode"] = QString(videoMode2String(_hyperion->getCurrentVideoMode())); - info["grabbers"] = grabbers; + info["grabbers"] = grabbers; // get available components QJsonArray component; - std::map components = _hyperion->getAllComponents(); - for(auto comp : components) + std::map components = _hyperion->getComponentRegister().getRegister(); + for (auto comp : components) { QJsonObject item; item["name"] = QString::fromStdString(hyperion::componentToIdString(comp.first)); @@ -589,23 +498,24 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& // add sessions QJsonArray sessions; - for (auto session: BonjourBrowserWrapper::getInstance()->getAllServices()) + for (auto session : BonjourBrowserWrapper::getInstance()->getAllServices()) { - if (session.port<0) continue; + if (session.port < 0) + continue; QJsonObject item; - item["name"] = session.serviceName; - item["type"] = session.registeredType; + item["name"] = session.serviceName; + item["type"] = session.registeredType; item["domain"] = session.replyDomain; - item["host"] = session.hostName; - item["address"]= session.address; - item["port"] = session.port; + item["host"] = session.hostName; + item["address"] = session.address; + item["port"] = session.port; sessions.append(item); } info["sessions"] = sessions; // add instance info QJsonArray instanceInfo; - for(const auto & entry : _instanceManager->getInstanceData()) + for (const auto &entry : API::getAllInstanceData()) { QJsonObject obj; obj.insert("friendly_name", entry["friendly_name"].toString()); @@ -622,168 +532,159 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& // BEGIN | The following entries are derecated but used to ensure backward compatibility with hyperion Classic remote control // TODO Output the real transformation information instead of default - // HOST NAME - info["hostname"] = QHostInfo::localHostName(); + // HOST NAME + info["hostname"] = QHostInfo::localHostName(); - // TRANSFORM INFORMATION (DEFAULT VALUES) - QJsonArray transformArray; - for (const QString& transformId : _hyperion->getAdjustmentIds()) + // TRANSFORM INFORMATION (DEFAULT VALUES) + QJsonArray transformArray; + for (const QString &transformId : _hyperion->getAdjustmentIds()) + { + QJsonObject transform; + QJsonArray blacklevel, whitelevel, gamma, threshold; + + transform["id"] = transformId; + transform["saturationGain"] = 1.0; + transform["valueGain"] = 1.0; + transform["saturationLGain"] = 1.0; + transform["luminanceGain"] = 1.0; + transform["luminanceMinimum"] = 0.0; + + for (int i = 0; i < 3; i++) { - QJsonObject transform; - QJsonArray blacklevel, whitelevel, gamma, threshold; - - transform["id"] = transformId; - transform["saturationGain"] = 1.0; - transform["valueGain"] = 1.0; - transform["saturationLGain"] = 1.0; - transform["luminanceGain"] = 1.0; - transform["luminanceMinimum"] = 0.0; - - for (int i = 0; i < 3; i++ ) - { - blacklevel.append(0.0); - whitelevel.append(1.0); - gamma.append(2.50); - threshold.append(0.0); - } - - transform.insert("blacklevel", blacklevel); - transform.insert("whitelevel", whitelevel); - transform.insert("gamma", gamma); - transform.insert("threshold", threshold); - - transformArray.append(transform); + blacklevel.append(0.0); + whitelevel.append(1.0); + gamma.append(2.50); + threshold.append(0.0); } - info["transform"] = transformArray; - // ACTIVE EFFECT INFO - QJsonArray activeEffects; - const std::list & activeEffectsDefinitions = _hyperion->getActiveEffects(); - for (const ActiveEffectDefinition & activeEffectDefinition : activeEffectsDefinitions) + transform.insert("blacklevel", blacklevel); + transform.insert("whitelevel", whitelevel); + transform.insert("gamma", gamma); + transform.insert("threshold", threshold); + + transformArray.append(transform); + } + info["transform"] = transformArray; + + // ACTIVE EFFECT INFO + QJsonArray activeEffects; + const std::list &activeEffectsDefinitions = _hyperion->getActiveEffects(); + for (const ActiveEffectDefinition &activeEffectDefinition : activeEffectsDefinitions) + { + if (activeEffectDefinition.priority != PriorityMuxer::LOWEST_PRIORITY - 1) { - if (activeEffectDefinition.priority != PriorityMuxer::LOWEST_PRIORITY -1) - { - QJsonObject activeEffect; - activeEffect["script"] = activeEffectDefinition.script; - activeEffect["name"] = activeEffectDefinition.name; - activeEffect["priority"] = activeEffectDefinition.priority; - activeEffect["timeout"] = activeEffectDefinition.timeout; - activeEffect["args"] = activeEffectDefinition.args; - activeEffects.append(activeEffect); - } + QJsonObject activeEffect; + activeEffect["script"] = activeEffectDefinition.script; + activeEffect["name"] = activeEffectDefinition.name; + activeEffect["priority"] = activeEffectDefinition.priority; + activeEffect["timeout"] = activeEffectDefinition.timeout; + activeEffect["args"] = activeEffectDefinition.args; + activeEffects.append(activeEffect); } - info["activeEffects"] = activeEffects; + } + info["activeEffects"] = activeEffects; - // ACTIVE STATIC LED COLOR - QJsonArray activeLedColors; - const Hyperion::InputInfo & priorityInfo = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority()); - if(priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty()) + // ACTIVE STATIC LED COLOR + QJsonArray activeLedColors; + const Hyperion::InputInfo &priorityInfo = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority()); + if (priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty()) + { + QJsonObject LEDcolor; + // check if LED Color not Black (0,0,0) + if ((priorityInfo.ledColors.begin()->red + + priorityInfo.ledColors.begin()->green + + priorityInfo.ledColors.begin()->blue != + 0)) { QJsonObject LEDcolor; - // check if LED Color not Black (0,0,0) - if ((priorityInfo.ledColors.begin()->red + - priorityInfo.ledColors.begin()->green + - priorityInfo.ledColors.begin()->blue != 0)) - { - QJsonObject LEDcolor; - // add RGB Value to Array - QJsonArray RGBValue; - RGBValue.append(priorityInfo.ledColors.begin()->red); - RGBValue.append(priorityInfo.ledColors.begin()->green); - RGBValue.append(priorityInfo.ledColors.begin()->blue); - LEDcolor.insert("RGB Value", RGBValue); + // add RGB Value to Array + QJsonArray RGBValue; + RGBValue.append(priorityInfo.ledColors.begin()->red); + RGBValue.append(priorityInfo.ledColors.begin()->green); + RGBValue.append(priorityInfo.ledColors.begin()->blue); + LEDcolor.insert("RGB Value", RGBValue); - uint16_t Hue; - float Saturation, Luminace; + uint16_t Hue; + float Saturation, Luminace; - // add HSL Value to Array - QJsonArray HSLValue; - ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red, - priorityInfo.ledColors.begin()->green, - priorityInfo.ledColors.begin()->blue, - Hue, Saturation, Luminace); + // add HSL Value to Array + QJsonArray HSLValue; + ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red, + priorityInfo.ledColors.begin()->green, + priorityInfo.ledColors.begin()->blue, + Hue, Saturation, Luminace); - HSLValue.append(Hue); - HSLValue.append(Saturation); - HSLValue.append(Luminace); - LEDcolor.insert("HSL Value", HSLValue); + HSLValue.append(Hue); + HSLValue.append(Saturation); + HSLValue.append(Luminace); + LEDcolor.insert("HSL Value", HSLValue); - activeLedColors.append(LEDcolor); - } + activeLedColors.append(LEDcolor); } - info["activeLedColor"] = activeLedColors; + } + info["activeLedColor"] = activeLedColors; // END sendSuccessDataReply(QJsonDocument(info), command, tan); // AFTER we send the info, the client might want to subscribe to future updates - if(message.contains("subscribe")) + if (message.contains("subscribe")) { // check if listeners are allowed - if(_noListener) + if (_noListener) return; QJsonArray subsArr = message["subscribe"].toArray(); // catch the all keyword and build a list of all cmds - if(subsArr.contains("all")) + if (subsArr.contains("all")) { subsArr = QJsonArray(); - for(const auto & entry : _jsonCB->getCommands()) + for (const auto &entry : _jsonCB->getCommands()) { subsArr.append(entry); } } - for(const auto & entry : subsArr) + for (const auto &entry : subsArr) { // config callbacks just if auth is set - if(entry == "settings-update" && !_userAuthorized) + if ((entry == "settings-update" || entry == "token-update") && !API::isAdminAuthorized()) continue; - - if(!_jsonCB->subscribeFor(entry.toString())) - sendErrorReply(QString("Subscription for '%1' not found. Possible values: %2").arg(entry.toString(), _jsonCB->getCommands().join(", ")), command, tan); + // silent failure if a subscribe type is not found + _jsonCB->subscribeFor(entry.toString()); } } } -void JsonAPI::handleClearCommand(const QJsonObject& message, const QString& command, const int tan) +void JsonAPI::handleClearCommand(const QJsonObject &message, const QString &command, const int tan) { emit forwardJsonMessage(message); - int priority = message["priority"].toInt(); + QString replyMsg; - if(priority > 0) - _hyperion->clear(priority); - else if(priority < 0) - _hyperion->clear(-1); - else + if (!API::clearPriority(priority, replyMsg)) { - sendErrorReply("Priority 0 is not allowed", command, tan); + sendErrorReply(replyMsg, command, tan); return; } - - // send reply sendSuccessReply(command, tan); } -void JsonAPI::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan) +void JsonAPI::handleClearallCommand(const QJsonObject &message, const QString &command, const int tan) { emit forwardJsonMessage(message); - - // clear priority - _hyperion->clear(-1); - - // send reply + QString replyMsg; + API::clearPriority(-1, replyMsg); sendSuccessReply(command, tan); } -void JsonAPI::handleAdjustmentCommand(const QJsonObject& message, const QString& command, const int tan) +void JsonAPI::handleAdjustmentCommand(const QJsonObject &message, const QString &command, const int tan) { - const QJsonObject & adjustment = message["adjustment"].toObject(); + const QJsonObject &adjustment = message["adjustment"].toObject(); const QString adjustmentId = adjustment["id"].toString(_hyperion->getAdjustmentIds().first()); - ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId); + ColorAdjustment *colorAdjustment = _hyperion->getAdjustment(adjustmentId); if (colorAdjustment == nullptr) { Warning(_log, "Incorrect adjustment identifier: %s", adjustmentId.toStdString().c_str()); @@ -792,39 +693,39 @@ void JsonAPI::handleAdjustmentCommand(const QJsonObject& message, const QString& if (adjustment.contains("red")) { - const QJsonArray & values = adjustment["red"].toArray(); + const QJsonArray &values = adjustment["red"].toArray(); colorAdjustment->_rgbRedAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); } if (adjustment.contains("green")) { - const QJsonArray & values = adjustment["green"].toArray(); + const QJsonArray &values = adjustment["green"].toArray(); colorAdjustment->_rgbGreenAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); } if (adjustment.contains("blue")) { - const QJsonArray & values = adjustment["blue"].toArray(); + const QJsonArray &values = adjustment["blue"].toArray(); colorAdjustment->_rgbBlueAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); } if (adjustment.contains("cyan")) { - const QJsonArray & values = adjustment["cyan"].toArray(); + const QJsonArray &values = adjustment["cyan"].toArray(); colorAdjustment->_rgbCyanAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); } if (adjustment.contains("magenta")) { - const QJsonArray & values = adjustment["magenta"].toArray(); + const QJsonArray &values = adjustment["magenta"].toArray(); colorAdjustment->_rgbMagentaAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); } if (adjustment.contains("yellow")) { - const QJsonArray & values = adjustment["yellow"].toArray(); + const QJsonArray &values = adjustment["yellow"].toArray(); colorAdjustment->_rgbYellowAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); } if (adjustment.contains("white")) { - const QJsonArray & values = adjustment["white"].toArray(); + const QJsonArray &values = adjustment["white"].toArray(); colorAdjustment->_rgbWhiteAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); } @@ -864,30 +765,25 @@ void JsonAPI::handleAdjustmentCommand(const QJsonObject& message, const QString& sendSuccessReply(command, tan); } -void JsonAPI::handleSourceSelectCommand(const QJsonObject& message, const QString& command, const int tan) +void JsonAPI::handleSourceSelectCommand(const QJsonObject &message, const QString &command, const int tan) { - bool success = false; - if (message["auto"].toBool(false)) + if (message.contains("auto")) { - _hyperion->setSourceAutoSelectEnabled(true); - success = true; + API::setSourceAutoSelect(message["auto"].toBool(false)); } else if (message.contains("priority")) { - success = _hyperion->setCurrentSourcePriority(message["priority"].toInt()); - } - - if (success) - { - sendSuccessReply(command, tan); + API::setVisiblePriority(message["priority"].toInt()); } else { - sendErrorReply("setting current priority failed", command, tan); + sendErrorReply("Priority request is invalid", command, tan); + return; } + sendSuccessReply(command, tan); } -void JsonAPI::handleConfigCommand(const QJsonObject& message, const QString& command, const int tan) +void JsonAPI::handleConfigCommand(const QJsonObject &message, const QString &command, const int tan) { QString subcommand = message["subcommand"].toString(""); QString full_command = command + "-" + subcommand; @@ -898,28 +794,28 @@ void JsonAPI::handleConfigCommand(const QJsonObject& message, const QString& com } else if (subcommand == "setconfig") { - if(_userAuthorized) + if (_adminAuthorized) handleConfigSetCommand(message, full_command, tan); else - sendErrorReply("No Authorization",command, tan); + sendErrorReply("No Authorization", command, tan); } else if (subcommand == "getconfig") { - if(_userAuthorized) + if (_adminAuthorized) sendSuccessDataReply(QJsonDocument(_hyperion->getQJsonConfig()), full_command, tan); else - sendErrorReply("No Authorization",command, tan); + sendErrorReply("No Authorization", command, tan); } else if (subcommand == "reload") { - if(_userAuthorized) + if (_adminAuthorized) { _hyperion->freeObjects(true); Process::restartHyperion(); sendErrorReply("failed to restart hyperion", full_command, tan); } else - sendErrorReply("No Authorization",command, tan); + sendErrorReply("No Authorization", command, tan); } else { @@ -927,24 +823,22 @@ void JsonAPI::handleConfigCommand(const QJsonObject& message, const QString& com } } -void JsonAPI::handleConfigSetCommand(const QJsonObject& message, const QString &command, const int tan) +void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const QString &command, const int tan) { if (message.contains("config")) { QJsonObject config = message["config"].toObject(); - if(_hyperion->isComponentEnabled(hyperion::COMP_ALL)) + if (API::isHyperionEnabled()) { - if(_hyperion->saveSettings(config, true)) - sendSuccessReply(command,tan); - else - sendErrorReply("Failed to save configuration, more information at the Hyperion log", command, tan); + API::saveSettings(config); + sendSuccessReply(command, tan); } else sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", command, tan); } } -void JsonAPI::handleSchemaGetCommand(const QJsonObject& message, const QString& command, const int tan) +void JsonAPI::handleSchemaGetCommand(const QJsonObject &message, const QString &command, const int tan) { // create result QJsonObject schemaJson, alldevices, properties; @@ -959,7 +853,7 @@ void JsonAPI::handleSchemaGetCommand(const QJsonObject& message, const QString& { schemaJson = QJsonFactory::readSchema(schemaFile); } - catch(const std::runtime_error& error) + catch (const std::runtime_error &error) { throw std::runtime_error(error.what()); } @@ -972,10 +866,10 @@ void JsonAPI::handleSchemaGetCommand(const QJsonObject& message, const QString& // collect all available effect schemas QJsonObject pyEffectSchemas, pyEffectSchema; QJsonArray in, ex; - const std::list & effectsSchemas = _hyperion->getEffectSchemas(); - for (const EffectSchema & effectSchema : effectsSchemas) + const std::list &effectsSchemas = _hyperion->getEffectSchemas(); + for (const EffectSchema &effectSchema : effectsSchemas) { - if (effectSchema.pyFile.mid(0, 1) == ":") + if (effectSchema.pyFile.mid(0, 1) == ":") { QJsonObject internal; internal.insert("script", effectSchema.pyFile); @@ -1007,26 +901,22 @@ void JsonAPI::handleSchemaGetCommand(const QJsonObject& message, const QString& sendSuccessDataReply(QJsonDocument(schemaJson), command, tan); } -void JsonAPI::handleComponentStateCommand(const QJsonObject& message, const QString &command, const int tan) +void JsonAPI::handleComponentStateCommand(const QJsonObject &message, const QString &command, const int tan) { - const QJsonObject & componentState = message["componentstate"].toObject(); + const QJsonObject &componentState = message["componentstate"].toObject(); + QString comp = componentState["component"].toString("invalid"); + bool compState = componentState["state"].toBool(true); + QString replyMsg; - QString compStr = componentState["component"].toString("invalid"); - bool compState = componentState["state"].toBool(true); - - Components component = stringToComponent(compStr); - - if (component != COMP_INVALID) + if (!API::setComponentState(comp, compState, replyMsg)) { - // send result before apply - sendSuccessReply(command, tan); - emit _hyperion->compStateChangeRequest(component, compState); + sendErrorReply(replyMsg, command, tan); return; } - sendErrorReply("invalid component name", command, tan); + sendSuccessReply(command, tan); } -void JsonAPI::handleLedColorsCommand(const QJsonObject& message, const QString &command, const int tan) +void JsonAPI::handleLedColorsCommand(const QJsonObject &message, const QString &command, const int tan) { // create result QString subcommand = message["subcommand"].toString(""); @@ -1037,25 +927,27 @@ void JsonAPI::handleLedColorsCommand(const QJsonObject& message, const QString & if (subcommand == "ledstream-start") { _streaming_leds_reply["success"] = true; - _streaming_leds_reply["command"] = command+"-ledstream-update"; - _streaming_leds_reply["tan"] = tan; + _streaming_leds_reply["command"] = command + "-ledstream-update"; + _streaming_leds_reply["tan"] = tan; - connect(_hyperion, &Hyperion::rawLedColors, this, [=](const std::vector& ledValues) - { + connect(_hyperion, &Hyperion::rawLedColors, this, [=](const std::vector &ledValues) { _currentLedValues = ledValues; // necessary because Qt::UniqueConnection for lambdas does not work until 5.9 // see: https://bugreports.qt.io/browse/QTBUG-52438 if (!_ledStreamConnection) - _ledStreamConnection = connect(_ledStreamTimer, &QTimer::timeout, this, [=]() - { + _ledStreamConnection = connect(_ledStreamTimer, &QTimer::timeout, this, [=]() { emit streamLedcolorsUpdate(_currentLedValues); - }, Qt::UniqueConnection); + }, + Qt::UniqueConnection); // start the timer if (!_ledStreamTimer->isActive() || _ledStreamTimer->interval() != streaming_interval) _ledStreamTimer->start(streaming_interval); - }, Qt::UniqueConnection); + }, + Qt::UniqueConnection); + // push once + _hyperion->update(); } else if (subcommand == "ledstream-stop") { @@ -1066,261 +958,245 @@ void JsonAPI::handleLedColorsCommand(const QJsonObject& message, const QString & else if (subcommand == "imagestream-start") { _streaming_image_reply["success"] = true; - _streaming_image_reply["command"] = command+"-imagestream-update"; - _streaming_image_reply["tan"] = tan; + _streaming_image_reply["command"] = command + "-imagestream-update"; + _streaming_image_reply["tan"] = tan; - connect(_hyperion, &Hyperion::currentImage, this, [=](const Image& image) - { - _currentImage = image; - - // necessary because Qt::UniqueConnection for lambdas does not work until 5.9 - // see: https://bugreports.qt.io/browse/QTBUG-52438 - if (!_imageStreamConnection) - _imageStreamConnection = connect(_imageStreamTimer, &QTimer::timeout, this, [=]() - { - emit setImage(_currentImage); - }, Qt::UniqueConnection); - - // start timer - if (!_imageStreamTimer->isActive() || _imageStreamTimer->interval() != streaming_interval) - _imageStreamTimer->start(streaming_interval); - }, Qt::UniqueConnection); - - _hyperion->update(); + connect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage, Qt::UniqueConnection); } else if (subcommand == "imagestream-stop") { disconnect(_hyperion, &Hyperion::currentImage, this, 0); - _imageStreamTimer->stop(); - disconnect(_imageStreamConnection); } else { return; } - sendSuccessReply(command+"-"+subcommand,tan); + sendSuccessReply(command + "-" + subcommand, tan); } -void JsonAPI::handleLoggingCommand(const QJsonObject& message, const QString &command, const int tan) +void JsonAPI::handleLoggingCommand(const QJsonObject &message, const QString &command, const int tan) { // create result QString subcommand = message["subcommand"].toString(""); - _streaming_logging_reply["success"] = true; - _streaming_logging_reply["command"] = command; - _streaming_logging_reply["tan"] = tan; - if (subcommand == "start") + if (API::isAdminAuthorized()) { - if (!_streaming_logging_activated) - { - _streaming_logging_reply["command"] = command+"-update"; - connect(LoggerManager::getInstance(),SIGNAL(newLogMessage(Logger::T_LOG_MESSAGE)), this, SLOT(incommingLogMessage(Logger::T_LOG_MESSAGE))); - Debug(_log, "log streaming activated for client %s",_peerAddress.toStdString().c_str()); // needed to trigger log sending - } - } - else if (subcommand == "stop") - { - if (_streaming_logging_activated) - { - disconnect(LoggerManager::getInstance(), SIGNAL(newLogMessage(Logger::T_LOG_MESSAGE)), this, 0); - _streaming_logging_activated = false; - Debug(_log, "log streaming deactivated for client %s",_peerAddress.toStdString().c_str()); + _streaming_logging_reply["success"] = true; + _streaming_logging_reply["command"] = command; + _streaming_logging_reply["tan"] = tan; + if (subcommand == "start") + { + if (!_streaming_logging_activated) + { + _streaming_logging_reply["command"] = command + "-update"; + connect(LoggerManager::getInstance(), SIGNAL(newLogMessage(Logger::T_LOG_MESSAGE)), this, SLOT(incommingLogMessage(Logger::T_LOG_MESSAGE))); + Debug(_log, "log streaming activated for client %s", _peerAddress.toStdString().c_str()); // needed to trigger log sending + } } + else if (subcommand == "stop") + { + if (_streaming_logging_activated) + { + disconnect(LoggerManager::getInstance(), SIGNAL(newLogMessage(Logger::T_LOG_MESSAGE)), this, 0); + _streaming_logging_activated = false; + Debug(_log, "log streaming deactivated for client %s", _peerAddress.toStdString().c_str()); + } + } + else + { + return; + } + + sendSuccessReply(command + "-" + subcommand, tan); } else { - return; + sendErrorReply("No Authorization", command + "-" + subcommand, tan); } - - sendSuccessReply(command+"-"+subcommand,tan); } -void JsonAPI::handleProcessingCommand(const QJsonObject& message, const QString &command, const int tan) +void JsonAPI::handleProcessingCommand(const QJsonObject &message, const QString &command, const int tan) { - _hyperion->setLedMappingType(ImageProcessor::mappingTypeToInt( message["mappingType"].toString("multicolor_mean")) ); - + API::setLedMappingType(ImageProcessor::mappingTypeToInt(message["mappingType"].toString("multicolor_mean"))); sendSuccessReply(command, tan); } -void JsonAPI::handleVideoModeCommand(const QJsonObject& message, const QString &command, const int tan) +void JsonAPI::handleVideoModeCommand(const QJsonObject &message, const QString &command, const int tan) { - _hyperion->setVideoMode(parse3DMode(message["videoMode"].toString("2D"))); - + API::setVideoMode(parse3DMode(message["videoMode"].toString("2D"))); sendSuccessReply(command, tan); } -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& id = message["id"].toString().trimmed(); - const QString& password = message["password"].toString().trimmed(); - const QString& newPassword = message["newPassword"].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(); + const QString &comment = message["comment"].toString().trimmed(); // catch test if auth is required - if(subc == "required") + if (subc == "tokenRequired") { QJsonObject req; - req["required"] = !_authorized; - sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan); + req["required"] = !API::isAuthorized(); + + sendSuccessDataReply(QJsonDocument(req), command + "-" + subc, tan); return; } // catch test if admin auth is required - if(subc == "adminRequired") + if (subc == "adminRequired") { QJsonObject req; - req["adminRequired"] = !_userAuthorized; - sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan); + req["adminRequired"] = !API::isAdminAuthorized(); + sendSuccessDataReply(QJsonDocument(req), command + "-" + subc, tan); return; } // default hyperion password is a security risk, replace it asap - if(subc == "newPasswordRequired") + if (subc == "newPasswordRequired") { QJsonObject req; - req["newPasswordRequired"] = _authManager->hasHyperionDefaultPw(); - sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan); + req["newPasswordRequired"] = API::hasHyperionDefaultPw(); + sendSuccessDataReply(QJsonDocument(req), command + "-" + subc, tan); return; } // catch logout - if(subc == "logout") + if (subc == "logout") { - _authorized = false; - _userAuthorized = false; // disconnect all kind of data callbacks - stopDataConnections(); - sendSuccessReply(command+"-"+subc, tan); + JsonAPI::stopDataConnections(); // TODO move to API + API::logout(); + sendSuccessReply(command + "-" + subc, tan); return; } // change password - if(subc == "newPassword") + if (subc == "newPassword") { // use password, newPassword - if(_userAuthorized) + if (API::isAdminAuthorized()) { - if(_authManager->updateUserPassword("Hyperion", password, newPassword)) + if (API::updateHyperionPassword(password, newPassword)) { - sendSuccessReply(command+"-"+subc, tan); + sendSuccessReply(command + "-" + subc, tan); return; } - sendErrorReply("Failed to update user password",command+"-"+subc, tan); + sendErrorReply("Failed to update user password", command + "-" + subc, tan); return; } - sendErrorReply("No Authorization",command+"-"+subc, tan); + sendErrorReply("No Authorization", command + "-" + subc, tan); return; } // token created from ui - if(subc == "createToken") + if (subc == "createToken") { - const QString& c = message["comment"].toString().trimmed(); + // use comment // for user authorized sessions - if(_userAuthorized) + AuthManager::AuthDefinition def; + const QString res = API::createToken(comment, def); + if (res.isEmpty()) { - AuthManager::AuthDefinition def = _authManager->createToken(c); QJsonObject newTok; newTok["comment"] = def.comment; newTok["id"] = def.id; newTok["token"] = def.token; - sendSuccessDataReply(QJsonDocument(newTok), command+"-"+subc, tan); + sendSuccessDataReply(QJsonDocument(newTok), command + "-" + subc, tan); return; } - sendErrorReply("No Authorization",command+"-"+subc, tan); + sendErrorReply(res, command + "-" + subc, tan); + return; + } + + // rename Token + if (subc == "renameToken") + { + // use id/comment + const QString res = API::renameToken(id, comment); + if (res.isEmpty()) + { + sendSuccessReply(command + "-" + subc, tan); + return; + } + sendErrorReply(res, command + "-" + subc, tan); return; } // delete token - if(subc == "deleteToken") + if (subc == "deleteToken") { // use id - // for user authorized sessions - if(_userAuthorized) + const QString res = API::deleteToken(id); + if (res.isEmpty()) { - _authManager->deleteToken(id); - sendSuccessReply(command+"-"+subc, tan); + sendSuccessReply(command + "-" + subc, tan); return; } - sendErrorReply("No Authorization",command+"-"+subc, tan); + sendErrorReply(res, command + "-" + subc, tan); return; } // catch token request - if(subc == "requestToken") + if (subc == "requestToken") { - // use id - const QString& comment = message["comment"].toString().trimmed(); - _authManager->setNewTokenRequest(this, comment, id); + // use id/comment + const QString &comment = message["comment"].toString().trimmed(); + const bool &acc = message["accept"].toBool(true); + if (acc) + API::setNewTokenRequest(comment, id); + else + API::cancelNewTokenRequest(comment, id); // client should wait for answer return; } // get pending token requests - if(subc == "getPendingRequests") + if (subc == "getPendingTokenRequests") { - if(_userAuthorized) + QVector vec; + if (API::getPendingTokenRequests(vec)) { - QMap map = _authManager->getPendingRequests(); QJsonArray arr; - for(const auto& entry : map) + for (const auto &entry : vec) { QJsonObject obj; obj["comment"] = entry.comment; obj["id"] = entry.id; - obj["timeout"] = int(entry.timeoutTime - QDateTime::currentMSecsSinceEpoch()); + obj["timeout"] = int(entry.timeoutTime); arr.append(obj); } - sendSuccessDataReply(QJsonDocument(arr),command+"-"+subc, tan); + sendSuccessDataReply(QJsonDocument(arr), command + "-" + subc, tan); } else - sendErrorReply("No Authorization", command+"-"+subc, tan); + sendErrorReply("No Authorization", command + "-" + subc, tan); return; } // accept/deny token request - if(subc == "answerRequest") + if (subc == "answerRequest") { // use id - const bool& accept = message["accept"].toBool(false); - if(_userAuthorized) - { - if(accept) - _authManager->acceptTokenRequest(id); - else - _authManager->denyTokenRequest(id); - } - else - sendErrorReply("No Authorization", command+"-"+subc, tan); - - return; - } - // deny token request - if(subc == "acceptRequest") - { - // use id - if(_userAuthorized) - { - _authManager->acceptTokenRequest(id); - } - else - sendErrorReply("No Authorization", command+"-"+subc, tan); - + const bool &accept = message["accept"].toBool(false); + if (!API::handlePendingTokenRequest(id, accept)) + sendErrorReply("No Authorization", command + "-" + subc, tan); return; } - // catch get token list - if(subc == "getTokenList") + // get token list + if (subc == "getTokenList") { - if(_userAuthorized) + QVector defVect; + if (API::getTokenList(defVect)) { - QVector defVect = _authManager->getTokenList(); QJsonArray tArr; - for(const auto& entry : defVect) + for (const auto &entry : defVect) { QJsonObject subO; subO["comment"] = entry.comment; @@ -1329,164 +1205,130 @@ void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString tArr.append(subO); } - - sendSuccessDataReply(QJsonDocument(tArr),command+"-"+subc, tan); + sendSuccessDataReply(QJsonDocument(tArr), command + "-" + subc, tan); return; } - sendErrorReply("No Authorization",command+"-"+subc, tan); + sendErrorReply("No Authorization", command + "-" + subc, tan); return; } // login - if(subc == "login") + if (subc == "login") { - const QString& token = message["token"].toString().trimmed(); + const QString &token = message["token"].toString().trimmed(); // catch token - if(!token.isEmpty()) + if (!token.isEmpty()) { // userToken is longer - if(token.count() > 36) + if (token.count() > 36) { - if(_authManager->isUserTokenAuthorized("Hyperion",token)) - { - _authorized = true; - _userAuthorized = true; - sendSuccessReply(command+"-"+subc, tan); - } + if (API::isUserTokenAuthorized(token)) + sendSuccessReply(command + "-" + subc, tan); else - sendErrorReply("No Authorization", command+"-"+subc, tan); + sendErrorReply("No Authorization", command + "-" + subc, tan); return; } // usual app token is 36 - if(token.count() == 36) + if (token.count() == 36) { - if(_authManager->isTokenAuthorized(token)) + if (API::isTokenAuthorized(token)) { - _authorized = true; - sendSuccessReply(command+"-"+subc, tan); + sendSuccessReply(command + "-" + subc, tan); } else - sendErrorReply("No Authorization", command+"-"+subc, tan); + sendErrorReply("No Authorization", command + "-" + subc, tan); } - else - sendErrorReply("Token is too short", command+"-"+subc, tan); - return; } // password // use password - - if(password.count() >= 8) + if (password.count() >= 8) { - if(_authManager->isUserAuthorized("Hyperion", password)) + QString userTokenRep; + if (API::isUserAuthorized(password) && API::getUserToken(userTokenRep)) { - _authorized = true; - _userAuthorized = true; // Return the current valid Hyperion user token QJsonObject obj; - obj["token"] = _authManager->getUserToken(); - sendSuccessDataReply(QJsonDocument(obj),command+"-"+subc, tan); + obj["token"] = userTokenRep; + sendSuccessDataReply(QJsonDocument(obj), command + "-" + subc, tan); } else - sendErrorReply("No Authorization", command+"-"+subc, tan); + sendErrorReply("No Authorization", command + "-" + subc, tan); } else - sendErrorReply("Password string too short", command+"-"+subc, tan); + sendErrorReply("Password too short", command + "-" + subc, tan); } } -bool JsonAPI::handleHTTPAuth(const QString& command, const int& tan, const QString& token) +void JsonAPI::handleInstanceCommand(const QJsonObject &message, const QString &command, const int tan) { - if(_authManager->isTokenAuthorized(token)) - { - _authorized = true; - return true; - } - sendErrorReply("No Authorization", command, tan); - return false; -} + const QString &subc = message["subcommand"].toString(); + const quint8 &inst = message["instance"].toInt(); + const QString &name = message["name"].toString(); -void JsonAPI::handleInstanceCommand(const QJsonObject & message, const QString &command, const int tan) -{ - const QString & subc = message["subcommand"].toString(); - const quint8 & inst = message["instance"].toInt(); - const QString & name = message["name"].toString(); - - if(subc == "switchTo") + if (subc == "switchTo") { - if(handleInstanceSwitch(inst)){ + if (handleInstanceSwitch(inst)) + { QJsonObject obj; obj["instance"] = inst; - sendSuccessDataReply(QJsonDocument(obj),command+"-"+subc, tan); + sendSuccessDataReply(QJsonDocument(obj), command + "-" + subc, tan); } else - sendErrorReply("Selected Hyperion instance isn't running",command+"-"+subc, tan); + sendErrorReply("Selected Hyperion instance isn't running", command + "-" + subc, tan); return; } - if(subc == "startInstance") + if (subc == "startInstance") { // silent fail - _instanceManager->startInstance(inst); - sendSuccessReply(command+"-"+subc, tan); + API::startInstance(inst); + sendSuccessReply(command + "-" + subc, tan); return; } - if(subc == "stopInstance") + if (subc == "stopInstance") { // silent fail - _instanceManager->stopInstance(inst); - sendSuccessReply(command+"-"+subc, tan); + API::stopInstance(inst); + sendSuccessReply(command + "-" + subc, tan); return; } - if(subc == "deleteInstance") + if (subc == "deleteInstance") { - if(_userAuthorized) - { - if(_instanceManager->deleteInstance(inst)) - sendSuccessReply(command+"-"+subc, tan); - else - sendErrorReply(QString("Failed to delete instance '%1'").arg(inst), command+"-"+subc, tan); - } + QString replyMsg; + if (API::deleteInstance(inst, replyMsg)) + sendSuccessReply(command + "-" + subc, tan); else - sendErrorReply("No Authorization",command+"-"+subc, tan); + sendErrorReply(replyMsg, command + "-" + subc, tan); return; } // create and save name requires name - if(name.isEmpty()) - sendErrorReply("Name string required for this command",command+"-"+subc, tan); + if (name.isEmpty()) + sendErrorReply("Name string required for this command", command + "-" + subc, tan); - if(subc == "createInstance") + if (subc == "createInstance") { - if(_userAuthorized) - { - if(_instanceManager->createInstance(name)) - sendSuccessReply(command+"-"+subc, tan); - else - sendErrorReply(QString("The instance name '%1' is already in use").arg(name), command+"-"+subc, tan); - } + QString replyMsg = API::createInstance(name); + if (replyMsg.isEmpty()) + sendSuccessReply(command + "-" + subc, tan); else - sendErrorReply("No Authorization",command+"-"+subc, tan); + sendErrorReply(replyMsg, command + "-" + subc, tan); return; } - if(subc == "saveName") + if (subc == "saveName") { - if(_userAuthorized) - { - // silent fail - if(_instanceManager->saveName(inst,name)) - sendSuccessReply(command+"-"+subc, tan); - else - sendErrorReply(QString("The instance name '%1' is already in use").arg(name), command+"-"+subc, tan); - } + QString replyMsg = API::setInstanceName(inst, name); + if (replyMsg.isEmpty()) + sendSuccessReply(command + "-" + subc, tan); else - sendErrorReply("No Authorization",command+"-"+subc, tan); + sendErrorReply(replyMsg, command + "-" + subc, tan); return; } } @@ -1514,7 +1356,7 @@ void JsonAPI::sendSuccessDataReply(const QJsonDocument &doc, const QString &comm reply["success"] = true; reply["command"] = command; reply["tan"] = tan; - if(doc.isArray()) + if (doc.isArray()) reply["info"] = doc.array(); else reply["info"] = doc.object(); @@ -1535,12 +1377,12 @@ void JsonAPI::sendErrorReply(const QString &error, const QString &command, const emit callbackMessage(reply); } -void JsonAPI::streamLedcolorsUpdate(const std::vector& ledColors) +void JsonAPI::streamLedcolorsUpdate(const std::vector &ledColors) { QJsonObject result; QJsonArray leds; - for(const auto & color : ledColors) + for (const auto &color : ledColors) { leds << QJsonValue(color.red) << QJsonValue(color.green) << QJsonValue(color.blue); } @@ -1552,16 +1394,16 @@ void JsonAPI::streamLedcolorsUpdate(const std::vector& ledColors) emit callbackMessage(_streaming_leds_reply); } -void JsonAPI::setImage(const Image & image) +void JsonAPI::setImage(const Image &image) { - QImage jpgImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); + QImage jpgImage((const uint8_t *)image.memptr(), image.width(), image.height(), 3 * image.width(), QImage::Format_RGB888); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); jpgImage.save(&buffer, "jpg"); QJsonObject result; - result["image"] = "data:image/jpg;base64,"+QString(ba.toBase64()); + result["image"] = "data:image/jpg;base64," + QString(ba.toBase64()); _streaming_image_reply["result"] = result; emit callbackMessage(_streaming_image_reply); } @@ -1574,8 +1416,8 @@ void JsonAPI::incommingLogMessage(const Logger::T_LOG_MESSAGE &msg) if (!_streaming_logging_activated) { _streaming_logging_activated = true; - QVector* logBuffer = LoggerManager::getInstance()->getLogMessageBuffer(); - for(int i=0; ilength(); i++) + QVector *logBuffer = LoggerManager::getInstance()->getLogMessageBuffer(); + for (int i = 0; i < logBuffer->length(); i++) { message["appName"] = logBuffer->at(i).appName; message["loggerName"] = logBuffer->at(i).loggerName; @@ -1608,49 +1450,42 @@ void JsonAPI::incommingLogMessage(const Logger::T_LOG_MESSAGE &msg) emit callbackMessage(_streaming_logging_reply); } -void JsonAPI::handlePendingTokenRequest(const QString& id, const QString& comment) +void JsonAPI::newPendingTokenRequest(const QString &id, const QString &comment) { - // just user sessions are allowed to react on this, to prevent that token authorized instances authorize new tokens on their own - if(_userAuthorized) - { - QJsonObject obj; - obj["command"] = "authorize-event"; - obj["comment"] = comment; - obj["id"] = id; + QJsonObject obj; + obj["comment"] = comment; + obj["id"] = id; + obj["timeout"] = 180000; - emit callbackMessage(obj); - } + sendSuccessDataReply(QJsonDocument(obj), "authorize-tokenRequest", 1); } -void JsonAPI::handleTokenResponse(const bool& success, QObject* caller, const QString& token, const QString& comment, const QString& id) +void JsonAPI::handleTokenResponse(const bool &success, const QString &token, const QString &comment, const QString &id) { - // if this is the requester, we send the reply - if(this == caller) - { - const QString cmd = "authorize-requestToken"; - QJsonObject result; - result["token"] = token; - result["comment"] = comment; - result["id"] = id; + const QString cmd = "authorize-requestToken"; + QJsonObject result; + result["token"] = token; + result["comment"] = comment; + result["id"] = id; - if(success) - sendSuccessDataReply(QJsonDocument(result), cmd); - else - sendErrorReply("Token request timeout or denied", cmd); - } + if (success) + sendSuccessDataReply(QJsonDocument(result), cmd); + else + sendErrorReply("Token request timeout or denied", cmd, 5); } -void JsonAPI::handleInstanceStateChange(const instanceState& state, const quint8& instance, const QString& name) +void JsonAPI::handleInstanceStateChange(const instanceState &state, const quint8 &instance, const QString &name) { - switch(state){ - case H_ON_STOP: - if(_hyperion->getInstanceIndex() == instance) - { - handleInstanceSwitch(); - } - break; - default: - break; + switch (state) + { + case H_ON_STOP: + if (_hyperion->getInstanceIndex() == instance) + { + handleInstanceSwitch(); + } + break; + default: + break; } } @@ -1659,7 +1494,8 @@ void JsonAPI::stopDataConnections(void) LoggerManager::getInstance()->disconnect(); _streaming_logging_activated = false; _jsonCB->resetSubscriptions(); - _imageStreamTimer->stop(); + // led stream colors + disconnect(_hyperion, &Hyperion::rawLedColors, this, 0); _ledStreamTimer->stop(); - + disconnect(_ledStreamConnection); } diff --git a/libsrc/api/JsonCB.cpp b/libsrc/api/JsonCB.cpp index 972a8ef0..e6749d33 100644 --- a/libsrc/api/JsonCB.cpp +++ b/libsrc/api/JsonCB.cpp @@ -35,7 +35,7 @@ JsonCB::JsonCB(QObject* parent) , _prioMuxer(nullptr) { _availableCommands << "components-update" << "sessions-update" << "priorities-update" << "imageToLedMapping-update" - << "adjustment-update" << "videomode-update" << "effects-update" << "settings-update" << "leds-update" << "instance-update"; + << "adjustment-update" << "videomode-update" << "effects-update" << "settings-update" << "leds-update" << "instance-update" << "token-update"; } bool JsonCB::subscribeFor(const QString& type, const bool & unsubscribe) @@ -131,6 +131,14 @@ bool JsonCB::subscribeFor(const QString& type, const bool & unsubscribe) connect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCB::handleInstanceChange, Qt::UniqueConnection); } + if (type == "token-update") + { + if (unsubscribe) + disconnect(AuthManager::getInstance(), &AuthManager::tokenChange, this, &JsonCB::handleTokenChange); + else + connect(AuthManager::getInstance(), &AuthManager::tokenChange, this, &JsonCB::handleTokenChange, Qt::UniqueConnection); + } + return true; } @@ -401,3 +409,17 @@ void JsonCB::handleInstanceChange() } doCallback("instance-update", QVariant(arr)); } + +void JsonCB::handleTokenChange(const QVector &def) +{ + QJsonArray arr; + for (const auto &entry : def) + { + QJsonObject sub; + sub["comment"] = entry.comment; + sub["id"] = entry.id; + sub["last_use"] = entry.lastUse; + arr.push_back(sub); + } + doCallback("token-update", QVariant(arr)); +} diff --git a/libsrc/effectengine/EffectEngine.cpp b/libsrc/effectengine/EffectEngine.cpp index 1b693ac7..e7ce8995 100644 --- a/libsrc/effectengine/EffectEngine.cpp +++ b/libsrc/effectengine/EffectEngine.cpp @@ -47,14 +47,14 @@ EffectEngine::~EffectEngine() { } -bool EffectEngine::saveEffect(const QJsonObject& obj, QString& resultMsg) +QString EffectEngine::saveEffect(const QJsonObject& obj) { - return _effectFileHandler->saveEffect(obj, resultMsg); + return _effectFileHandler->saveEffect(obj); } -bool EffectEngine::deleteEffect(const QString& effectName, QString& resultMsg) +QString EffectEngine::deleteEffect(const QString& effectName) { - return _effectFileHandler->deleteEffect(effectName, resultMsg); + return _effectFileHandler->deleteEffect(effectName); } const std::list &EffectEngine::getActiveEffects() diff --git a/libsrc/effectengine/EffectFileHandler.cpp b/libsrc/effectengine/EffectFileHandler.cpp index 6e355a1d..4425a484 100644 --- a/libsrc/effectengine/EffectFileHandler.cpp +++ b/libsrc/effectengine/EffectFileHandler.cpp @@ -58,8 +58,9 @@ void EffectFileHandler::handleSettingsUpdate(const settings::type& type, const Q } } -bool EffectFileHandler::deleteEffect(const QString& effectName, QString& resultMsg) +QString EffectFileHandler::deleteEffect(const QString& effectName) { + QString resultMsg; std::list effectsDefinition = getEffects(); std::list::iterator it = std::find_if(effectsDefinition.begin(), effectsDefinition.end(), find_effect(effectName)); @@ -82,7 +83,7 @@ bool EffectFileHandler::deleteEffect(const QString& effectName, QString& resultM if (result) { updateEffects(); - return true; + return ""; } else resultMsg = "Can't delete effect configuration file: " + effectConfigurationFile.absoluteFilePath() + ". Please check permissions"; } else @@ -92,11 +93,12 @@ bool EffectFileHandler::deleteEffect(const QString& effectName, QString& resultM } else resultMsg = "Effect " + effectName + " not found"; - return false; + return resultMsg; } -bool EffectFileHandler::saveEffect(const QJsonObject& message, QString& resultMsg) +QString EffectFileHandler::saveEffect(const QJsonObject& message) { + QString resultMsg; if (!message["args"].toObject().isEmpty()) { QString scriptName; @@ -111,8 +113,7 @@ bool EffectFileHandler::saveEffect(const QJsonObject& message, QString& resultMs { if(!JsonUtils::validate("EffectFileHandler", message["args"].toObject(), it->schemaFile, _log)) { - resultMsg = "Error during arg validation against schema, please consult the Hyperion Log"; - return false; + return "Error during arg validation against schema, please consult the Hyperion Log"; } QJsonObject effectJson; @@ -123,8 +124,7 @@ bool EffectFileHandler::saveEffect(const QJsonObject& message, QString& resultMs { if (message["name"].toString().trimmed().isEmpty() || message["name"].toString().trimmed().startsWith(".")) { - resultMsg = "Can't save new effect. Effect name is empty or begins with a dot."; - return false; + return "Can't save new effect. Effect name is empty or begins with a dot."; } effectJson["name"] = message["name"].toString(); @@ -140,8 +140,7 @@ bool EffectFileHandler::saveEffect(const QJsonObject& message, QString& resultMs newFileName.setFile(iter->file); if (newFileName.absoluteFilePath().mid(0, 1) == ":") { - resultMsg = "The effect name '" + message["name"].toString() + "' is assigned to an internal effect. Please rename your effekt."; - return false; + return "The effect name '" + message["name"].toString() + "' is assigned to an internal effect. Please rename your effekt."; } } else { @@ -156,21 +155,18 @@ bool EffectFileHandler::saveEffect(const QJsonObject& message, QString& resultMs QFileInfo imageFileName(effectArray[0].toString().replace("$ROOT",_rootPath) + "/" + message["args"].toObject().value("image").toString()); if(!FileUtils::writeFile(imageFileName.absoluteFilePath(), QByteArray::fromBase64(message["imageData"].toString("").toUtf8()), _log)) { - resultMsg = "Error while saving image file '" + message["args"].toObject().value("image").toString() + ", please check the Hyperion Log"; - return false; + return "Error while saving image file '" + message["args"].toObject().value("image").toString() + ", please check the Hyperion Log"; } } if(!JsonUtils::write(newFileName.absoluteFilePath(), effectJson, _log)) { - resultMsg = "Error while saving effect, please check the Hyperion Log"; - return false; + return "Error while saving effect, please check the Hyperion Log"; } Info(_log, "Reload effect list"); updateEffects(); - resultMsg = ""; - return true; + return ""; } else resultMsg = "Can't save new effect. Effect path empty"; } else @@ -178,7 +174,7 @@ bool EffectFileHandler::saveEffect(const QJsonObject& message, QString& resultMs } else resultMsg = "Missing or empty Object 'args'"; - return false; + return resultMsg; } void EffectFileHandler::updateEffects() diff --git a/libsrc/hyperion/AuthManager.cpp b/libsrc/hyperion/AuthManager.cpp index 70bc892d..c9197873 100644 --- a/libsrc/hyperion/AuthManager.cpp +++ b/libsrc/hyperion/AuthManager.cpp @@ -8,11 +8,11 @@ #include #include -AuthManager* AuthManager::manager = nullptr; +AuthManager *AuthManager::manager = nullptr; -AuthManager::AuthManager(QObject* parent) +AuthManager::AuthManager(QObject *parent) : QObject(parent) - , _authTable(new AuthTable("",this)) + , _authTable(new AuthTable("", this)) , _metaTable(new MetaTable(this)) , _pendingRequests() , _authRequired(true) @@ -21,10 +21,12 @@ AuthManager::AuthManager(QObject* parent) { AuthManager::manager = this; - // get uuid _uuid = _metaTable->getUUID(); + // Register meta + qRegisterMetaType>("QVector"); + // setup timer _timer->setInterval(1000); connect(_timer, &QTimer::timeout, this, &AuthManager::checkTimeout); @@ -34,16 +36,16 @@ AuthManager::AuthManager(QObject* parent) connect(_authBlockTimer, &QTimer::timeout, this, &AuthManager::checkAuthBlockTimeout); // init with default user and password - if(!_authTable->userExist("Hyperion")) + if (!_authTable->userExist("Hyperion")) { - _authTable->createUser("Hyperion","hyperion"); + _authTable->createUser("Hyperion", "hyperion"); } // update Hyperion user token on startup _authTable->setUserToken("Hyperion"); } -const AuthManager::AuthDefinition AuthManager::createToken(const QString& comment) +AuthManager::AuthDefinition AuthManager::createToken(const QString &comment) { const QString token = QUuid::createUuid().toString().mid(1, 36); const QString id = QUuid::createUuid().toString().mid(1, 36).left(5); @@ -55,14 +57,15 @@ const AuthManager::AuthDefinition AuthManager::createToken(const QString& commen def.token = token; def.id = id; + emit tokenChange(getTokenList()); return def; } -const QVector AuthManager::getTokenList() +QVector AuthManager::getTokenList() { QVector vector = _authTable->getTokenList(); QVector finalVec; - for(const auto& entry : vector) + for (const auto &entry : vector) { AuthDefinition def; def.comment = entry["comment"].toString(); @@ -70,67 +73,73 @@ const QVector AuthManager::getTokenList() def.lastUse = entry["last_use"].toString(); // don't add empty ids - if(!entry["id"].toString().isEmpty()) + if (!entry["id"].toString().isEmpty()) finalVec.append(def); } return finalVec; } -const QString AuthManager::getUserToken(const QString & usr) +const QString AuthManager::getUserToken(const QString &usr) { + QString tok = _authTable->getUserToken(usr); return QString(_authTable->getUserToken(usr)); } -void AuthManager::setAuthBlock(const bool& user) +void AuthManager::setAuthBlock(const bool &user) { // current timestamp +10 minutes - if(user) - _userAuthAttempts.append(QDateTime::currentMSecsSinceEpoch()+600000); + if (user) + _userAuthAttempts.append(QDateTime::currentMSecsSinceEpoch() + 600000); else - _tokenAuthAttempts.append(QDateTime::currentMSecsSinceEpoch()+600000); + _tokenAuthAttempts.append(QDateTime::currentMSecsSinceEpoch() + 600000); - QMetaObject::invokeMethod(_authBlockTimer, "start", Qt::QueuedConnection); + _authBlockTimer->start(); } -bool AuthManager::isUserAuthorized(const QString& user, const QString& pw) +bool AuthManager::isUserAuthorized(const QString &user, const QString &pw) { - if(isUserAuthBlocked()) + if (isUserAuthBlocked()) return false; - if(!_authTable->isUserAuthorized(user, pw)){ + if (!_authTable->isUserAuthorized(user, pw)) + { setAuthBlock(true); return false; } return true; } -bool AuthManager::isTokenAuthorized(const QString& token) +bool AuthManager::isTokenAuthorized(const QString &token) { - if(isTokenAuthBlocked()) + if (isTokenAuthBlocked()) return false; - if(!_authTable->tokenExist(token)){ + if (!_authTable->tokenExist(token)) + { setAuthBlock(); return false; } + // timestamp update + tokenChange(getTokenList()); return true; } -bool AuthManager::isUserTokenAuthorized(const QString& usr, const QString& token) +bool AuthManager::isUserTokenAuthorized(const QString &usr, const QString &token) { - if(isUserAuthBlocked()) + if (isUserAuthBlocked()) return false; - if(!_authTable->isUserTokenAuthorized(usr, token)){ + if (!_authTable->isUserTokenAuthorized(usr, token)) + { setAuthBlock(true); return false; } return true; } -bool AuthManager::updateUserPassword(const QString& user, const QString& pw, const QString& newPw) +bool AuthManager::updateUserPassword(const QString &user, const QString &pw, const QString &newPw) { - if(isUserAuthorized(user, pw)) + if (isUserAuthorized(user, pw)) return _authTable->updateUserPassword(user, newPw); return false; @@ -141,64 +150,90 @@ 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)) { - AuthDefinition newDef {id, comment, caller, uint64_t(QDateTime::currentMSecsSinceEpoch()+60000)}; + AuthDefinition newDef{id, comment, caller, uint64_t(QDateTime::currentMSecsSinceEpoch() + 180000)}; _pendingRequests[id] = newDef; _timer->start(); emit newPendingTokenRequest(id, comment); } } -bool AuthManager::acceptTokenRequest(const QString& id) +void AuthManager::cancelNewTokenRequest(QObject *caller, const QString &comment, const QString &id) { - if(_pendingRequests.contains(id)) + if (_pendingRequests.contains(id)) { - const QString token = QUuid::createUuid().toString().remove("{").remove("}"); - AuthDefinition def = _pendingRequests.take(id); - _authTable->createToken(token, def.comment, id); - emit tokenResponse(true, def.caller, token, def.comment, id); - return true; + AuthDefinition def = _pendingRequests.value(id); + if (def.caller == caller) + _pendingRequests.remove(id); + emit newPendingTokenRequest(id, ""); } - return false; } -bool AuthManager::denyTokenRequest(const QString& id) +void AuthManager::handlePendingTokenRequest(const QString &id, const bool &accept) { - if(_pendingRequests.contains(id)) + if (_pendingRequests.contains(id)) { AuthDefinition def = _pendingRequests.take(id); - emit tokenResponse(false, def.caller, QString(), def.comment, id); + + if (accept) + { + const QString token = QUuid::createUuid().toString().remove("{").remove("}"); + _authTable->createToken(token, def.comment, id); + emit tokenResponse(true, def.caller, token, def.comment, id); + emit tokenChange(getTokenList()); + } + else + { + emit tokenResponse(false, def.caller, QString(), def.comment, id); + } + } +} + +QVector AuthManager::getPendingRequests() +{ + QVector finalVec; + for (const auto &entry : _pendingRequests) + { + AuthDefinition def; + def.comment = entry.comment; + def.id = entry.id; + def.timeoutTime = entry.timeoutTime - QDateTime::currentMSecsSinceEpoch(); + finalVec.append(def); + } + return finalVec; +} + +bool AuthManager::renameToken(const QString &id, const QString &comment) +{ + if (_authTable->renameToken(id, comment)) + { + emit tokenChange(getTokenList()); return true; } return false; } -const QMap AuthManager::getPendingRequests() +bool AuthManager::deleteToken(const QString &id) { - return _pendingRequests; -} - -bool AuthManager::deleteToken(const QString& id) -{ - if(_authTable->deleteToken(id)) + if (_authTable->deleteToken(id)) { - //emit tokenDeleted(token); + emit tokenChange(getTokenList()); return true; } return false; } -void AuthManager::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +void AuthManager::handleSettingsUpdate(const settings::type &type, const QJsonDocument &config) { - if(type == settings::NETWORK) + if (type == settings::NETWORK) { - const QJsonObject& obj = config.object(); + const QJsonObject &obj = config.object(); _authRequired = obj["apiAuth"].toBool(true); _localAuthRequired = obj["localApiAuth"].toBool(false); - _localAdminAuthRequired = obj["localAdminAuth"].toBool(false); + _localAdminAuthRequired = obj["localAdminAuth"].toBool(true); } } @@ -209,38 +244,43 @@ void AuthManager::checkTimeout() QMapIterator i(_pendingRequests); while (i.hasNext()) { - i.next(); + i.next(); - const AuthDefinition& def = i.value(); - if(def.timeoutTime <= now) + const AuthDefinition &def = i.value(); + if (def.timeoutTime <= now) { emit tokenResponse(false, def.caller, QString(), def.comment, def.id); _pendingRequests.remove(i.key()); } } // abort if empty - if(_pendingRequests.isEmpty()) + if (_pendingRequests.isEmpty()) _timer->stop(); } -void AuthManager::checkAuthBlockTimeout(){ +void AuthManager::checkAuthBlockTimeout() +{ // handle user auth block - for (auto it = _userAuthAttempts.begin(); it != _userAuthAttempts.end(); it++) { + for (auto it = _userAuthAttempts.begin(); it != _userAuthAttempts.end(); it++) + { // after 10 minutes, we remove the entry - if (*it < (uint64_t)QDateTime::currentMSecsSinceEpoch()) { + if (*it < (uint64_t)QDateTime::currentMSecsSinceEpoch()) + { _userAuthAttempts.erase(it--); } } // handle token auth block - for (auto it = _tokenAuthAttempts.begin(); it != _tokenAuthAttempts.end(); it++) { + for (auto it = _tokenAuthAttempts.begin(); it != _tokenAuthAttempts.end(); it++) + { // after 10 minutes, we remove the entry - if (*it < (uint64_t)QDateTime::currentMSecsSinceEpoch()) { + if (*it < (uint64_t)QDateTime::currentMSecsSinceEpoch()) + { _tokenAuthAttempts.erase(it--); } } // if the lists are empty we stop - if(_userAuthAttempts.empty() && _tokenAuthAttempts.empty()) + if (_userAuthAttempts.empty() && _tokenAuthAttempts.empty()) _authBlockTimer->stop(); } diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index cc6d7921..5bb26eac 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -290,13 +290,12 @@ unsigned Hyperion::getLedCount() const return _ledString.leds().size(); } -void Hyperion::setSourceAutoSelectEnabled(bool enabled) +void Hyperion::setSourceAutoSelect(const bool state) { - if(_muxer.setSourceAutoSelectEnabled(enabled)) - update(); + _muxer.setSourceAutoSelectEnabled(state); } -bool Hyperion::setCurrentSourcePriority(int priority ) +bool Hyperion::setVisiblePriority(const int& priority) { return _muxer.setPriority(priority); } @@ -465,14 +464,14 @@ const Hyperion::InputInfo Hyperion::getPriorityInfo(const int priority) const return _muxer.getInputInfo(priority); } -bool Hyperion::saveEffect(const QJsonObject& obj, QString& resultMsg) +QString Hyperion::saveEffect(const QJsonObject& obj) { - return _effectEngine->saveEffect(obj, resultMsg); + return _effectEngine->saveEffect(obj); } -bool Hyperion::deleteEffect(const QString& effectName, QString& resultMsg) +QString Hyperion::deleteEffect(const QString& effectName) { - return _effectEngine->deleteEffect(effectName, resultMsg); + return _effectEngine->deleteEffect(effectName); } const std::list & Hyperion::getEffects() const