mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2023-10-10 13:36:59 +02:00
- The first part
- Added CodeDocs config file for customization - Fixing LGTM alerts - LGTM bug fixed again - added token option to hyperion-remote - fix DBManager::getDB() - next bugfix - correct broken signal from SettingManager to Hyperion - Token list is created after the schema is fetched Signed-off-by: Paulchen-Panther <Paulchen-Panter@protonmail.com>
This commit is contained in:
parent
4fc745e748
commit
ea796160af
76
.codedocs
Normal file
76
.codedocs
Normal file
@ -0,0 +1,76 @@
|
||||
# Hyperion.NG .codedocs Configuration File
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# CodeDocs Configuration
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# Include the Doxygen configuration from another file.
|
||||
# The file must be a relative path with respect to the root of the repository.
|
||||
|
||||
DOXYFILE =
|
||||
|
||||
# Specify external repository to link documentation with.
|
||||
# This is similar to Doxygen's TAGFILES option, but will automatically link to
|
||||
# tags of other repositories already using CodeDocs. List each repository to
|
||||
# link with by giving its location in the form of owner/repository.
|
||||
# For example:
|
||||
# TAGLINKS = doxygen/doxygen CodeDocs/osg
|
||||
# Note: these repositories must already be built on CodeDocs.
|
||||
|
||||
TAGLINKS =
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Doxygen Configuration
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# Doxygen configuration may also be placed in this file.
|
||||
# Currently, the following Doxygen configuration options are available. Refer
|
||||
# to http://doxygen.org/manual/config.html for detailed explanation of the
|
||||
# options. To request support for more options, contact support@codedocs.xyz.
|
||||
#
|
||||
# ABBREVIATE_BRIEF =
|
||||
# ALIASES =
|
||||
# ALPHABETICAL_INDEX =
|
||||
# ALWAYS_DETAILED_SEC =
|
||||
# CASE_SENSE_NAMES =
|
||||
# CLASS_DIAGRAMS =
|
||||
# DISABLE_INDEX =
|
||||
# DISTRIBUTE_GROUP_DOC =
|
||||
# EXAMPLE_PATH =
|
||||
EXCLUDE = .ci/ \
|
||||
assets/ \
|
||||
bin/
|
||||
config/ \
|
||||
effects/ \
|
||||
test/ \
|
||||
# EXCLUDE_PATTERNS =
|
||||
# EXCLUDE_SYMBOLS =
|
||||
# EXTENSION_MAPPING =
|
||||
# EXTRACT_LOCAL_CLASSES =
|
||||
# FILE_PATTERNS =
|
||||
# GENERATE_TAGFILE =
|
||||
# GENERATE_TREEVIEW =
|
||||
# HIDE_COMPOUND_REFERENCE =
|
||||
# HIDE_SCOPE_NAMES =
|
||||
# HIDE_UNDOC_CLASSES =
|
||||
# HIDE_UNDOC_MEMBERS =
|
||||
# HTML_TIMESTAMP =
|
||||
# INLINE_GROUPED_CLASSES =
|
||||
# INPUT_ENCODING =
|
||||
# INTERNAL_DOCS =
|
||||
# OPTIMIZE_OUTPUT_FOR_C =
|
||||
PROJECT_BRIEF = "The successor to Hyperion aka Hyperion Next Generation"
|
||||
PROJECT_NAME = "Hyperion.NG"
|
||||
# PROJECT_NUMBER =
|
||||
# SHORT_NAMES =
|
||||
# SHOW_FILES =
|
||||
# SHOW_INCLUDE_FILES =
|
||||
# SHOW_NAMESPACES =
|
||||
# SORT_BRIEF_DOCS =
|
||||
# SORT_BY_SCOPE_NAME =
|
||||
# SORT_MEMBER_DOCS =
|
||||
# STRICT_PROTO_MATCHING =
|
||||
# TYPEDEF_HIDES_STRUCT =
|
||||
USE_MDFILE_AS_MAINPAGE = README.md
|
||||
# VERBATIM_HEADERS =
|
||||
#
|
@ -311,7 +311,7 @@ IF ( CMAKE_CROSSCOMPILING )
|
||||
ENDIF()
|
||||
|
||||
SET(QT_MIN_VERSION "5.5.0")
|
||||
find_package(Qt5 COMPONENTS Core Gui Network SerialPort REQUIRED)
|
||||
find_package(Qt5 COMPONENTS Core Gui Network SerialPort Sql REQUIRED)
|
||||
message( STATUS "Found Qt Version: ${Qt5Core_VERSION}" )
|
||||
IF ( "${Qt5Core_VERSION}" VERSION_LESS "${QT_MIN_VERSION}" )
|
||||
message( FATAL_ERROR "Your Qt version is to old! Minimum required ${QT_MIN_VERSION}" )
|
||||
@ -354,9 +354,6 @@ endif ()
|
||||
# Add resources directory
|
||||
add_subdirectory(resources)
|
||||
|
||||
# Add the doxygen generation directory
|
||||
add_subdirectory(doc)
|
||||
|
||||
# remove generated files on make cleaan too
|
||||
LIST( APPEND GENERATED_QRC
|
||||
${CMAKE_BINARY_DIR}/EffectEngine.qrc
|
||||
|
@ -4,6 +4,7 @@
|
||||
[![Azure-Pipeline](https://dev.azure.com/Hyperion-Project/Hyperion.NG/_apis/build/status/Hyperion.NG?branchName=master)](https://dev.azure.com/Hyperion-Project/Hyperion.NG/_build/latest?definitionId=7&branchName=master)
|
||||
[![Travis-CI](https://travis-ci.org/hyperion-project/hyperion.ng.svg?branch=master)](https://travis-ci.org/hyperion-project/hyperion.ng)
|
||||
[![LGTM](https://img.shields.io/lgtm/alerts/g/hyperion-project/hyperion.ng.svg)](https://lgtm.com/projects/g/hyperion-project/hyperion.ng/alerts/)
|
||||
[![Documentation](https://codedocs.xyz/hyperion-project/hyperion.ng.svg)](https://codedocs.xyz/hyperion-project/hyperion.ng/)
|
||||
|
||||
## About Hyperion
|
||||
|
||||
|
@ -18,6 +18,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6" id="tok_desc">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><i class="fa fa-key fa-fw"></i><span data-i18n="conf_general_tok_title"></span></div>
|
||||
<div class="panel-body">
|
||||
<div id="tok_desc_cont"></div>
|
||||
<div id="tktable"></div>
|
||||
<div style="margin: 30px 0; border-top:1px solid #d0d0d0"></div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<p data-i18n="conf_general_tok_comment_title" style="font-weight:bold"></p>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<input class="form-control" id="tok_comment" type="text"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-primary" id="btn_create_tok" data-i18n="conf_general_createToken_btn" disabled>Create New Token</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -33,6 +33,7 @@
|
||||
"general_btn_ok": "OK",
|
||||
"general_btn_cancel": "Abbrechen",
|
||||
"general_btn_continue": "Fortfahren",
|
||||
"general_btn_delete" : "Löschen",
|
||||
"general_btn_save": "Speichern",
|
||||
"general_btn_saverestart": "Speichern und neustarten",
|
||||
"general_btn_saveandreload": "Speichern und neu laden",
|
||||
@ -99,6 +100,14 @@
|
||||
"conf_general_impexp_l2": "Exportiere eine Konfiguration, indem du auf \"Exportieren\" klickst. Dein Browser startet einen Download.",
|
||||
"conf_general_impexp_impbtn": "Importieren",
|
||||
"conf_general_impexp_expbtn": "Exportieren",
|
||||
"conf_general_tok_title" : "Token Management",
|
||||
"conf_general_tok_desc" : "Tokens erlauben andere Anwendungen auf die Hyperion API zuzugreifen. Eine Anwendung kann ein Token anfordern welches von dir bestätigt werden muss oder du erstellst dir selbst ein neues Token. Diese Tokens werden nur benötigt, wenn \"API Autorisierung\" in den Netzwerkeinstellungen aktiviert ist.",
|
||||
"conf_general_tok_cidhead" : "Beschreibung",
|
||||
"conf_general_tok_lastuse" : "Zuletzt genutzt",
|
||||
"conf_general_tok_comment_title" : "Token Beschreibung",
|
||||
"conf_general_createToken_btn" : "Erstelle Token",
|
||||
"conf_general_tok_diaTitle" : "Neues Token erstellt!",
|
||||
"conf_general_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_helptable_option": "Option",
|
||||
"conf_helptable_expl": "Erklärung",
|
||||
"conf_effect_path_intro": "Hier kannst du Ordner angeben, die beim Laden von Effekten berücksichtig werden sollen. Zusätzlich können Effekte anhand ihres Namens deaktiviert werden um sie aus Listen zu löschen.",
|
||||
@ -167,6 +176,7 @@
|
||||
"conf_colors_color_intro": "Erstelle Kalibrierungsprofile die einzelnen Komponenten zugewisen werden können. Passe dabei Farben, Gamma, Helligkeit, Kompensation und mehr an.",
|
||||
"conf_colors_smoothing_intro": "Glätte den Farbverlauf und Helligkeitsänderungen um nicht von schnellen Übergängen abgelenkt zu werden.",
|
||||
"conf_colors_blackborder_intro": "Ignoriere schwarze Balken, jeder Modus nutzt einen anderen Algorithmus um diese zu erkennen. Erhöhe die Schwelle, sollte es nicht funktionieren.",
|
||||
"conf_network_net_intro" : "Einstellungen zum Netzwerk die für alle Netzwerk-Dienste gelten",
|
||||
"conf_network_json_intro": "Der JSON-RPC-Port dieser Hyperion-Instanz, wird genutzt zur Fernsteuerung.",
|
||||
"conf_network_bobl_intro": "Boblight Empfänger",
|
||||
"conf_network_udpl_intro": "UDP Empfänger",
|
||||
@ -575,6 +585,16 @@
|
||||
"edt_conf_fw_proto_title": "Liste von Proto zielen",
|
||||
"edt_conf_fw_proto_expl": "Ein Proto Ziel pro Zeile. Bestehend aus IP:PORT (Beispiel: 127.0.0.1:19401)",
|
||||
"edt_conf_fw_proto_itemtitle": "Proto Ziel",
|
||||
"edt_conf_net_heading_title" : "Network",
|
||||
"edt_conf_net_internetAccessAPI_title":"Internet API Zugriff",
|
||||
"edt_conf_net_internetAccessAPI_expl":"Erlaube Zugriff auf die Hyperion API/Webinterface aus dem Internet, deaktivieren für höhere Sicherheit.",
|
||||
"edt_conf_net_ipWhitelist_title":"Erlaubte IP's",
|
||||
"edt_conf_net_ipWhitelist_expl":"Anstatt den Zugriff für alle Verbindungen aus dem Internet zu erlauben kannst du hier Ausnahmen für zugelassene IP Adressen hinzufügen.",
|
||||
"edt_conf_net_ip_itemtitle":"IP",
|
||||
"edt_conf_net_apiAuth_title":"API Authentifizierung",
|
||||
"edt_conf_net_apiAuth_expl":"Zwinge alle Anwendungen welche die Hyperion API nutzen sich zu authentifizieren. Aktivieren für höhere Sicherheit, da nun jede neue Anwendung einmalig von dir bestätigt werden muss.",
|
||||
"edt_conf_net_localApiAuth_title" : "Lokale API Authentifizierung",
|
||||
"edt_conf_net_localApiAuth_expl" : "Wenn aktiviert, müssen Verbindungen aus dem Heimnetzwerk mit einem Token authentifiziert werden.",
|
||||
"edt_conf_js_heading_title": "JSON Server",
|
||||
"edt_conf_fbs_heading_title": "Flatbuffers Server",
|
||||
"edt_conf_fbs_timeout_title": "Zeitüberschreitung",
|
||||
|
@ -32,6 +32,7 @@
|
||||
"general_btn_yes" : "Yes",
|
||||
"general_btn_ok" : "OK",
|
||||
"general_btn_cancel" : "Cancel",
|
||||
"general_btn_delete" : "Delete",
|
||||
"general_btn_continue" : "Continue",
|
||||
"general_btn_save" : "Save",
|
||||
"general_btn_saverestart" : "Save and restart",
|
||||
@ -99,6 +100,14 @@
|
||||
"conf_general_impexp_l2" : "Export a configuration by clicking on \"Export\". Your browser starts a download.",
|
||||
"conf_general_impexp_impbtn" : "Import",
|
||||
"conf_general_impexp_expbtn" : "Export",
|
||||
"conf_general_tok_title" : "Token management",
|
||||
"conf_general_tok_desc" : "Tokens grant other applications access to the Hyperion API, an application can request a token where you need to accept it or you create them on your own below. These tokens are just required when \"API Authorization\" is enabled in network settings.",
|
||||
"conf_general_tok_cidhead" : "Description",
|
||||
"conf_general_tok_lastuse" : "Last use",
|
||||
"conf_general_tok_comment_title" : "Token description",
|
||||
"conf_general_createToken_btn" : "Create Token",
|
||||
"conf_general_tok_diaTitle" : "New Token created!",
|
||||
"conf_general_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 now.",
|
||||
"conf_helptable_option" : "Option",
|
||||
"conf_helptable_expl" : "Explanation",
|
||||
"conf_effect_path_intro" : "Load effects from the defined paths. Additional you can disable single effects by name to hide them from all effect lists.",
|
||||
@ -167,6 +176,7 @@
|
||||
"conf_colors_color_intro" : "Create one or more calibration profiles, adjust each color, brightness, linearization and more.",
|
||||
"conf_colors_smoothing_intro" : "Smoothing flattens color/brightness changes to reduce annoying distraction.",
|
||||
"conf_colors_blackborder_intro" : "Skip black bars wherever they are. Each mode use another detection algorithm which is tuned for special situations. Higher the threshold if it doesn't work for you.",
|
||||
"conf_network_net_intro" : "Network related settings which are applied to all network services.",
|
||||
"conf_network_json_intro" : "The JSON-RPC-Port of this Hyperion instance, used for remote control.",
|
||||
"conf_network_bobl_intro" : "Receiver for Boblight",
|
||||
"conf_network_udpl_intro" : "Receiver for UDP",
|
||||
@ -575,6 +585,16 @@
|
||||
"edt_conf_fw_proto_title" : "List of proto clients",
|
||||
"edt_conf_fw_proto_expl" : "One proto target per line. Contains IP:PORT (Example: 127.0.0.1:19401)",
|
||||
"edt_conf_fw_proto_itemtitle" : "Proto target",
|
||||
"edt_conf_net_heading_title" : "Network",
|
||||
"edt_conf_net_internetAccessAPI_title":"Internet API Access",
|
||||
"edt_conf_net_internetAccessAPI_expl":"Allow access to the Hyperion API/Webinterface from the internet, disable for higher security.",
|
||||
"edt_conf_net_ipWhitelist_title":"Whitelisted IP's",
|
||||
"edt_conf_net_ipWhitelist_expl":"You can whitelist IP addresses instead allowing all connections from internet to connect to the Hyperion API/Webinterface.",
|
||||
"edt_conf_net_ip_itemtitle":"IP",
|
||||
"edt_conf_net_apiAuth_title":"API Authentication",
|
||||
"edt_conf_net_apiAuth_expl":"Enforce all applications that use the Hyperion API to authenticate themself against Hyperion. Higher security, as you control the access and revoke it at any time.",
|
||||
"edt_conf_net_localApiAuth_title" : "Local API Authentication",
|
||||
"edt_conf_net_localApiAuth_expl" : "When enabled, connections from your home network needs to authenticate themself against Hyperion too.",
|
||||
"edt_conf_js_heading_title" : "JSON Server",
|
||||
"edt_conf_fbs_heading_title" : "Flatbuffers Server",
|
||||
"edt_conf_fbs_timeout_title" : "Timeout",
|
||||
|
@ -25,6 +25,50 @@ $(document).ready( function() {
|
||||
requestWriteConfig(conf_editor.getValue());
|
||||
});
|
||||
|
||||
// Token handling
|
||||
function buildTokenList()
|
||||
{
|
||||
console.log(tokenList)
|
||||
$('.tktbody').html("");
|
||||
for(var key in tokenList)
|
||||
{
|
||||
var lastUse = (tokenList[key].last_use) ? tokenList[key].last_use : "-";
|
||||
var btn = '<button id="tok'+tokenList[key].id+'" type="button" class="btn btn-danger">'+$.i18n('general_btn_delete')+'</button>';
|
||||
$('.tktbody').append(createTableRow([tokenList[key].comment, lastUse, btn], false, true));
|
||||
$('#tok'+tokenList[key].id).off().on('click', handleDeleteToken);
|
||||
}
|
||||
}
|
||||
|
||||
createTable('tkthead', 'tktbody', 'tktable');
|
||||
$('.tkthead').html(createTableRow([$.i18n('conf_general_tok_cidhead'), $.i18n('conf_general_tok_lastuse'), $.i18n('general_btn_delete')], true, true));
|
||||
buildTokenList();
|
||||
|
||||
function handleDeleteToken(e)
|
||||
{
|
||||
var key = e.currentTarget.id.replace("tok","");
|
||||
requestTokenDelete(key);
|
||||
$('#tok'+key).parent().parent().remove();
|
||||
// rm deleted token id
|
||||
tokenList = tokenList.filter(function( obj ) {
|
||||
return obj.id !== key;
|
||||
});
|
||||
}
|
||||
|
||||
$('#btn_create_tok').off().on('click',function() {
|
||||
requestToken($('#tok_comment').val())
|
||||
$('#tok_comment').val("")
|
||||
$('#btn_create_tok').attr('disabled', true)
|
||||
});
|
||||
$('#tok_comment').off().on('input',function(e) {
|
||||
(e.currentTarget.value.length >= 10) ? $('#btn_create_tok').attr('disabled', false) : $('#btn_create_tok').attr('disabled', true);
|
||||
});
|
||||
$(window.hyperion).off("cmd-authorize-createToken").on("cmd-authorize-createToken", function(event) {
|
||||
var val = event.response.info;
|
||||
showInfoDialog("newToken",$.i18n('conf_general_tok_diaTitle'),$.i18n('conf_general_tok_diaMsg')+'<br><div style="font-weight:bold">'+val.token+'</div>')
|
||||
tokenList.push(val)
|
||||
buildTokenList()
|
||||
});
|
||||
|
||||
//import
|
||||
function dis_imp_btn(state)
|
||||
{
|
||||
@ -104,6 +148,7 @@ $(document).ready( function() {
|
||||
//create introduction
|
||||
if(window.showOptHelp)
|
||||
createHint("intro", $.i18n('conf_general_intro'), "editor_container");
|
||||
createHint("intro", $.i18n('conf_general_tok_desc'), "tok_desc_cont");
|
||||
|
||||
removeOverlay();
|
||||
});
|
||||
|
@ -34,6 +34,11 @@ $(document).ready( function() {
|
||||
updateSessions();
|
||||
});
|
||||
|
||||
$(window.hyperion).one("cmd-authorize-getTokenList", function(event) {
|
||||
tokenList = event.response.info;
|
||||
requestServerInfo();
|
||||
});
|
||||
|
||||
$(window.hyperion).on("cmd-sysinfo", function(event) {
|
||||
requestServerInfo();
|
||||
window.sysInfo = event.response.info;
|
||||
@ -45,6 +50,7 @@ $(document).ready( function() {
|
||||
$(window.hyperion).one("cmd-config-getschema", function(event) {
|
||||
window.serverSchema = event.response.info;
|
||||
requestServerConfig();
|
||||
requestTokenInfo();
|
||||
|
||||
window.schema = window.serverSchema.properties;
|
||||
});
|
||||
@ -62,12 +68,16 @@ $(document).ready( function() {
|
||||
}
|
||||
});
|
||||
|
||||
$(window.hyperion).one("cmd-authorize-login", function(event) {
|
||||
requestServerConfigSchema();
|
||||
});
|
||||
|
||||
$(window.hyperion).on("error",function(event){
|
||||
showInfoDialog("error","Error", event.reason);
|
||||
});
|
||||
|
||||
$(window.hyperion).on("open",function(event){
|
||||
requestServerConfigSchema();
|
||||
requestAuthorization();
|
||||
});
|
||||
|
||||
$(window.hyperion).one("ready", function(event) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
$(document).ready( function() {
|
||||
performTranslation();
|
||||
|
||||
var conf_editor_net = null;
|
||||
var conf_editor_json = null;
|
||||
var conf_editor_proto = null;
|
||||
var conf_editor_fbs = null;
|
||||
@ -10,6 +11,11 @@ $(document).ready( function() {
|
||||
|
||||
if(window.showOptHelp)
|
||||
{
|
||||
//network
|
||||
$('#conf_cont').append(createRow('conf_cont_net'))
|
||||
$('#conf_cont_net').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_net_heading_title"), 'editor_container_net', 'btn_submit_net'));
|
||||
$('#conf_cont_net').append(createHelpTable(window.schema.network.properties, $.i18n("edt_conf_net_heading_title")));
|
||||
|
||||
//jsonserver
|
||||
$('#conf_cont').append(createRow('conf_cont_json'))
|
||||
$('#conf_cont_json').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_js_heading_title"), 'editor_container_jsonserver', 'btn_submit_jsonserver'));
|
||||
@ -46,6 +52,7 @@ $(document).ready( function() {
|
||||
else
|
||||
{
|
||||
$('#conf_cont').addClass('row');
|
||||
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_net_heading_title"), 'editor_container_net', 'btn_submit_net'));
|
||||
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_js_heading_title"), 'editor_container_jsonserver', 'btn_submit_jsonserver'));
|
||||
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_fbs_heading_title"), 'editor_container_fbserver', 'btn_submit_fbserver'));
|
||||
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_pbs_heading_title"), 'editor_container_protoserver', 'btn_submit_protoserver'));
|
||||
@ -55,6 +62,19 @@ $(document).ready( function() {
|
||||
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_fw_heading_title"), 'editor_container_forwarder', 'btn_submit_forwarder'));
|
||||
}
|
||||
|
||||
// net
|
||||
conf_editor_net = createJsonEditor('editor_container_net', {
|
||||
network : window.schema.network
|
||||
}, true, true);
|
||||
|
||||
conf_editor_net.on('change',function() {
|
||||
conf_editor_net.validate().length ? $('#btn_submit_net').attr('disabled', true) : $('#btn_submit_net').attr('disabled', false);
|
||||
});
|
||||
|
||||
$('#btn_submit_net').off().on('click',function() {
|
||||
requestWriteConfig(conf_editor_net.getValue());
|
||||
});
|
||||
|
||||
//json
|
||||
conf_editor_json = createJsonEditor('editor_container_jsonserver', {
|
||||
jsonServer : window.schema.jsonServer
|
||||
@ -139,6 +159,7 @@ $(document).ready( function() {
|
||||
//create introduction
|
||||
if(window.showOptHelp)
|
||||
{
|
||||
createHint("intro", $.i18n('conf_network_net_intro'), "editor_container_net");
|
||||
createHint("intro", $.i18n('conf_network_json_intro'), "editor_container_jsonserver");
|
||||
createHint("intro", $.i18n('conf_network_fbs_intro'), "editor_container_fbserver");
|
||||
createHint("intro", $.i18n('conf_network_proto_intro'), "editor_container_protoserver");
|
||||
|
@ -26,6 +26,7 @@ window.watchdog = 0;
|
||||
window.debugMessagesActive = true;
|
||||
window.wSess = [];
|
||||
window.comps = [];
|
||||
tokenList = {};
|
||||
|
||||
function initRestart()
|
||||
{
|
||||
@ -162,7 +163,31 @@ function sendToHyperion(command, subcommand, msg)
|
||||
// -----------------------------------------------------------
|
||||
// wrapped server commands
|
||||
|
||||
// also used for watchdog
|
||||
function requestAuthorization()
|
||||
{
|
||||
sendToHyperion("authorize","login",'"username": "Hyperion", "password": "hyperion"');
|
||||
}
|
||||
|
||||
function requestToken(comment)
|
||||
{
|
||||
sendToHyperion("authorize","createToken",'"comment": "'+comment+'"');
|
||||
}
|
||||
|
||||
function requestTokenInfo()
|
||||
{
|
||||
sendToHyperion("authorize","getTokenList","");
|
||||
}
|
||||
|
||||
function requestHandleTokenRequest(id, state)
|
||||
{
|
||||
sendToHyperion("authorize","answerRequest",'"id":"'+id+'", "accept":'+state);
|
||||
}
|
||||
|
||||
function requestTokenDelete(id)
|
||||
{
|
||||
sendToHyperion("authorize","deleteToken",'"id":"'+id+'"');
|
||||
}
|
||||
|
||||
function requestServerInfo()
|
||||
{
|
||||
sendToHyperion("serverinfo","",'"subscribe":["components-update","sessions-update","priorities-update", "imageToLedMapping-update", "adjustment-update", "videomode-update", "effects-update", "settings-update"]');
|
||||
|
@ -125,6 +125,7 @@ $(document).ready( function() {
|
||||
for (var i = 0; i<window.wSess.length; i++)
|
||||
{
|
||||
if(lsys != window.wSess[i].host+':'+window.wSess[i].port)
|
||||
{
|
||||
var hyperionAddress
|
||||
|
||||
if (window.wSess[i].address.indexOf(':') > -1 && window.wSess[i].address.length == 36)
|
||||
@ -134,6 +135,7 @@ $(document).ready( function() {
|
||||
|
||||
$('#id_select').append(createSelOpt(hyperionAddress, window.wSess[i].host))
|
||||
}
|
||||
}
|
||||
|
||||
$('#id_btn_saveset').off().on('click',function() {
|
||||
$("#loading_overlay").addClass("overlay");
|
||||
|
@ -198,6 +198,17 @@ function showInfoDialog(type,header,message)
|
||||
$('#id_body').append(message);
|
||||
$('#id_footer').html('<button type="button" class="btn btn-primary" data-dismiss="modal">'+$.i18n('general_btn_ok')+'</button>');
|
||||
}
|
||||
else if (type == "newToken")
|
||||
{
|
||||
$('#id_body').html('<img style="margin-bottom:20px" src="img/hyperion/hyperionlogo.png" alt="Redefine ambient light!">');
|
||||
$('#id_footer').html('<button type="button" class="btn btn-primary" data-dismiss="modal">'+$.i18n('general_btn_ok')+'</button>');
|
||||
}
|
||||
else if (type == "grantToken")
|
||||
{
|
||||
$('#id_body').html('<img style="margin-bottom:20px" src="img/hyperion/hyperionlogo.png" alt="Redefine ambient light!">');
|
||||
$('#id_footer').html('<button type="button" class="btn btn-primary" data-dismiss="modal" id="tok_grant_acc">'+$.i18n('general_btn_grantAccess')+'</button>');
|
||||
$('#id_footer').append('<button type="button" class="btn btn-danger" data-dismiss="modal" id="tok_deny_acc">'+$.i18n('general_btn_denyAccess')+'</button>');
|
||||
}
|
||||
|
||||
$('#id_body').append('<h4 style="font-weight:bold;text-transform:uppercase;">'+header+'</h4>');
|
||||
$('#id_body').append(message);
|
||||
|
@ -7,5 +7,6 @@ STRING ( STRIP "${BUILD_ID}" BUILD_ID )
|
||||
STRING ( STRIP "${VERSION_ID}" VERSION_ID )
|
||||
STRING ( STRIP "${GIT_REMOTE_PATH}" GIT_REMOTE_PATH )
|
||||
SET ( HYPERION_BUILD_ID "${VERSION_ID} (${BUILD_ID}) Git Remote: ${GIT_REMOTE_PATH}" )
|
||||
SET ( HYPERION_VERSION "${HYPERION_VERSION_CHANNEL}.${HYPERION_VERSION_MAJOR}.${HYPERION_VERSION_MINOR}.${HYPERION_VERSION_PATCH}" )
|
||||
message ( STATUS "Current Version: ${HYPERION_VERSION} (${HYPERION_BUILD_ID})" )
|
||||
SET ( HYPERION_VERSION "${HYPERION_VERSION_CHANNEL} ${HYPERION_VERSION_MAJOR}.${HYPERION_VERSION_MINOR}.${HYPERION_VERSION_PATCH}" )
|
||||
message ( STATUS "Current Version: ${HYPERION_VERSION}" )
|
||||
message ( STATUS " - Build: ${HYPERION_BUILD_ID}" )
|
||||
|
@ -13,11 +13,11 @@ ENDIF()
|
||||
find_package(RpmBuilder)
|
||||
find_package(DebBuilder)
|
||||
IF(RPM_BUILDER_FOUND)
|
||||
message("CPACK: Found RPM builder")
|
||||
message(STATUS "CPACK: Found RPM builder")
|
||||
SET ( CPACK_GENERATOR ${CPACK_GENERATOR} "RPM")
|
||||
ENDIF()
|
||||
IF(DEB_BUILDER_FOUND)
|
||||
message("CPACK: Found DEB builder")
|
||||
message(STATUS "CPACK: Found DEB builder")
|
||||
SET ( CPACK_GENERATOR ${CPACK_GENERATOR} "DEB")
|
||||
ENDIF()
|
||||
|
||||
|
@ -307,13 +307,22 @@
|
||||
]
|
||||
},
|
||||
|
||||
"instCapture" : {
|
||||
"instCapture" :
|
||||
{
|
||||
"systemEnable" : true,
|
||||
"systemPriority" : 250,
|
||||
"v4lEnable" : false,
|
||||
"v4lPriority" : 240
|
||||
},
|
||||
|
||||
"network" :
|
||||
{
|
||||
"internetAccessAPI" : false,
|
||||
"ipWhitelist" : [],
|
||||
"apiAuth" : true,
|
||||
"localApiAuth" : false
|
||||
},
|
||||
|
||||
/// Recreate and save led layouts made with web config. These values are just helpers for ui, not for Hyperion.
|
||||
"ledConfig" :
|
||||
{
|
||||
|
@ -173,13 +173,22 @@
|
||||
"disable": [""]
|
||||
},
|
||||
|
||||
"instCapture" : {
|
||||
"instCapture" :
|
||||
{
|
||||
"systemEnable" : true,
|
||||
"systemPriority" : 250,
|
||||
"v4lEnable" : false,
|
||||
"v4lPriority" : 240
|
||||
},
|
||||
|
||||
"network" :
|
||||
{
|
||||
"internetAccessAPI" : false,
|
||||
"ipWhitelist" : [],
|
||||
"apiAuth" : true,
|
||||
"localApiAuth" : false
|
||||
},
|
||||
|
||||
"ledConfig" :
|
||||
{
|
||||
"top" : 8,
|
||||
|
@ -1,34 +0,0 @@
|
||||
option(BUILD_HYPERION_DOC "Build hyperion documentation" OFF)
|
||||
|
||||
# Find doxygen and check if Doxygen is installed
|
||||
find_package(Doxygen QUIET)
|
||||
|
||||
if (BUILD_HYPERION_DOC)
|
||||
if (DOXYGEN_FOUND)
|
||||
|
||||
# set input and output files
|
||||
set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/hyperion.in.doxygen)
|
||||
set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/hyperion.doxygen)
|
||||
|
||||
# request to configure the file
|
||||
configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
|
||||
message(STATUS "Doxygen build started")
|
||||
|
||||
# Define all static (i.e. not generated) documentation files
|
||||
set(StaticDocumentationFiles hyperion-footer.html)
|
||||
|
||||
# Loop over all static documentation files
|
||||
foreach(StaticDocumentationFile ${StaticDocumentationFiles})
|
||||
# Copy the file to the bindary documentation directory
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${StaticDocumentationFile} ${CMAKE_CURRENT_BINARY_DIR}/html/${StaticDocumentationFile} COPYONLY)
|
||||
endforeach()
|
||||
|
||||
add_custom_target( doc_doxygen ALL
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Generating API documentation with Doxygen"
|
||||
VERBATIM )
|
||||
else(DOXYGEN_FOUND)
|
||||
message(WARNING "Doxygen not found, unable to generate documenation!")
|
||||
endif(DOXYGEN_FOUND)
|
||||
endif()
|
@ -1,8 +0,0 @@
|
||||
<hr>
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td><address style="text-align: right;"><small>Generated at $datetime for $projectname by <a href="http://www.doxygen.org">doxygen</a> $doxygenversion.</small></address></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
@ -2234,7 +2234,7 @@ INTERACTIVE_SVG = NO
|
||||
# found. If left blank, it is assumed the dot tool can be found in the path.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_PATH = ""
|
||||
DOT_PATH =
|
||||
|
||||
# The DOTFILE_DIRS tag can be used to specify one or more directories that
|
||||
# contain dot files that are included in the documentation (see the \dotfile
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <QString>
|
||||
|
||||
class JsonCB;
|
||||
class AuthManager;
|
||||
|
||||
class JsonAPI : public QObject
|
||||
{
|
||||
@ -24,16 +25,17 @@ public:
|
||||
/// @param peerAddress provide the Address of the peer
|
||||
/// @param log The Logger class of the creator
|
||||
/// @param parent Parent QObject
|
||||
/// @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, 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);
|
||||
void handleMessage(const QString & message, const QString& httpAuthHeader = "");
|
||||
|
||||
public slots:
|
||||
///
|
||||
@ -48,6 +50,24 @@ public slots:
|
||||
/// process and push new log messages from logger (if enabled)
|
||||
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
|
||||
/// @param id The id of the request
|
||||
/// @param The comment which needs to be accepted
|
||||
///
|
||||
void handlePendingTokenRequest(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);
|
||||
|
||||
signals:
|
||||
///
|
||||
/// Signal emits with the reply message provided with handleMessage()
|
||||
@ -60,6 +80,16 @@ signals:
|
||||
void forwardJsonMessage(QJsonObject);
|
||||
|
||||
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;
|
||||
|
||||
@ -214,6 +244,21 @@ private:
|
||||
///
|
||||
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)
|
||||
///
|
||||
const bool handleHTTPAuth(const QString& command, const int& tan, const QString& token);
|
||||
|
||||
///
|
||||
/// Handle an incoming JSON Clearall message
|
||||
///
|
||||
|
226
include/db/AuthTable.h
Normal file
226
include/db/AuthTable.h
Normal file
@ -0,0 +1,226 @@
|
||||
#pragma once
|
||||
|
||||
// hyperion
|
||||
#include <db/DBManager.h>
|
||||
#include <QCryptographicHash>
|
||||
|
||||
// qt
|
||||
#include <QDateTime>
|
||||
#include <QUuid>
|
||||
|
||||
///
|
||||
/// @brief Authentication table interface
|
||||
///
|
||||
class AuthTable : public DBManager
|
||||
{
|
||||
|
||||
public:
|
||||
/// construct wrapper with auth table
|
||||
AuthTable(const QString& rootPath, QObject* parent = nullptr)
|
||||
: DBManager(parent)
|
||||
{
|
||||
// Init Hyperion database usage
|
||||
setRootPath(rootPath);
|
||||
setDB("hyperion");
|
||||
|
||||
// init Auth table
|
||||
setTable("auth");
|
||||
// create table columns
|
||||
createTable(QStringList()<<"user TEXT"<<"password BLOB"<<"token BLOB"<<"salt BLOB"<<"comment TEXT"<<"id TEXT"<<"created_at TEXT"<<"last_use TEXT");
|
||||
};
|
||||
~AuthTable(){};
|
||||
|
||||
///
|
||||
/// @brief Create a user record, if called on a existing user the auth is recreated
|
||||
/// @param[in] user The username
|
||||
/// @param[in] pw The password
|
||||
/// @return true on success else false
|
||||
///
|
||||
inline bool createUser(const QString& user, const QString& pw)
|
||||
{
|
||||
// new salt
|
||||
QByteArray salt = QCryptographicHash::hash(QUuid::createUuid().toByteArray(), QCryptographicHash::Sha512).toHex();
|
||||
QVariantMap map;
|
||||
map["user"] = user;
|
||||
map["salt"] = salt;
|
||||
map["password"] = hashPasswordWithSalt(pw,salt);
|
||||
map["created_at"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
|
||||
|
||||
VectorPair cond;
|
||||
cond.append(CPair("user",user));
|
||||
return createRecord(cond, map);
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Test if user record exists
|
||||
/// @param[in] user The user id
|
||||
/// @return true on success else false
|
||||
///
|
||||
inline bool userExist(const QString& user)
|
||||
{
|
||||
VectorPair cond;
|
||||
cond.append(CPair("user",user));
|
||||
return recordExists(cond);
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Test if a user is authorized for access with given pw.
|
||||
/// @param user The user name
|
||||
/// @param pw The password
|
||||
/// @return True on success else false
|
||||
///
|
||||
inline bool isUserAuthorized(const QString& user, const QString& pw)
|
||||
{
|
||||
if(userExist(user) && (calcPasswordHashOfUser(user, pw) == getPasswordHashOfUser(user)))
|
||||
{
|
||||
updateUserUsed(user);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Update 'last_use' column entry for the corresponding user
|
||||
/// @param[in] user The user to search for
|
||||
///
|
||||
inline void updateUserUsed(const QString& user)
|
||||
{
|
||||
QVariantMap map;
|
||||
map["last_use"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
|
||||
|
||||
VectorPair cond;
|
||||
cond.append(CPair("user", user));
|
||||
updateRecord(cond, map);
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Test if token record exists, updates last_use on success
|
||||
/// @param[in] token The token id
|
||||
/// @return true on success else false
|
||||
///
|
||||
inline bool tokenExist(const QString& token)
|
||||
{
|
||||
QVariantMap map;
|
||||
map["last_use"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
|
||||
|
||||
VectorPair cond;
|
||||
cond.append(CPair("token", hashToken(token)));
|
||||
if(recordExists(cond))
|
||||
{
|
||||
// update it
|
||||
createRecord(cond,map);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Create a new token record with comment
|
||||
/// @param[in] token The token id as plaintext
|
||||
/// @param[in] comment The comment for the token (eg a human readable identifier)
|
||||
/// @param[in] id The id for the token
|
||||
/// @return true on success else false
|
||||
///
|
||||
inline bool createToken(const QString& token, const QString& comment, const QString& id)
|
||||
{
|
||||
QVariantMap map;
|
||||
map["comment"] = comment;
|
||||
map["id"] = idExist(id) ? QUuid::createUuid().toString().remove("{").remove("}").left(5) : id;
|
||||
map["created_at"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
|
||||
|
||||
VectorPair cond;
|
||||
cond.append(CPair("token", hashToken(token)));
|
||||
return createRecord(cond, map);
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Delete token record by id
|
||||
/// @param[in] id The token id
|
||||
/// @return true on success else false
|
||||
///
|
||||
inline bool deleteToken(const QString& id)
|
||||
{
|
||||
VectorPair cond;
|
||||
cond.append(CPair("id", id));
|
||||
return deleteRecord(cond);
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Get all 'comment', 'last_use' and 'id' column entries
|
||||
/// @return A vector of all lists
|
||||
///
|
||||
inline const QVector<QVariantMap> getTokenList()
|
||||
{
|
||||
QVector<QVariantMap> results;
|
||||
getRecords(results, QStringList() << "comment" << "id" << "last_use");
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Test if id exists
|
||||
/// @param[in] id The id
|
||||
/// @return true on success else false
|
||||
///
|
||||
inline bool idExist(const QString& id)
|
||||
{
|
||||
|
||||
VectorPair cond;
|
||||
cond.append(CPair("id", id));
|
||||
return recordExists(cond);
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Get the passwort hash of a user from db
|
||||
/// @param user The user name
|
||||
/// @return password as hash
|
||||
///
|
||||
inline const QByteArray getPasswordHashOfUser(const QString& user)
|
||||
{
|
||||
QVariantMap results;
|
||||
VectorPair cond;
|
||||
cond.append(CPair("user", user));
|
||||
getRecord(cond, results, QStringList()<<"password");
|
||||
|
||||
return results["password"].toByteArray();
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Calc the password hash of a user based on user name and password
|
||||
/// @param user The user name
|
||||
/// @param pw The password
|
||||
/// @return The calced password hash
|
||||
///
|
||||
inline const QByteArray calcPasswordHashOfUser(const QString& user, const QString& pw)
|
||||
{
|
||||
// get salt
|
||||
QVariantMap results;
|
||||
VectorPair cond;
|
||||
cond.append(CPair("user", user));
|
||||
getRecord(cond, results, QStringList()<<"salt");
|
||||
|
||||
// calc
|
||||
return hashPasswordWithSalt(pw,results["salt"].toByteArray());
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Create a password hash of plaintex password + salt
|
||||
/// @param pw The plaintext password
|
||||
/// @param salt The salt
|
||||
/// @return The password hash with salt
|
||||
///
|
||||
inline const QByteArray hashPasswordWithSalt(const QString& pw, const QByteArray& salt)
|
||||
{
|
||||
return QCryptographicHash::hash(pw.toUtf8().append(salt), QCryptographicHash::Sha512).toHex();
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Create a token hash
|
||||
/// @param token The plaintext token
|
||||
/// @return The token hash
|
||||
///
|
||||
inline const QByteArray hashToken(const QString& token)
|
||||
{
|
||||
return QCryptographicHash::hash(token.toUtf8(), QCryptographicHash::Sha512).toHex();
|
||||
}
|
||||
};
|
130
include/db/DBManager.h
Normal file
130
include/db/DBManager.h
Normal file
@ -0,0 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/Logger.h>
|
||||
#include <QMap>
|
||||
#include <QVariant>
|
||||
#include <QPair>
|
||||
#include <QVector>
|
||||
|
||||
class QSqlDatabase;
|
||||
class QSqlQuery;
|
||||
|
||||
typedef QPair<QString,QVariant> CPair;
|
||||
typedef QVector<CPair> VectorPair;
|
||||
|
||||
///
|
||||
/// @brief Database interface for SQLite3.
|
||||
/// Inherit this class to create component specific methods based on this interface
|
||||
/// Usage: setTable(name) once before you use read/write actions
|
||||
/// To use another database use setDb(newDB) (defaults to "hyperion")
|
||||
///
|
||||
/// Incompatible functions with SQlite3:
|
||||
/// QSqlQuery::size() returns always -1
|
||||
///
|
||||
class DBManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DBManager(QObject* parent = nullptr);
|
||||
~DBManager();
|
||||
|
||||
/// set root path
|
||||
void setRootPath(const QString& rootPath);
|
||||
/// define the database to work with
|
||||
void setDB(const QString& dbn);
|
||||
/// set a table to work with
|
||||
void setTable(const QString& table);
|
||||
|
||||
/// get current database object set with setDB()
|
||||
QSqlDatabase getDB() const;
|
||||
|
||||
///
|
||||
/// @brief Create a table (if required) with the given columns. Older tables will be updated accordingly with missing columns
|
||||
/// Does not delete or migrate old columns
|
||||
/// @param[in] columns The columns of the table. Requires at least one entry!
|
||||
/// @return True on success else false
|
||||
///
|
||||
bool createTable(QStringList& columns) const;
|
||||
|
||||
///
|
||||
/// @brief Create a column if the column already exists returns false and logs error
|
||||
/// @param[in] column The column of the table
|
||||
/// @return True on success else false
|
||||
///
|
||||
bool createColumn(const QString& column) const;
|
||||
|
||||
///
|
||||
/// @brief Check if at least one record exists in table with the conditions
|
||||
/// @param[in] conditions The search conditions (WHERE)
|
||||
/// @return True on success else false
|
||||
///
|
||||
bool recordExists(const VectorPair& conditions) const;
|
||||
|
||||
///
|
||||
/// @brief Create a new record in table when the conditions find no existing entry. Add additional key:value pairs in columns
|
||||
/// DO NOT repeat column keys between 'conditions' and 'columns' as they will be merged on creation
|
||||
/// @param[in] conditions conditions to search for, as a record may exist and should be updated instead (WHERE)
|
||||
/// @param[in] columns columns to create or update (optional)
|
||||
/// @return True on success else false
|
||||
///
|
||||
bool createRecord(const VectorPair& conditions, const QVariantMap& columns = QVariantMap()) const;
|
||||
|
||||
///
|
||||
/// @brief Update a record with conditions and additional key:value pairs in columns
|
||||
/// @param[in] conditions conditions which rows should be updated (WHERE)
|
||||
/// @param[in] columns columns to update
|
||||
/// @return True on success else false
|
||||
///
|
||||
bool updateRecord(const VectorPair& conditions, const QVariantMap& columns) const;
|
||||
|
||||
///
|
||||
/// @brief Get data of a single record, multiple records are not supported
|
||||
/// @param[in] conditions condition to search for (WHERE)
|
||||
/// @param[out] results results of query
|
||||
/// @param[in] tColumns target columns to search in (optional) if not provided returns all columns
|
||||
/// @return True on success else false
|
||||
///
|
||||
bool getRecord(const VectorPair& conditions, QVariantMap& results, const QStringList& tColumns = QStringList()) const;
|
||||
|
||||
///
|
||||
/// @brief Get data of multiple records, you need to specify the columns. This search is without conditions. Good to grab all data from db
|
||||
/// @param[in] conditions condition to search for (WHERE)
|
||||
/// @param[out] results results of query
|
||||
/// @param[in] tColumns target columns to search in (optional) if not provided returns all columns
|
||||
/// @return True on success else false
|
||||
///
|
||||
bool getRecords(QVector<QVariantMap>& results, const QStringList& tColumns = QStringList()) const;
|
||||
|
||||
///
|
||||
/// @brief Delete a record determined by conditions
|
||||
/// @param[in] conditions conditions of the row to delete it (WHERE)
|
||||
/// @return True on success; on error or not found false
|
||||
///
|
||||
bool deleteRecord(const VectorPair& conditions) const;
|
||||
|
||||
///
|
||||
/// @brief Check if table exists in current database
|
||||
/// @param[in] table The name of the table
|
||||
/// @return True on success else false
|
||||
///
|
||||
bool tableExists(const QString& table) const;
|
||||
|
||||
///
|
||||
/// @brief Delete a table, fails silent (return true) if table is not found
|
||||
/// @param[in] table The name of the table
|
||||
/// @return True on success else false
|
||||
///
|
||||
bool deleteTable(const QString& table) const;
|
||||
|
||||
private:
|
||||
|
||||
Logger* _log;
|
||||
/// databse connection & file name, defaults to hyperion
|
||||
QString _dbn = "hyperion";
|
||||
/// table in database
|
||||
QString _table;
|
||||
|
||||
/// addBindValue to query given by QVariantList
|
||||
void doAddBindValue(QSqlQuery& query, const QVariantList& variants) const;
|
||||
};
|
114
include/db/SettingsTable.h
Normal file
114
include/db/SettingsTable.h
Normal file
@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
// hyperion
|
||||
#include <db/DBManager.h>
|
||||
|
||||
// qt
|
||||
#include <QDateTime>
|
||||
#include <QJsonDocument>
|
||||
|
||||
///
|
||||
/// @brief settings table db interface
|
||||
///
|
||||
class SettingsTable : public DBManager
|
||||
{
|
||||
|
||||
public:
|
||||
/// construct wrapper with settings table
|
||||
SettingsTable(const quint8& instance, QObject* parent = nullptr)
|
||||
: DBManager(parent)
|
||||
, _hyperion_inst(instance)
|
||||
{
|
||||
setTable("settings");
|
||||
// create table columns
|
||||
createTable(QStringList()<<"type TEXT"<<"config TEXT"<<"hyperion_inst INTEGER"<<"updated_at TEXT");
|
||||
};
|
||||
~SettingsTable(){};
|
||||
|
||||
///
|
||||
/// @brief Create or update a settings record
|
||||
/// @param[in] type type of setting
|
||||
/// @param[in] config The configuration data
|
||||
/// @return true on success else false
|
||||
///
|
||||
inline bool createSettingsRecord(const QString& type, const QString& config) const
|
||||
{
|
||||
QVariantMap map;
|
||||
map["config"] = config;
|
||||
map["updated_at"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
|
||||
|
||||
VectorPair cond;
|
||||
cond.append(CPair("type",type));
|
||||
// when a setting is not global we are searching also for the instance
|
||||
if(!isSettingGlobal(type))
|
||||
cond.append(CPair("AND hyperion_inst",_hyperion_inst));
|
||||
return createRecord(cond, map);
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Test if record exist, type can be global setting or local (instance)
|
||||
/// @param[in] type type of setting
|
||||
/// @param[in] hyperion_inst The instance of hyperion assigned (might be empty)
|
||||
/// @return true on success else false
|
||||
///
|
||||
inline bool recordExist(const QString& type) const
|
||||
{
|
||||
VectorPair cond;
|
||||
cond.append(CPair("type",type));
|
||||
// when a setting is not global we are searching also for the instance
|
||||
if(!isSettingGlobal(type))
|
||||
cond.append(CPair("AND hyperion_inst",_hyperion_inst));
|
||||
return recordExists(cond);
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Get 'config' column of settings entry as QJsonDocument
|
||||
/// @param[in] type The settings type
|
||||
/// @return The QJsonDocument
|
||||
///
|
||||
inline QJsonDocument getSettingsRecord(const QString& type) const
|
||||
{
|
||||
QVariantMap results;
|
||||
VectorPair cond;
|
||||
cond.append(CPair("type",type));
|
||||
// when a setting is not global we are searching also for the instance
|
||||
if(!isSettingGlobal(type))
|
||||
cond.append(CPair("AND hyperion_inst",_hyperion_inst));
|
||||
getRecord(cond, results, QStringList("config"));
|
||||
return QJsonDocument::fromJson(results["config"].toByteArray());
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Get 'config' column of settings entry as QString
|
||||
/// @param[in] type The settings type
|
||||
/// @return The QString
|
||||
///
|
||||
inline QString getSettingsRecordString(const QString& type) const
|
||||
{
|
||||
QVariantMap results;
|
||||
VectorPair cond;
|
||||
cond.append(CPair("type",type));
|
||||
// when a setting is not global we are searching also for the instance
|
||||
if(!isSettingGlobal(type))
|
||||
cond.append(CPair("AND hyperion_inst",_hyperion_inst));
|
||||
getRecord(cond, results, QStringList("config"));
|
||||
return results["config"].toString();
|
||||
}
|
||||
|
||||
inline bool isSettingGlobal(const QString& type) const
|
||||
{
|
||||
// list of global settings
|
||||
QStringList list;
|
||||
// server port services
|
||||
list << "jsonServer" << "protoServer" << "flatbufServer" << "udpListener" << "webConfig" << "network"
|
||||
// capture
|
||||
<< "framegrabber" << "grabberV4L2"
|
||||
// other
|
||||
<< "logger";
|
||||
|
||||
return list.contains(type);
|
||||
}
|
||||
|
||||
private:
|
||||
const quint8 _hyperion_inst;
|
||||
};
|
@ -9,6 +9,8 @@
|
||||
|
||||
class QTcpServer;
|
||||
class FlatBufferClient;
|
||||
class NetOrigin;
|
||||
|
||||
|
||||
///
|
||||
/// @brief A TcpServer to receive images of different formats with Google Flatbuffer
|
||||
@ -56,6 +58,7 @@ private:
|
||||
|
||||
private:
|
||||
QTcpServer* _server;
|
||||
NetOrigin* _netOrigin;
|
||||
Logger* _log;
|
||||
int _timeout;
|
||||
quint16 _port;
|
||||
|
160
include/hyperion/AuthManager.h
Normal file
160
include/hyperion/AuthManager.h
Normal file
@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/Logger.h>
|
||||
#include <utils/settings.h>
|
||||
|
||||
//qt
|
||||
#include <QMap>
|
||||
|
||||
class AuthTable;
|
||||
class QTimer;
|
||||
|
||||
///
|
||||
/// @brief Manage the authorization of user and tokens. This class is created once as part of the HyperionDaemon
|
||||
/// To work with the global instance use AuthManager::getInstance()
|
||||
///
|
||||
class AuthManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
friend class HyperionDaemon;
|
||||
/// constructor is private, can be called from HyperionDaemon
|
||||
AuthManager(const QString& rootPath, QObject* parent = 0);
|
||||
|
||||
public:
|
||||
struct AuthDefinition{
|
||||
QString id;
|
||||
QString comment;
|
||||
QObject* caller;
|
||||
uint64_t timeoutTime;
|
||||
QString token;
|
||||
QString lastUse;
|
||||
};
|
||||
|
||||
///
|
||||
/// @brief Get all available token entries
|
||||
///
|
||||
const QVector<AuthDefinition> getTokenList();
|
||||
|
||||
///
|
||||
/// @brief Check authorization is required according to the user setting
|
||||
/// @return True if authorization required else false
|
||||
///
|
||||
const bool & isAuthRequired();
|
||||
|
||||
///
|
||||
/// @brief Check if authorization is required for local network connections
|
||||
/// @return True if authorization required else false
|
||||
///
|
||||
const bool & isLocalAuthRequired();
|
||||
|
||||
///
|
||||
/// @brief Create a new token and skip the usual chain
|
||||
/// @param comment The comment that should be used for
|
||||
/// @return The new Auth definition
|
||||
///
|
||||
const AuthDefinition createToken(const QString& comment);
|
||||
|
||||
///
|
||||
/// @brief Check if user is authorized
|
||||
/// @param user The username
|
||||
/// @param pw The password
|
||||
/// @return True if authorized else false
|
||||
///
|
||||
const bool isUserAuthorized(const QString& user, const QString& pw);
|
||||
|
||||
///
|
||||
/// @brief Check if token is authorized
|
||||
/// @param token The token
|
||||
/// @return True if authorized else false
|
||||
///
|
||||
const bool isTokenAuthorized(const QString& token);
|
||||
|
||||
///
|
||||
/// @brief Generate a new pending token request with the provided comment and id as identifier helper
|
||||
/// @param caller The QObject of the caller to deliver the reply
|
||||
/// @param comment The comment as ident helper
|
||||
/// @param id The id created by the caller
|
||||
///
|
||||
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
|
||||
///
|
||||
const bool acceptTokenRequest(const QString& id);
|
||||
|
||||
///
|
||||
/// @brief Deny a token request by id, inform the requester
|
||||
/// @param id The id of the request
|
||||
/// @return True on success, false if not found
|
||||
///
|
||||
const bool denyTokenRequest(const QString& id);
|
||||
|
||||
///
|
||||
/// @brief Get pending requests
|
||||
/// @return All pending requests
|
||||
///
|
||||
const QMap<QString, AuthDefinition> getPendingRequests();
|
||||
|
||||
///
|
||||
/// @brief Delete a token by id
|
||||
/// @param id The token id
|
||||
/// @return True on success else false (or not found)
|
||||
///
|
||||
const bool deleteToken(const QString& id);
|
||||
|
||||
/// Pointer of this instance
|
||||
static AuthManager* manager;
|
||||
/// Get Pointer of this instance
|
||||
static AuthManager* getInstance() { return manager; };
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
///
|
||||
void newPendingTokenRequest(const QString& id, const QString& comment);
|
||||
|
||||
///
|
||||
/// @brief Emits when the user has accepted or denied a token
|
||||
/// @param success If true the request was accepted else false and no token will be available
|
||||
/// @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 tokenResponse(const bool& success, QObject* caller, const QString& token, const QString& comment, const QString& id);
|
||||
|
||||
private:
|
||||
/// Database interface for auth table
|
||||
AuthTable* _authTable;
|
||||
|
||||
/// All pending requests
|
||||
QMap<QString,AuthDefinition> _pendingRequests;
|
||||
|
||||
/// Reflect state of global auth
|
||||
bool _authRequired;
|
||||
|
||||
/// Reflect state of local auth
|
||||
bool _localAuthRequired;
|
||||
|
||||
/// Timer for counting against pendingRequest timeouts
|
||||
QTimer* _timer;
|
||||
|
||||
private slots:
|
||||
///
|
||||
/// @brief Check timeout of pending requests
|
||||
///
|
||||
void checkTimeout();
|
||||
};
|
@ -7,6 +7,7 @@
|
||||
#include <QJsonObject>
|
||||
|
||||
class Hyperion;
|
||||
class SettingsTable;
|
||||
|
||||
///
|
||||
/// @brief Manage the settings read write from/to config file, on settings changed will emit a signal to update components accordingly
|
||||
@ -17,16 +18,11 @@ class SettingsManager : public QObject
|
||||
public:
|
||||
///
|
||||
/// @brief Construct a settings manager and assign a hyperion instance
|
||||
/// @params hyperion The parent hyperion instance
|
||||
/// @params instance Instance number of Hyperion
|
||||
/// @params configFile The config file
|
||||
/// @params hyperion The parent hyperion instance
|
||||
///
|
||||
SettingsManager(Hyperion* hyperion, const quint8& instance, const QString& configFile);
|
||||
|
||||
///
|
||||
/// @brief Construct a settings manager for HyperionDaemon
|
||||
///
|
||||
SettingsManager(const quint8& instance, const QString& configFile);
|
||||
~SettingsManager();
|
||||
SettingsManager(const quint8& instance, const QString& configFile, Hyperion* hyperion = nullptr);
|
||||
|
||||
///
|
||||
/// @brief Save a complete json config
|
||||
@ -64,6 +60,9 @@ private:
|
||||
/// Logger instance
|
||||
Logger* _log;
|
||||
|
||||
/// instance of database table interface
|
||||
SettingsTable* _sTable;
|
||||
|
||||
/// the schema
|
||||
static QJsonObject schemaJson;
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
class QTcpServer;
|
||||
class ProtoClientConnection;
|
||||
class NetOrigin;
|
||||
|
||||
///
|
||||
/// @brief This class creates a TCP server which accepts connections wich can then send
|
||||
@ -58,6 +59,7 @@ private:
|
||||
|
||||
private:
|
||||
QTcpServer* _server;
|
||||
NetOrigin* _netOrigin;
|
||||
Logger* _log;
|
||||
int _timeout;
|
||||
quint16 _port;
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
class BonjourServiceRegister;
|
||||
class QUdpSocket;
|
||||
class NetOrigin;
|
||||
|
||||
///
|
||||
/// This class creates a UDP server which accepts connections from boblight clients.
|
||||
@ -106,4 +107,7 @@ private:
|
||||
QHostAddress _listenAddress;
|
||||
uint16_t _listenPort;
|
||||
QAbstractSocket::BindFlag _bondage;
|
||||
|
||||
/// Check Network Origin
|
||||
NetOrigin* _netOrigin;
|
||||
};
|
||||
|
54
include/utils/NetOrigin.h
Normal file
54
include/utils/NetOrigin.h
Normal file
@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
// qt
|
||||
#include <QHostAddress>
|
||||
#include <QJsonArray>
|
||||
|
||||
// utils
|
||||
#include <utils/Logger.h>
|
||||
#include <utils/settings.h>
|
||||
|
||||
///
|
||||
/// @brief Checks the origin ip addresses for access allowed
|
||||
///
|
||||
class NetOrigin : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
friend class HyperionDaemon;
|
||||
NetOrigin(QObject* parent = 0, Logger* log = Logger::getInstance("NETWORK"));
|
||||
|
||||
public:
|
||||
///
|
||||
/// @brief Check if address is allowed to connect. The local address is the network adapter ip this connection comes from
|
||||
/// @param address The peer address
|
||||
/// @param local The local address of the socket (Differs based on NetworkAdapter IP or localhost)
|
||||
/// @return True when allowed, else false
|
||||
///
|
||||
bool accessAllowed(const QHostAddress& address, const QHostAddress& local);
|
||||
|
||||
///
|
||||
/// @brief Check if address is in subnet of local
|
||||
/// @return True or false
|
||||
///
|
||||
bool isLocalAddress(const QHostAddress& address, const QHostAddress& local);
|
||||
|
||||
static NetOrigin* getInstance(){ return instance; };
|
||||
static NetOrigin* instance;
|
||||
|
||||
private slots:
|
||||
///
|
||||
/// @brief Handle settings update from SettingsManager
|
||||
/// @param type settingyType from enum
|
||||
/// @param config configuration object
|
||||
///
|
||||
void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config);
|
||||
|
||||
private:
|
||||
Logger* _log;
|
||||
/// True when internet access is allowed
|
||||
bool _internetAccessAllowed;
|
||||
/// Whitelisted ip addresses
|
||||
QList<QHostAddress> _ipWhitelist;
|
||||
|
||||
};
|
@ -29,7 +29,7 @@ public:
|
||||
quint16 getPort() { return _port; };
|
||||
|
||||
/// check if server has been inited
|
||||
bool isInited() { return _inited; };
|
||||
const bool isInited() { return _inited; };
|
||||
|
||||
///
|
||||
/// @brief Set a new description, if empty the description is NotFound for clients
|
||||
|
@ -18,5 +18,6 @@ add_subdirectory(utils)
|
||||
add_subdirectory(effectengine)
|
||||
add_subdirectory(grabber)
|
||||
add_subdirectory(webserver)
|
||||
add_subdirectory(db)
|
||||
add_subdirectory(api)
|
||||
add_subdirectory(python)
|
||||
|
44
libsrc/api/JSONRPC_schema/schema-authorize.json
Normal file
44
libsrc/api/JSONRPC_schema/schema-authorize.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"type":"object",
|
||||
"required":true,
|
||||
"properties":{
|
||||
"command": {
|
||||
"type" : "string",
|
||||
"required" : true,
|
||||
"enum" : ["authorize"]
|
||||
},
|
||||
"subcommand" : {
|
||||
"type" : "string",
|
||||
"required" : true,
|
||||
"enum" : ["requestToken","createToken","deleteToken","getTokenList","logout","login","required","answerRequest","getPendingRequests"]
|
||||
},
|
||||
"tan" : {
|
||||
"type" : "integer"
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"minLength" : 3
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"minLength" : 8
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"minLength" : 36
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string",
|
||||
"minLength" : 5
|
||||
},
|
||||
"id" : {
|
||||
"type" : "string",
|
||||
"minLength" : 5,
|
||||
"maxLength" : 5
|
||||
},
|
||||
"accept" : {
|
||||
"type" : "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
"command": {
|
||||
"type" : "string",
|
||||
"required" : true,
|
||||
"enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "transform", "correction" , "temperature"]
|
||||
"enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "transform", "correction" , "temperature"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
<file alias="schema-logging">JSONRPC_schema/schema-logging.json</file>
|
||||
<file alias="schema-processing">JSONRPC_schema/schema-processing.json</file>
|
||||
<file alias="schema-videomode">JSONRPC_schema/schema-videomode.json</file>
|
||||
<file alias="schema-authorize">JSONRPC_schema/schema-authorize.json</file>
|
||||
<!-- The following schemas are derecated but used to ensure backward compatibility with hyperion Classic remote control-->
|
||||
<file alias="schema-transform">JSONRPC_schema/schema-hyperion-classic.json</file>
|
||||
<file alias="schema-correction">JSONRPC_schema/schema-hyperion-classic.json</file>
|
||||
|
@ -35,10 +35,17 @@
|
||||
// api includes
|
||||
#include <api/JsonCB.h>
|
||||
|
||||
// auth manager
|
||||
#include <hyperion/AuthManager.h>
|
||||
|
||||
using namespace hyperion;
|
||||
|
||||
JsonAPI::JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListener)
|
||||
JsonAPI::JsonAPI(QString peerAddress, Logger* log, const bool& localConnection, QObject* parent, bool noListener)
|
||||
: QObject(parent)
|
||||
, _authManager(AuthManager::getInstance())
|
||||
, _authorized(false)
|
||||
, _userAuthorized(false)
|
||||
, _apiAuthRequired(_authManager->isAuthRequired())
|
||||
, _noListener(noListener)
|
||||
, _peerAddress(peerAddress)
|
||||
, _log(log)
|
||||
@ -50,6 +57,14 @@ JsonAPI::JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListe
|
||||
{
|
||||
Q_INIT_RESOURCE(JSONRPC_schemas);
|
||||
|
||||
// if this is localConnection and network allows unauth locals, set authorized flag
|
||||
if(_apiAuthRequired && localConnection)
|
||||
_authorized = !_authManager->isLocalAuthRequired();
|
||||
|
||||
// setup auth interface
|
||||
connect(_authManager, &AuthManager::newPendingTokenRequest, this, &JsonAPI::handlePendingTokenRequest);
|
||||
connect(_authManager, &AuthManager::tokenResponse, this, &JsonAPI::handleTokenResponse);
|
||||
|
||||
// the JsonCB creates json messages you can subscribe to e.g. data change events; forward them to the parent client
|
||||
connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
|
||||
|
||||
@ -57,7 +72,7 @@ JsonAPI::JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListe
|
||||
connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage);
|
||||
}
|
||||
|
||||
void JsonAPI::handleMessage(const QString& messageString)
|
||||
void JsonAPI::handleMessage(const QString& messageString, const QString& httpAuthHeader)
|
||||
{
|
||||
const QString ident = "JsonRpc@"+_peerAddress;
|
||||
QJsonObject message;
|
||||
@ -85,6 +100,29 @@ void JsonAPI::handleMessage(const QString& messageString)
|
||||
|
||||
int tan = message["tan"].toInt();
|
||||
|
||||
// client auth before everything else but not for http
|
||||
if (!_noListener && command == "authorize")
|
||||
{
|
||||
handleAuthorizeCommand(message, command, tan);
|
||||
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)
|
||||
{
|
||||
sendErrorReply("No Authorization", command, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// switch over all possible commands and handle them
|
||||
if (command == "color") handleColorCommand (message, command, tan);
|
||||
else if (command == "image") handleImageCommand (message, command, tan);
|
||||
@ -104,7 +142,8 @@ void JsonAPI::handleMessage(const QString& messageString)
|
||||
else if (command == "videomode") handleVideoModeCommand (message, command, tan);
|
||||
|
||||
// BEGIN | The following commands are derecated but used to ensure backward compatibility with hyperion Classic remote control
|
||||
else if (command == "clearall") handleClearallCommand(message, command, tan);
|
||||
else if (command == "clearall")
|
||||
handleClearallCommand(message, command, tan);
|
||||
else if (command == "transform" || command == "correction" || command == "temperature")
|
||||
sendErrorReply("The command " + command + "is deprecated, please use the Hyperion Web Interface to configure");
|
||||
// END
|
||||
@ -949,6 +988,205 @@ void JsonAPI::handleVideoModeCommand(const QJsonObject& message, const QString &
|
||||
sendSuccessReply(command, tan);
|
||||
}
|
||||
|
||||
void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString &command, const int tan)
|
||||
{
|
||||
const QString& subc = message["subcommand"].toString().trimmed();
|
||||
// catch test if auth is required
|
||||
if(subc == "required")
|
||||
{
|
||||
QJsonObject req;
|
||||
req["required"] = _apiAuthRequired;
|
||||
sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// catch logout
|
||||
if(subc == "logout")
|
||||
{
|
||||
_authorized = false;
|
||||
_userAuthorized = false;
|
||||
sendSuccessReply(command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// token created from ui
|
||||
if(subc == "createToken")
|
||||
{
|
||||
const QString& c = message["comment"].toString().trimmed();
|
||||
// for user authorized sessions
|
||||
if(_userAuthorized)
|
||||
{
|
||||
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);
|
||||
return;
|
||||
}
|
||||
sendErrorReply("No Authorization",command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// delete token
|
||||
if(subc == "deleteToken")
|
||||
{
|
||||
const QString& did = message["id"].toString().trimmed();
|
||||
// for user authorized sessions
|
||||
if(_userAuthorized)
|
||||
{
|
||||
_authManager->deleteToken(did);
|
||||
sendSuccessReply(command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
sendErrorReply("No Authorization",command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// catch token request
|
||||
if(subc == "requestToken")
|
||||
{
|
||||
const QString& comment = message["comment"].toString().trimmed();
|
||||
const QString& id = message["id"].toString().trimmed();
|
||||
_authManager->setNewTokenRequest(this, comment, id);
|
||||
// client should wait for answer
|
||||
return;
|
||||
}
|
||||
|
||||
// get pending token requests
|
||||
if(subc == "getPendingRequests")
|
||||
{
|
||||
if(_userAuthorized)
|
||||
{
|
||||
QMap<QString, AuthManager::AuthDefinition> map = _authManager->getPendingRequests();
|
||||
QJsonArray arr;
|
||||
for(const auto& entry : map)
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["comment"] = entry.comment;
|
||||
obj["id"] = entry.id;
|
||||
obj["timeout"] = int(entry.timeoutTime - QDateTime::currentMSecsSinceEpoch());
|
||||
arr.append(obj);
|
||||
}
|
||||
sendSuccessDataReply(QJsonDocument(arr),command+"-"+subc, tan);
|
||||
}
|
||||
else
|
||||
sendErrorReply("No Authorization", command+"-"+subc, tan);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// accept token request
|
||||
if(subc == "answerRequest")
|
||||
{
|
||||
const QString& id = message["id"].toString().trimmed();
|
||||
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")
|
||||
{
|
||||
const QString& id = message["id"].toString().trimmed();
|
||||
if(_userAuthorized)
|
||||
{
|
||||
_authManager->acceptTokenRequest(id);
|
||||
}
|
||||
else
|
||||
sendErrorReply("No Authorization", command+"-"+subc, tan);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// cath get token list
|
||||
if(subc == "getTokenList")
|
||||
{
|
||||
if(_userAuthorized)
|
||||
{
|
||||
QVector<AuthManager::AuthDefinition> defVect = _authManager->getTokenList();
|
||||
QJsonArray tArr;
|
||||
for(const auto& entry : defVect)
|
||||
{
|
||||
QJsonObject subO;
|
||||
subO["comment"] = entry.comment;
|
||||
subO["id"] = entry.id;
|
||||
subO["last_use"] = entry.lastUse;
|
||||
|
||||
tArr.append(subO);
|
||||
}
|
||||
|
||||
sendSuccessDataReply(QJsonDocument(tArr),command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
sendErrorReply("No Authorization",command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// login
|
||||
if(subc == "login")
|
||||
{
|
||||
// catch token auth
|
||||
const QString& token = message["token"].toString().trimmed();
|
||||
|
||||
if(!token.isEmpty())
|
||||
{
|
||||
if(token.count() >= 36)
|
||||
{
|
||||
if(_authManager->isTokenAuthorized(token))
|
||||
{
|
||||
_authorized = true;
|
||||
sendSuccessReply(command+"-"+subc, tan);
|
||||
}
|
||||
else
|
||||
sendErrorReply("No Authorization", command+"-"+subc, tan);
|
||||
}
|
||||
else
|
||||
sendErrorReply("Token is too short", command+"-"+subc, tan);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// user & password
|
||||
const QString& user = message["username"].toString().trimmed();
|
||||
const QString& password = message["password"].toString().trimmed();
|
||||
|
||||
if(user.count() >= 3 && password.count() >= 8)
|
||||
{
|
||||
if(_authManager->isUserAuthorized(user, password))
|
||||
{
|
||||
_authorized = true;
|
||||
_userAuthorized = true;
|
||||
sendSuccessReply(command+"-"+subc, tan);
|
||||
}
|
||||
else
|
||||
sendErrorReply("No Authorization", command+"-"+subc, tan);
|
||||
}
|
||||
else
|
||||
sendErrorReply("User or password string too short", command+"-"+subc, tan);
|
||||
}
|
||||
}
|
||||
|
||||
const bool JsonAPI::handleHTTPAuth(const QString& command, const int& tan, const QString& token)
|
||||
{
|
||||
if(_authManager->isTokenAuthorized(token))
|
||||
{
|
||||
_authorized = true;
|
||||
return true;
|
||||
}
|
||||
sendErrorReply("No Authorization", command, tan);
|
||||
return false;
|
||||
}
|
||||
|
||||
void JsonAPI::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan)
|
||||
{
|
||||
emit forwardJsonMessage(message);
|
||||
@ -1093,3 +1331,35 @@ void JsonAPI::incommingLogMessage(const Logger::T_LOG_MESSAGE &msg)
|
||||
// send the result
|
||||
emit callbackMessage(_streaming_logging_reply);
|
||||
}
|
||||
|
||||
void JsonAPI::handlePendingTokenRequest(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;
|
||||
|
||||
emit callbackMessage(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void JsonAPI::handleTokenResponse(const bool& success, QObject* caller, 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;
|
||||
|
||||
if(success)
|
||||
sendSuccessDataReply(QJsonDocument(result), cmd);
|
||||
else
|
||||
sendErrorReply("Token request timeout or denied", cmd);
|
||||
}
|
||||
}
|
||||
|
18
libsrc/db/CMakeLists.txt
Normal file
18
libsrc/db/CMakeLists.txt
Normal file
@ -0,0 +1,18 @@
|
||||
find_package(Qt5 COMPONENTS Sql REQUIRED)
|
||||
|
||||
# Define the current source locations
|
||||
SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/db)
|
||||
SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/db)
|
||||
|
||||
FILE ( GLOB DB_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" )
|
||||
|
||||
add_library(database
|
||||
${DB_SOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries(database
|
||||
hyperion
|
||||
hyperion-utils
|
||||
Qt5::Core
|
||||
Qt5::Sql
|
||||
)
|
408
libsrc/db/DBManager.cpp
Normal file
408
libsrc/db/DBManager.cpp
Normal file
@ -0,0 +1,408 @@
|
||||
#include <db/DBManager.h>
|
||||
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlError>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlRecord>
|
||||
#include <QDir>
|
||||
|
||||
// not in header because of linking
|
||||
static QString _rootPath;
|
||||
|
||||
DBManager::DBManager(QObject* parent)
|
||||
: QObject(parent)
|
||||
, _log(Logger::getInstance("DB"))
|
||||
{
|
||||
}
|
||||
|
||||
DBManager::~DBManager()
|
||||
{
|
||||
}
|
||||
|
||||
void DBManager::setRootPath(const QString& rootPath)
|
||||
{
|
||||
_rootPath = rootPath;
|
||||
// create directory
|
||||
QDir().mkpath(_rootPath+"/db");
|
||||
}
|
||||
|
||||
void DBManager::setDB(const QString& dbn)
|
||||
{
|
||||
_dbn = dbn;
|
||||
// new database connection if not found
|
||||
if(!QSqlDatabase::contains(dbn))
|
||||
{
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE",dbn);
|
||||
db.setDatabaseName(_rootPath+"/db/"+dbn+".db");
|
||||
if(!db.open())
|
||||
{
|
||||
Error(_log, QSTRING_CSTR(db.lastError().text()));
|
||||
throw std::runtime_error("Failed to open database connection!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DBManager::setTable(const QString& table)
|
||||
{
|
||||
_table = table;
|
||||
}
|
||||
|
||||
QSqlDatabase DBManager::getDB() const
|
||||
{
|
||||
QSqlDatabase db = QSqlDatabase::database(_dbn);
|
||||
|
||||
if (db.isOpen() && db.isValid())
|
||||
{
|
||||
return db;
|
||||
}
|
||||
else
|
||||
{
|
||||
db = QSqlDatabase::addDatabase("QSQLITE", _dbn);
|
||||
db.setDatabaseName(_rootPath+"/db/"+_dbn+".db");
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
bool DBManager::createRecord(const VectorPair& conditions, const QVariantMap& columns) const
|
||||
{
|
||||
if(recordExists(conditions))
|
||||
{
|
||||
// if there is no column data, return
|
||||
if(columns.isEmpty())
|
||||
return true;
|
||||
|
||||
if(!updateRecord(conditions, columns))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
query.setForwardOnly(true);
|
||||
|
||||
QVariantList cValues;
|
||||
QStringList prep;
|
||||
QStringList placeh;
|
||||
// prep merge columns & condition
|
||||
QVariantMap::const_iterator i = columns.constBegin();
|
||||
while (i != columns.constEnd()) {
|
||||
prep.append(i.key());
|
||||
cValues += i.value();
|
||||
placeh.append("?");
|
||||
|
||||
++i;
|
||||
}
|
||||
for(const auto& pair : conditions)
|
||||
{
|
||||
// remove the condition statements
|
||||
QString tmp = pair.first;
|
||||
prep << tmp.remove("AND");
|
||||
cValues << pair.second;
|
||||
placeh.append("?");
|
||||
}
|
||||
query.prepare(QString("INSERT INTO %1 ( %2 ) VALUES ( %3 )").arg(_table,prep.join(", ")).arg(placeh.join(", ")));
|
||||
// add column & condition values
|
||||
doAddBindValue(query, cValues);
|
||||
if(!query.exec())
|
||||
{
|
||||
Error(_log, "Failed to create record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prep.join(", ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBManager::recordExists(const VectorPair& conditions) const
|
||||
{
|
||||
if(conditions.isEmpty())
|
||||
return false;
|
||||
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
query.setForwardOnly(true);
|
||||
|
||||
QStringList prepCond;
|
||||
QVariantList bindVal;
|
||||
prepCond << "WHERE";
|
||||
|
||||
for(const auto& pair : conditions)
|
||||
{
|
||||
prepCond << pair.first+"=?";
|
||||
bindVal << pair.second;
|
||||
}
|
||||
query.prepare(QString("SELECT * FROM %1 %2").arg(_table,prepCond.join(" ")));
|
||||
doAddBindValue(query, bindVal);
|
||||
if(!query.exec())
|
||||
{
|
||||
Error(_log, "Failed recordExists(): '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
|
||||
int entry = 0;
|
||||
while (query.next()) {
|
||||
entry++;
|
||||
}
|
||||
|
||||
if(entry)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBManager::updateRecord(const VectorPair& conditions, const QVariantMap& columns) const
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
query.setForwardOnly(true);
|
||||
|
||||
QVariantList values;
|
||||
QStringList prep;
|
||||
|
||||
// prepare columns valus
|
||||
QVariantMap::const_iterator i = columns.constBegin();
|
||||
while (i != columns.constEnd()) {
|
||||
prep += i.key()+"=?";
|
||||
values += i.value();
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
// prepare condition values
|
||||
QStringList prepCond;
|
||||
QVariantList prepBindVal;
|
||||
if(!conditions.isEmpty())
|
||||
prepCond << "WHERE";
|
||||
|
||||
for(const auto& pair : conditions)
|
||||
{
|
||||
prepCond << pair.first+"=?";
|
||||
prepBindVal << pair.second;
|
||||
}
|
||||
|
||||
query.prepare(QString("UPDATE %1 SET %2 %3").arg(_table,prep.join(", ")).arg(prepCond.join(" ")));
|
||||
// add column values
|
||||
doAddBindValue(query, values);
|
||||
// add condition values
|
||||
doAddBindValue(query, prepBindVal);
|
||||
if(!query.exec())
|
||||
{
|
||||
Error(_log, "Failed to update record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBManager::getRecord(const VectorPair& conditions, QVariantMap& results, const QStringList& tColumns) const
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
query.setForwardOnly(true);
|
||||
|
||||
QString sColumns("*");
|
||||
if(!tColumns.isEmpty())
|
||||
sColumns = tColumns.join(", ");
|
||||
|
||||
// prep conditions
|
||||
QStringList prepCond;
|
||||
QVariantList bindVal;
|
||||
if(!conditions.isEmpty())
|
||||
prepCond << "WHERE";
|
||||
|
||||
for(const auto& pair : conditions)
|
||||
{
|
||||
prepCond << pair.first+"=?";
|
||||
bindVal << pair.second;
|
||||
}
|
||||
query.prepare(QString("SELECT %1 FROM %2 %3").arg(sColumns,_table).arg(prepCond.join(" ")));
|
||||
doAddBindValue(query, bindVal);
|
||||
|
||||
if(!query.exec())
|
||||
{
|
||||
Error(_log, "Failed to get record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// go to first row
|
||||
query.next();
|
||||
|
||||
QSqlRecord rec = query.record();
|
||||
for(int i = 0; i<rec.count(); i++)
|
||||
{
|
||||
results[rec.fieldName(i)] = rec.value(i);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBManager::getRecords(QVector<QVariantMap>& results, const QStringList& tColumns) const
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
query.setForwardOnly(true);
|
||||
|
||||
QString sColumns("*");
|
||||
if(!tColumns.isEmpty())
|
||||
sColumns = tColumns.join(", ");
|
||||
|
||||
query.prepare(QString("SELECT %1 FROM %2").arg(sColumns,_table));
|
||||
|
||||
if(!query.exec())
|
||||
{
|
||||
Error(_log, "Failed to get records: '%s' in table: '%s' Error: %s", QSTRING_CSTR(sColumns), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// iterate through all found records
|
||||
while(query.next())
|
||||
{
|
||||
QVariantMap entry;
|
||||
QSqlRecord rec = query.record();
|
||||
for(int i = 0; i<rec.count(); i++)
|
||||
{
|
||||
entry[rec.fieldName(i)] = rec.value(i);
|
||||
}
|
||||
results.append(entry);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool DBManager::deleteRecord(const VectorPair& conditions) const
|
||||
{
|
||||
if(conditions.isEmpty())
|
||||
{
|
||||
Error(_log, "Oops, a deleteRecord() call wants to delete the entire table (%s)! Denied it", QSTRING_CSTR(_table));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(recordExists(conditions))
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
|
||||
// prep conditions
|
||||
QStringList prepCond("WHERE");
|
||||
QVariantList bindValues;
|
||||
|
||||
for(const auto& pair : conditions)
|
||||
{
|
||||
prepCond << pair.first+"=?";
|
||||
bindValues << pair.second;
|
||||
}
|
||||
|
||||
query.prepare(QString("DELETE FROM %1 %2").arg(_table,prepCond.join(" ")));
|
||||
doAddBindValue(query, bindValues);
|
||||
if(!query.exec())
|
||||
{
|
||||
Error(_log, "Failed to delete record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBManager::createTable(QStringList& columns) const
|
||||
{
|
||||
if(columns.isEmpty())
|
||||
{
|
||||
Error(_log,"Empty tables aren't supported!");
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlDatabase idb = getDB();
|
||||
// create table if required
|
||||
QSqlQuery query(idb);
|
||||
if(!tableExists(_table))
|
||||
{
|
||||
// empty tables aren't supported by sqlite, add one column
|
||||
QString tcolumn = columns.takeFirst();
|
||||
// default CURRENT_TIMESTAMP is not supported by ALTER TABLE
|
||||
if(!query.exec(QString("CREATE TABLE %1 ( %2 )").arg(_table,tcolumn)))
|
||||
{
|
||||
Error(_log, "Failed to create table: '%s' Error: %s", QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// create columns if required
|
||||
QSqlRecord rec = idb.record(_table);
|
||||
int err = 0;
|
||||
for(const auto& column : columns)
|
||||
{
|
||||
QStringList id = column.split(' ');
|
||||
if(rec.indexOf(id.at(0)) == -1)
|
||||
{
|
||||
if(!createColumn(column))
|
||||
{
|
||||
err++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(err)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBManager::createColumn(const QString& column) const
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
if(!query.exec(QString("ALTER TABLE %1 ADD COLUMN %2").arg(_table,column)))
|
||||
{
|
||||
Error(_log, "Failed to create column: '%s' in table: '%s' Error: %s", QSTRING_CSTR(column), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBManager::tableExists(const QString& table) const
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QStringList tables = idb.tables();
|
||||
if(tables.contains(table))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBManager::deleteTable(const QString& table) const
|
||||
{
|
||||
if(tableExists(table))
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
if(!query.exec(QString("DROP TABLE %1").arg(table)))
|
||||
{
|
||||
Error(_log, "Failed to delete table: '%s' Error: %s", QSTRING_CSTR(table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DBManager::doAddBindValue(QSqlQuery& query, const QVariantList& variants) const
|
||||
{
|
||||
for(const auto& variant : variants)
|
||||
{
|
||||
QVariant::Type t = variant.type();
|
||||
switch(t)
|
||||
{
|
||||
case QVariant::UInt:
|
||||
case QVariant::Int:
|
||||
case QVariant::Bool:
|
||||
query.addBindValue(variant.toInt());
|
||||
break;
|
||||
case QVariant::Double:
|
||||
query.addBindValue(variant.toFloat());
|
||||
break;
|
||||
case QVariant::ByteArray:
|
||||
query.addBindValue(variant.toByteArray());
|
||||
break;
|
||||
default:
|
||||
query.addBindValue(variant.toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
#include <flatbufserver/FlatBufferServer.h>
|
||||
#include "FlatBufferClient.h"
|
||||
|
||||
// util
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
// qt
|
||||
#include <QJsonObject>
|
||||
#include <QTcpServer>
|
||||
@ -24,6 +27,7 @@ FlatBufferServer::~FlatBufferServer()
|
||||
|
||||
void FlatBufferServer::initServer()
|
||||
{
|
||||
_netOrigin = NetOrigin::getInstance();
|
||||
connect(_server, &QTcpServer::newConnection, this, &FlatBufferServer::newConnection);
|
||||
|
||||
// apply config
|
||||
@ -57,6 +61,8 @@ void FlatBufferServer::newConnection()
|
||||
while(_server->hasPendingConnections())
|
||||
{
|
||||
if(QTcpSocket* socket = _server->nextPendingConnection())
|
||||
{
|
||||
if(_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress()))
|
||||
{
|
||||
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
|
||||
FlatBufferClient *client = new FlatBufferClient(socket, _timeout, this);
|
||||
@ -64,6 +70,9 @@ void FlatBufferServer::newConnection()
|
||||
connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected);
|
||||
_openConnections.append(client);
|
||||
}
|
||||
else
|
||||
socket->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
164
libsrc/hyperion/AuthManager.cpp
Normal file
164
libsrc/hyperion/AuthManager.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
#include <hyperion/AuthManager.h>
|
||||
|
||||
// util
|
||||
#include <db/AuthTable.h>
|
||||
|
||||
// qt
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
|
||||
AuthManager* AuthManager::manager = nullptr;
|
||||
|
||||
AuthManager::AuthManager(const QString& rootPath, QObject* parent)
|
||||
: QObject(parent)
|
||||
, _authTable(new AuthTable(rootPath, this))
|
||||
, _pendingRequests()
|
||||
, _authRequired(true)
|
||||
, _timer(new QTimer(this))
|
||||
{
|
||||
AuthManager::manager = this;
|
||||
|
||||
// setup timer
|
||||
_timer->setInterval(1000);
|
||||
connect(_timer, &QTimer::timeout, this, &AuthManager::checkTimeout);
|
||||
|
||||
// init with default user and password
|
||||
if(!_authTable->userExist("Hyperion"))
|
||||
{
|
||||
_authTable->createUser("Hyperion","hyperion");
|
||||
}
|
||||
}
|
||||
|
||||
const bool & AuthManager::isAuthRequired()
|
||||
{
|
||||
return _authRequired;
|
||||
}
|
||||
|
||||
const bool & AuthManager::isLocalAuthRequired()
|
||||
{
|
||||
return _localAuthRequired;
|
||||
}
|
||||
|
||||
const 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);
|
||||
|
||||
_authTable->createToken(token, comment, id);
|
||||
|
||||
AuthDefinition def;
|
||||
def.comment = comment;
|
||||
def.token = token;
|
||||
def.id = id;
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
const QVector<AuthManager::AuthDefinition> AuthManager::getTokenList()
|
||||
{
|
||||
QVector<QVariantMap> vector = _authTable->getTokenList();
|
||||
QVector<AuthManager::AuthDefinition> finalVec;
|
||||
for(const auto& entry : vector)
|
||||
{
|
||||
AuthDefinition def;
|
||||
def.comment = entry["comment"].toString();
|
||||
def.id = entry["id"].toString();
|
||||
def.lastUse = entry["last_use"].toString();
|
||||
|
||||
// don't add empty ids
|
||||
if(!entry["id"].toString().isEmpty())
|
||||
finalVec.append(def);
|
||||
}
|
||||
return finalVec;
|
||||
}
|
||||
|
||||
const bool AuthManager::isUserAuthorized(const QString& user, const QString& pw)
|
||||
{
|
||||
return _authTable->isUserAuthorized(user, pw);
|
||||
}
|
||||
|
||||
const bool AuthManager::isTokenAuthorized(const QString& token)
|
||||
{
|
||||
return _authTable->tokenExist(token);
|
||||
}
|
||||
|
||||
void AuthManager::setNewTokenRequest(QObject* caller, const QString& comment, const QString& id)
|
||||
{
|
||||
if(!_pendingRequests.contains(id))
|
||||
{
|
||||
AuthDefinition newDef {id, comment, caller, uint64_t(QDateTime::currentMSecsSinceEpoch()+60000)};
|
||||
_pendingRequests[id] = newDef;
|
||||
_timer->start();
|
||||
emit newPendingTokenRequest(id, comment);
|
||||
}
|
||||
}
|
||||
|
||||
const bool AuthManager::acceptTokenRequest(const QString& 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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool AuthManager::denyTokenRequest(const QString& id)
|
||||
{
|
||||
if(_pendingRequests.contains(id))
|
||||
{
|
||||
AuthDefinition def = _pendingRequests.take(id);
|
||||
emit tokenResponse(false, def.caller, QString(), def.comment, id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const QMap<QString, AuthManager::AuthDefinition> AuthManager::getPendingRequests()
|
||||
{
|
||||
return _pendingRequests;
|
||||
}
|
||||
|
||||
const bool AuthManager::deleteToken(const QString& id)
|
||||
{
|
||||
if(_authTable->deleteToken(id))
|
||||
{
|
||||
//emit tokenDeleted(token);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AuthManager::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config)
|
||||
{
|
||||
if(type == settings::NETWORK)
|
||||
{
|
||||
const QJsonObject& obj = config.object();
|
||||
_authRequired = obj["apiAuth"].toBool(true);
|
||||
_localAuthRequired = obj["localApiAuth"].toBool(false);
|
||||
}
|
||||
}
|
||||
|
||||
void AuthManager::checkTimeout()
|
||||
{
|
||||
const uint64_t now = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
QMapIterator<QString, AuthDefinition> i(_pendingRequests);
|
||||
while (i.hasNext())
|
||||
{
|
||||
i.next();
|
||||
|
||||
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())
|
||||
_timer->stop();
|
||||
}
|
@ -25,5 +25,6 @@ target_link_libraries(hyperion
|
||||
bonjour
|
||||
boblightserver
|
||||
effectengine
|
||||
database
|
||||
${QT_LIBRARIES}
|
||||
)
|
||||
|
@ -71,7 +71,7 @@ Hyperion* Hyperion::getInstance()
|
||||
|
||||
Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath)
|
||||
: _daemon(daemon)
|
||||
, _settingsManager(new SettingsManager(this, instance, configFile))
|
||||
, _settingsManager(new SettingsManager(instance, configFile, this))
|
||||
, _componentRegister(this)
|
||||
, _ledString(hyperion::createLedString(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object())))
|
||||
, _ledStringClone(hyperion::createLedStringClone(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object())))
|
||||
@ -89,6 +89,9 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
|
||||
, _prevCompId(hyperion::COMP_INVALID)
|
||||
, _ledBuffer(_ledString.leds().size(), ColorRgb::BLACK)
|
||||
{
|
||||
// forward settings changed to Hyperion
|
||||
connect(_settingsManager, &SettingsManager::settingsChanged, this, &Hyperion::settingsChanged);
|
||||
|
||||
if (!_raw2ledAdjustment->verifyAdjustments())
|
||||
Warning(_log, "At least one led has no color calibration, please add all leds from your led layout to an 'LED index' field!");
|
||||
|
||||
@ -209,9 +212,6 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum
|
||||
|
||||
const QJsonArray leds = config.array();
|
||||
|
||||
// // lock update()
|
||||
// _lockUpdate = true;
|
||||
|
||||
// stop and cache all running effects, as effects depend heavily on ledlayout
|
||||
_effectEngine->cacheRunningEffects();
|
||||
|
||||
@ -247,14 +247,10 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum
|
||||
|
||||
// start cached effects
|
||||
_effectEngine->startCachedEffects();
|
||||
|
||||
// // unlock
|
||||
// _lockUpdate = false;
|
||||
}
|
||||
else if(type == settings::DEVICE)
|
||||
{
|
||||
QMutexLocker lock(&_changes);
|
||||
// _lockUpdate = true;
|
||||
QJsonObject dev = config.object();
|
||||
|
||||
// handle hwLedCount update
|
||||
@ -278,7 +274,6 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum
|
||||
// do always reinit until the led devices can handle dynamic changes
|
||||
dev["currentLedCount"] = int(_hwLedCount); // Inject led count info
|
||||
_ledDeviceWrapper->createLedDevice(dev);
|
||||
// _lockUpdate = false;
|
||||
}
|
||||
// update once to push single color sets / adjustments/ ledlayout resizes and update ledBuffer color
|
||||
update();
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
// util
|
||||
#include <utils/JsonUtils.h>
|
||||
#include <db/SettingsTable.h>
|
||||
|
||||
// json schema process
|
||||
#include <utils/jsonschema/QJsonFactory.h>
|
||||
@ -16,11 +17,11 @@
|
||||
|
||||
QJsonObject SettingsManager::schemaJson;
|
||||
|
||||
SettingsManager::SettingsManager(Hyperion* hyperion, const quint8& instance, const QString& configFile)
|
||||
SettingsManager::SettingsManager(const quint8& instance, const QString& configFile, Hyperion* hyperion)
|
||||
: _hyperion(hyperion)
|
||||
, _log(Logger::getInstance("SettingsManager"))
|
||||
, _sTable(new SettingsTable(instance, this))
|
||||
{
|
||||
connect(this, &SettingsManager::settingsChanged, _hyperion, &Hyperion::settingsChanged);
|
||||
// get schema
|
||||
if(schemaJson.isEmpty())
|
||||
{
|
||||
@ -34,11 +35,14 @@ SettingsManager::SettingsManager(Hyperion* hyperion, const quint8& instance, con
|
||||
throw std::runtime_error(error.what());
|
||||
}
|
||||
}
|
||||
|
||||
// get default config
|
||||
QJsonObject defaultConfig;
|
||||
if(!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log))
|
||||
throw std::runtime_error("Failed to read default config");
|
||||
|
||||
// TODO BEGIN - remove when database migration is done
|
||||
|
||||
Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile));
|
||||
QJsonSchemaChecker schemaCheckerT;
|
||||
schemaCheckerT.setSchema(schemaJson);
|
||||
@ -70,76 +74,74 @@ SettingsManager::SettingsManager(Hyperion* hyperion, const quint8& instance, con
|
||||
throw std::runtime_error("ERROR: Can't save configuration file, aborting");
|
||||
}
|
||||
|
||||
Debug(_log,"Settings database initialized")
|
||||
}
|
||||
// TODO END - remove when database migration is done
|
||||
|
||||
SettingsManager::SettingsManager(const quint8& instance, const QString& configFile)
|
||||
: _hyperion(nullptr)
|
||||
, _log(Logger::getInstance("SettingsManager"))
|
||||
// transform json to string lists
|
||||
QStringList keyList = defaultConfig.keys();
|
||||
QStringList defValueList;
|
||||
for(const auto key : keyList)
|
||||
{
|
||||
Q_INIT_RESOURCE(resource);
|
||||
// get schema
|
||||
if(schemaJson.isEmpty())
|
||||
if(defaultConfig[key].isObject())
|
||||
{
|
||||
try
|
||||
{
|
||||
schemaJson = QJsonFactory::readSchema(":/hyperion-schema");
|
||||
defValueList << QString(QJsonDocument(defaultConfig[key].toObject()).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
catch(const std::runtime_error& error)
|
||||
else if(defaultConfig[key].isArray())
|
||||
{
|
||||
throw std::runtime_error(error.what());
|
||||
defValueList << QString(QJsonDocument(defaultConfig[key].toArray()).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
}
|
||||
// get default config
|
||||
QJsonObject defaultConfig;
|
||||
if(!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log))
|
||||
throw std::runtime_error("Failed to read default config");
|
||||
|
||||
Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile));
|
||||
QJsonSchemaChecker schemaCheckerT;
|
||||
schemaCheckerT.setSchema(schemaJson);
|
||||
|
||||
if(!JsonUtils::readFile(configFile, _qconfig, _log))
|
||||
throw std::runtime_error("Failed to load config!");
|
||||
|
||||
// validate config with schema and correct it if required
|
||||
QPair<bool, bool> validate = schemaCheckerT.validate(_qconfig);
|
||||
|
||||
// errors in schema syntax, abort
|
||||
if (!validate.second)
|
||||
// fill database with default data if required
|
||||
for(const auto key : keyList)
|
||||
{
|
||||
foreach (auto & schemaError, schemaCheckerT.getMessages())
|
||||
QString val = defValueList.takeFirst();
|
||||
// prevent overwrite
|
||||
if(!_sTable->recordExist(key))
|
||||
_sTable->createSettingsRecord(key,val);
|
||||
}
|
||||
|
||||
// need to validate all data in database constuct the entire data object
|
||||
// TODO refactor schemaChecker to accept QJsonArray in validate(); QJsonDocument container? To validate them per entry...
|
||||
QJsonObject dbConfig;
|
||||
for(const auto key : keyList)
|
||||
{
|
||||
QJsonDocument doc = _sTable->getSettingsRecord(key);
|
||||
if(doc.isArray())
|
||||
dbConfig[key] = doc.array();
|
||||
else
|
||||
dbConfig[key] = doc.object();
|
||||
}
|
||||
|
||||
// validate full dbconfig against schema, on error we need to rewrite entire table
|
||||
QJsonSchemaChecker schemaChecker;
|
||||
schemaChecker.setSchema(schemaJson);
|
||||
QPair<bool,bool> valid = schemaChecker.validate(dbConfig);
|
||||
// check if our main schema syntax is IO
|
||||
if (!valid.second)
|
||||
{
|
||||
foreach (auto & schemaError, schemaChecker.getMessages())
|
||||
Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError));
|
||||
|
||||
throw std::runtime_error("ERROR: Hyperion schema has syntax errors!");
|
||||
throw std::runtime_error("The config schema has invalid syntax. This should never happen! Go fix it!");
|
||||
}
|
||||
// errors in configuration, correct it!
|
||||
if (!validate.first)
|
||||
if (!valid.first)
|
||||
{
|
||||
Warning(_log,"Errors have been found in the configuration file. Automatic correction has been applied");
|
||||
_qconfig = schemaCheckerT.getAutoCorrectedConfig(_qconfig);
|
||||
Info(_log,"Table upgrade required...");
|
||||
dbConfig = schemaChecker.getAutoCorrectedConfig(dbConfig);
|
||||
|
||||
foreach (auto & schemaError, schemaCheckerT.getMessages())
|
||||
foreach (auto & schemaError, schemaChecker.getMessages())
|
||||
Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError));
|
||||
|
||||
if (!JsonUtils::write(configFile, _qconfig, _log))
|
||||
throw std::runtime_error("ERROR: Can't save configuration file, aborting");
|
||||
saveSettings(dbConfig);
|
||||
}
|
||||
else
|
||||
_qconfig = dbConfig;
|
||||
|
||||
Debug(_log,"Settings database initialized")
|
||||
}
|
||||
|
||||
SettingsManager::~SettingsManager()
|
||||
{
|
||||
}
|
||||
|
||||
const QJsonDocument SettingsManager::getSetting(const settings::type& type)
|
||||
{
|
||||
QString key = settings::typeToString(type);
|
||||
if(_qconfig[key].isObject())
|
||||
return QJsonDocument(_qconfig[key].toObject());
|
||||
else
|
||||
return QJsonDocument(_qconfig[key].toArray());
|
||||
return _sTable->getSettingsRecord(settings::typeToString(type));
|
||||
}
|
||||
|
||||
bool SettingsManager::saveSettings(QJsonObject config, const bool& correct)
|
||||
@ -168,25 +170,34 @@ bool SettingsManager::saveSettings(QJsonObject config, const bool& correct)
|
||||
return false;
|
||||
}
|
||||
|
||||
// compare old data with new data to emit/save changes accordingly
|
||||
for(const auto key : config.keys())
|
||||
{
|
||||
QString newData, oldData;
|
||||
|
||||
_qconfig[key].isObject()
|
||||
? oldData = QString(QJsonDocument(_qconfig[key].toObject()).toJson(QJsonDocument::Compact))
|
||||
: oldData = QString(QJsonDocument(_qconfig[key].toArray()).toJson(QJsonDocument::Compact));
|
||||
|
||||
config[key].isObject()
|
||||
? newData = QString(QJsonDocument(config[key].toObject()).toJson(QJsonDocument::Compact))
|
||||
: newData = QString(QJsonDocument(config[key].toArray()).toJson(QJsonDocument::Compact));
|
||||
|
||||
if(oldData != newData)
|
||||
emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(newData.toLocal8Bit()));
|
||||
}
|
||||
|
||||
// store the current state
|
||||
// store the new config
|
||||
_qconfig = config;
|
||||
|
||||
// extract keys and data
|
||||
QStringList keyList = config.keys();
|
||||
QStringList newValueList;
|
||||
for(const auto key : keyList)
|
||||
{
|
||||
if(config[key].isObject())
|
||||
{
|
||||
newValueList << QString(QJsonDocument(config[key].toObject()).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
else if(config[key].isArray())
|
||||
{
|
||||
newValueList << QString(QJsonDocument(config[key].toArray()).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
}
|
||||
|
||||
// compare database data with new data to emit/save changes accordingly
|
||||
for(const auto key : keyList)
|
||||
{
|
||||
QString data = newValueList.takeFirst();
|
||||
if(_sTable->getSettingsRecordString(key) != data)
|
||||
{
|
||||
_sTable->createSettingsRecord(key, data);
|
||||
|
||||
emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(data.toLocal8Bit()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -79,6 +79,10 @@
|
||||
{
|
||||
"$ref": "schema-instCapture.json"
|
||||
},
|
||||
"network":
|
||||
{
|
||||
"$ref": "schema-network.json"
|
||||
},
|
||||
"ledConfig":
|
||||
{
|
||||
"$ref": "schema-ledConfig.json"
|
||||
|
@ -23,5 +23,6 @@
|
||||
<file alias="schema-ledConfig.json">schema/schema-ledConfig.json</file>
|
||||
<file alias="schema-leds.json">schema/schema-leds.json</file>
|
||||
<file alias="schema-instCapture.json">schema/schema-instCapture.json</file>
|
||||
<file alias="schema-network.json">schema/schema-network.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
54
libsrc/hyperion/schema/schema-network.json
Normal file
54
libsrc/hyperion/schema/schema-network.json
Normal file
@ -0,0 +1,54 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"title" : "edt_conf_net_heading_title",
|
||||
"required" : true,
|
||||
"properties" :
|
||||
{
|
||||
"internetAccessAPI" :
|
||||
{
|
||||
"type" : "boolean",
|
||||
"title" : "edt_conf_net_internetAccessAPI_title",
|
||||
"required" : true,
|
||||
"default" : false,
|
||||
"propertyOrder" : 1
|
||||
},
|
||||
"ipWhitelist" :
|
||||
{
|
||||
"type" : "array",
|
||||
"title" : "edt_conf_net_ipWhitelist_title",
|
||||
"required" : true,
|
||||
"items" : {
|
||||
"type": "string",
|
||||
"title" : "edt_conf_net_ip_itemtitle"
|
||||
},
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"internetAccessAPI": false
|
||||
}
|
||||
},
|
||||
"propertyOrder" : 2
|
||||
},
|
||||
"apiAuth" :
|
||||
{
|
||||
"type" : "boolean",
|
||||
"title" : "edt_conf_net_apiAuth_title",
|
||||
"required" : true,
|
||||
"default" : true,
|
||||
"propertyOrder" : 3
|
||||
},
|
||||
"localApiAuth" :
|
||||
{
|
||||
"type" : "boolean",
|
||||
"title" : "edt_conf_net_localApiAuth_title",
|
||||
"required" : true,
|
||||
"default" : false,
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"apiAuth": true
|
||||
}
|
||||
},
|
||||
"propertyOrder" : 4
|
||||
}
|
||||
},
|
||||
"additionalProperties" : false
|
||||
}
|
@ -6,20 +6,16 @@
|
||||
#include <QTcpSocket>
|
||||
#include <QHostAddress>
|
||||
|
||||
// websocket includes
|
||||
#include "webserver/WebSocketClient.h"
|
||||
|
||||
JsonClientConnection::JsonClientConnection(QTcpSocket *socket)
|
||||
JsonClientConnection::JsonClientConnection(QTcpSocket *socket, const bool& localConnection)
|
||||
: QObject()
|
||||
, _socket(socket)
|
||||
, _websocketClient(nullptr)
|
||||
, _receiveBuffer()
|
||||
, _log(Logger::getInstance("JSONCLIENTCONNECTION"))
|
||||
{
|
||||
connect(_socket, &QTcpSocket::disconnected, this, &JsonClientConnection::disconnected);
|
||||
connect(_socket, &QTcpSocket::readyRead, this, &JsonClientConnection::readRequest);
|
||||
// create a new instance of JsonAPI
|
||||
_jsonAPI = new JsonAPI(socket->peerAddress().toString(), _log, this);
|
||||
_jsonAPI = new JsonAPI(socket->peerAddress().toString(), _log, localConnection, this);
|
||||
// get the callback messages from JsonAPI and send it to the client
|
||||
connect(_jsonAPI,SIGNAL(callbackMessage(QJsonObject)),this,SLOT(sendMessage(QJsonObject)));
|
||||
}
|
||||
@ -27,21 +23,6 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket)
|
||||
void JsonClientConnection::readRequest()
|
||||
{
|
||||
_receiveBuffer += _socket->readAll();
|
||||
|
||||
// might be an old hyperion classic handshake request or raw socket data
|
||||
if(_receiveBuffer.contains("Upgrade: websocket"))
|
||||
{
|
||||
if(_websocketClient == Q_NULLPTR)
|
||||
{
|
||||
// disconnect this slot from socket for further requests
|
||||
disconnect(_socket, &QTcpSocket::readyRead, this, &JsonClientConnection::readRequest);
|
||||
int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19;
|
||||
QByteArray header(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data());
|
||||
_websocketClient = new WebSocketClient(header, _socket, this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// raw socket data, handling as usual
|
||||
int bytes = _receiveBuffer.indexOf('\n') + 1;
|
||||
while(bytes > 0)
|
||||
@ -59,7 +40,6 @@ void JsonClientConnection::readRequest()
|
||||
bytes = _receiveBuffer.indexOf('\n') + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qint64 JsonClientConnection::sendMessage(QJsonObject message)
|
||||
{
|
||||
|
@ -10,7 +10,6 @@
|
||||
|
||||
class JsonAPI;
|
||||
class QTcpSocket;
|
||||
class WebSocketClient;
|
||||
|
||||
///
|
||||
/// The Connection object created by \a JsonServer when a new connection is established
|
||||
@ -24,7 +23,7 @@ public:
|
||||
/// Constructor
|
||||
/// @param socket The Socket object for this connection
|
||||
///
|
||||
JsonClientConnection(QTcpSocket * socket);
|
||||
JsonClientConnection(QTcpSocket * socket, const bool& localConnection);
|
||||
|
||||
signals:
|
||||
void connectionClosed();
|
||||
@ -42,7 +41,6 @@ private slots:
|
||||
|
||||
private:
|
||||
QTcpSocket* _socket;
|
||||
WebSocketClient* _websocketClient;
|
||||
/// new instance of JsonAPI
|
||||
JsonAPI * _jsonAPI;
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
// bonjour include
|
||||
#include <bonjour/bonjourserviceregister.h>
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
// qt includes
|
||||
#include <QTcpServer>
|
||||
@ -19,6 +20,7 @@ JsonServer::JsonServer(const QJsonDocument& config)
|
||||
, _server(new QTcpServer(this))
|
||||
, _openConnections()
|
||||
, _log(Logger::getInstance("JSONSERVER"))
|
||||
, _netOrigin(NetOrigin::getInstance())
|
||||
{
|
||||
Debug(_log, "Created instance");
|
||||
|
||||
@ -94,14 +96,19 @@ void JsonServer::newConnection()
|
||||
while(_server->hasPendingConnections())
|
||||
{
|
||||
if (QTcpSocket * socket = _server->nextPendingConnection())
|
||||
{
|
||||
if(_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress()))
|
||||
{
|
||||
Debug(_log, "New connection from: %s ",socket->localAddress().toString().toStdString().c_str());
|
||||
JsonClientConnection * connection = new JsonClientConnection(socket);
|
||||
JsonClientConnection * connection = new JsonClientConnection(socket, _netOrigin->isLocalAddress(socket->peerAddress(), socket->localAddress()));
|
||||
_openConnections.insert(connection);
|
||||
|
||||
// register slot for cleaning up after the connection closed
|
||||
connect(connection, &JsonClientConnection::connectionClosed, this, &JsonServer::closedConnection);
|
||||
}
|
||||
else
|
||||
socket->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ bool ProviderUdp::init(const QJsonObject &deviceConfig)
|
||||
}
|
||||
|
||||
_port = deviceConfig["port"].toInt(_port);
|
||||
if ( (_port <= 0) || (_port > 65535) )
|
||||
if ( (_port <= 0) || (_port > MAX_PORT) )
|
||||
{
|
||||
throw std::runtime_error("invalid target port");
|
||||
}
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
class QUdpSocket;
|
||||
|
||||
#define MAX_PORT 65535
|
||||
|
||||
///
|
||||
/// The ProviderUdp implements an abstract base-class for LedDevices using UDP packets.
|
||||
///
|
||||
@ -54,6 +56,6 @@ protected:
|
||||
///
|
||||
QUdpSocket * _udpSocket;
|
||||
QHostAddress _address;
|
||||
quint16 _port;
|
||||
ushort _port;
|
||||
QString _defaultHost;
|
||||
};
|
||||
|
@ -1,6 +1,9 @@
|
||||
#include <protoserver/ProtoServer.h>
|
||||
#include "ProtoClientConnection.h"
|
||||
|
||||
// util
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
// qt
|
||||
#include <QJsonObject>
|
||||
#include <QTcpServer>
|
||||
@ -24,6 +27,7 @@ ProtoServer::~ProtoServer()
|
||||
|
||||
void ProtoServer::initServer()
|
||||
{
|
||||
_netOrigin = NetOrigin::getInstance();
|
||||
connect(_server, &QTcpServer::newConnection, this, &ProtoServer::newConnection);
|
||||
|
||||
// apply config
|
||||
@ -57,6 +61,8 @@ void ProtoServer::newConnection()
|
||||
while(_server->hasPendingConnections())
|
||||
{
|
||||
if(QTcpSocket * socket = _server->nextPendingConnection())
|
||||
{
|
||||
if(_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress()))
|
||||
{
|
||||
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
|
||||
ProtoClientConnection * client = new ProtoClientConnection(socket, _timeout, this);
|
||||
@ -64,6 +70,9 @@ void ProtoServer::newConnection()
|
||||
connect(client, &ProtoClientConnection::clientDisconnected, this, &ProtoServer::clientDisconnected);
|
||||
_openConnections.append(client);
|
||||
}
|
||||
else
|
||||
socket->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,20 +8,24 @@
|
||||
#include <hyperion/Hyperion.h>
|
||||
#include "HyperionConfig.h"
|
||||
|
||||
// utils includes
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
// qt includes
|
||||
#include <QUdpSocket>
|
||||
#include <QJsonObject>
|
||||
|
||||
using namespace hyperion;
|
||||
|
||||
UDPListener::UDPListener(const QJsonDocument& config) :
|
||||
QObject(),
|
||||
_server(new QUdpSocket(this)),
|
||||
_priority(0),
|
||||
_timeout(0),
|
||||
_log(Logger::getInstance("UDPLISTENER")),
|
||||
_isActive(false),
|
||||
_listenPort(0)
|
||||
UDPListener::UDPListener(const QJsonDocument& config)
|
||||
: QObject()
|
||||
, _server(new QUdpSocket(this))
|
||||
, _priority(0)
|
||||
, _timeout(0)
|
||||
, _log(Logger::getInstance("UDPLISTENER"))
|
||||
, _isActive(false)
|
||||
, _listenPort(0)
|
||||
, _netOrigin(NetOrigin::getInstance())
|
||||
{
|
||||
// listen for component change
|
||||
connect(Hyperion::getInstance(), &Hyperion::componentStateChanged, this, &UDPListener::componentStateChanged);
|
||||
@ -116,6 +120,8 @@ void UDPListener::readPendingDatagrams()
|
||||
quint16 senderPort;
|
||||
|
||||
_server->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
|
||||
|
||||
if(_netOrigin->accessAllowed(sender, _listenAddress))
|
||||
processTheDatagram(&datagram, &sender);
|
||||
}
|
||||
}
|
||||
|
76
libsrc/utils/NetOrigin.cpp
Normal file
76
libsrc/utils/NetOrigin.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
NetOrigin* NetOrigin::instance = nullptr;
|
||||
|
||||
NetOrigin::NetOrigin(QObject* parent, Logger* log)
|
||||
: QObject(parent)
|
||||
, _log(log)
|
||||
, _internetAccessAllowed(false)
|
||||
, _ipWhitelist()
|
||||
{
|
||||
NetOrigin::instance = this;
|
||||
}
|
||||
|
||||
bool NetOrigin::accessAllowed(const QHostAddress& address, const QHostAddress& local)
|
||||
{
|
||||
if(_internetAccessAllowed)
|
||||
return true;
|
||||
|
||||
if(_ipWhitelist.contains(address)) // v4 and v6
|
||||
return true;
|
||||
|
||||
if(!isLocalAddress(address, local))
|
||||
{
|
||||
Warning(_log,"Client connection with IP address '%s' has been rejected! It's not whitelisted, access denied.",QSTRING_CSTR(address.toString()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NetOrigin::isLocalAddress(const QHostAddress& address, const QHostAddress& local)
|
||||
{
|
||||
if(address.protocol() == QAbstractSocket::IPv4Protocol)
|
||||
{
|
||||
if(!address.isInSubnet(local, 24)) // 255.255.255.xxx; IPv4 0-32
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(address.protocol() == QAbstractSocket::IPv6Protocol)
|
||||
{
|
||||
if(!address.isInSubnet(local, 64)) // 2001:db8:abcd:0012:XXXX:XXXX:XXXX:XXXX; IPv6 0-128
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetOrigin::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config)
|
||||
{
|
||||
if(type == settings::NETWORK)
|
||||
{
|
||||
const QJsonObject& obj = config.object();
|
||||
_internetAccessAllowed = obj["internetAccessAPI"].toBool(false);
|
||||
|
||||
const QJsonArray& arr = obj["ipWhitelist"].toArray();
|
||||
_ipWhitelist.clear();
|
||||
|
||||
for(const auto& e : arr)
|
||||
{
|
||||
const QString& entry = e.toString("");
|
||||
if(entry.isEmpty())
|
||||
continue;
|
||||
|
||||
QHostAddress host(entry);
|
||||
if(host.isNull())
|
||||
{
|
||||
Warning(_log,"The whitelisted IP address '%s' isn't valid! Skipped",QSTRING_CSTR(entry));
|
||||
continue;
|
||||
}
|
||||
_ipWhitelist << host;
|
||||
}
|
||||
}
|
||||
}
|
@ -55,7 +55,6 @@ void CgiHandler::cmd_cfg_jsonserver()
|
||||
if ( _args.at(0) == "cfg_jsonserver" )
|
||||
{
|
||||
quint16 jsonPort = 19444;
|
||||
|
||||
// send result as reply
|
||||
_reply->addHeader ("Content-Type", "text/plain" );
|
||||
_reply->appendRawData (QByteArrayLiteral(":") % QString::number(jsonPort).toUtf8() );
|
||||
|
@ -4,8 +4,8 @@
|
||||
#include "QtHttpReply.h"
|
||||
#include "QtHttpServer.h"
|
||||
#include "QtHttpHeader.h"
|
||||
#include "WebSocketClient.h"
|
||||
#include "WebJsonRpc.h"
|
||||
#include "webserver/WebSocketClient.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QTcpSocket>
|
||||
@ -16,13 +16,14 @@
|
||||
|
||||
const QByteArray & QtHttpClientWrapper::CRLF = QByteArrayLiteral ("\r\n");
|
||||
|
||||
QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * parent)
|
||||
QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, const bool& localConnection, QtHttpServer * parent)
|
||||
: QObject (parent)
|
||||
, m_guid ("")
|
||||
, m_parsingStatus (AwaitingRequest)
|
||||
, m_sockClient (sock)
|
||||
, m_currentRequest (Q_NULLPTR)
|
||||
, m_serverHandle (parent)
|
||||
, m_localConnection(localConnection)
|
||||
, m_websocketClient(nullptr)
|
||||
, m_webJsonRpc (nullptr)
|
||||
{
|
||||
@ -120,7 +121,7 @@ void QtHttpClientWrapper::onClientDataReceived (void) {
|
||||
{
|
||||
// disconnect this slot from socket for further requests
|
||||
disconnect(m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
|
||||
m_websocketClient = new WebSocketClient(m_currentRequest->getHeader(QtHttpHeader::SecWebSocketKey), m_sockClient, this);
|
||||
m_websocketClient = new WebSocketClient(m_currentRequest, m_sockClient, m_localConnection, this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -149,7 +150,7 @@ void QtHttpClientWrapper::onClientDataReceived (void) {
|
||||
{
|
||||
if(m_webJsonRpc == Q_NULLPTR)
|
||||
{
|
||||
m_webJsonRpc = new WebJsonRpc(m_currentRequest, m_serverHandle, this);
|
||||
m_webJsonRpc = new WebJsonRpc(m_currentRequest, m_serverHandle, m_localConnection, this);
|
||||
}
|
||||
m_webJsonRpc->handleMessage(m_currentRequest);
|
||||
break;
|
||||
|
@ -16,7 +16,7 @@ class QtHttpClientWrapper : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * parent);
|
||||
explicit QtHttpClientWrapper (QTcpSocket * sock, const bool& localConnection, QtHttpServer * parent);
|
||||
|
||||
static const char SPACE = ' ';
|
||||
static const char COLON = ':';
|
||||
@ -50,6 +50,7 @@ private:
|
||||
QTcpSocket * m_sockClient;
|
||||
QtHttpRequest * m_currentRequest;
|
||||
QtHttpServer * m_serverHandle;
|
||||
const bool m_localConnection;
|
||||
WebSocketClient * m_websocketClient;
|
||||
WebJsonRpc * m_webJsonRpc;
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
#include "QtHttpServer.h"
|
||||
#include "QtHttpRequest.h"
|
||||
#include "QtHttpReply.h"
|
||||
@ -5,113 +6,105 @@
|
||||
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
const QString & QtHttpServer::HTTP_VERSION = QStringLiteral ("HTTP/1.1");
|
||||
|
||||
QtHttpServerWrapper::QtHttpServerWrapper (QObject * parent)
|
||||
: QTcpServer (parent)
|
||||
, m_useSsl (false)
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
QtHttpServerWrapper::~QtHttpServerWrapper (void)
|
||||
{
|
||||
}
|
||||
QtHttpServerWrapper::~QtHttpServerWrapper (void) { }
|
||||
|
||||
void QtHttpServerWrapper::setUseSecure (const bool ssl) {
|
||||
m_useSsl = ssl;
|
||||
}
|
||||
|
||||
void QtHttpServerWrapper::incomingConnection (qintptr handle)
|
||||
{
|
||||
void QtHttpServerWrapper::incomingConnection (qintptr handle) {
|
||||
QTcpSocket * sock = (m_useSsl
|
||||
? new QSslSocket (this)
|
||||
: new QTcpSocket (this));
|
||||
(sock->setSocketDescriptor (handle))
|
||||
? addPendingConnection (sock)
|
||||
: delete sock;
|
||||
if (sock->setSocketDescriptor (handle)) {
|
||||
addPendingConnection (sock);
|
||||
}
|
||||
else {
|
||||
delete sock;
|
||||
}
|
||||
}
|
||||
|
||||
QtHttpServer::QtHttpServer (QObject * parent)
|
||||
: QObject (parent)
|
||||
, m_useSsl (false)
|
||||
, m_serverName (QStringLiteral ("The Qt5 HTTP Server"))
|
||||
, m_netOrigin (NetOrigin::getInstance())
|
||||
{
|
||||
m_sockServer = new QtHttpServerWrapper (this);
|
||||
connect (m_sockServer, &QtHttpServerWrapper::newConnection, this, &QtHttpServer::onClientConnected);
|
||||
}
|
||||
|
||||
const QString & QtHttpServer::getServerName (void) const
|
||||
{
|
||||
const QString & QtHttpServer::getServerName (void) const {
|
||||
return m_serverName;
|
||||
}
|
||||
|
||||
quint16 QtHttpServer::getServerPort (void) const
|
||||
{
|
||||
quint16 QtHttpServer::getServerPort (void) const {
|
||||
return m_sockServer->serverPort ();
|
||||
}
|
||||
|
||||
QString QtHttpServer::getErrorString (void) const
|
||||
{
|
||||
QString QtHttpServer::getErrorString (void) const {
|
||||
return m_sockServer->errorString ();
|
||||
}
|
||||
|
||||
void QtHttpServer::start (quint16 port)
|
||||
{
|
||||
void QtHttpServer::start (quint16 port) {
|
||||
if(!m_sockServer->isListening())
|
||||
(m_sockServer->listen (QHostAddress::Any, port))
|
||||
? emit started (m_sockServer->serverPort ())
|
||||
: emit error (m_sockServer->errorString ());
|
||||
{
|
||||
if (m_sockServer->listen (QHostAddress::Any, port)) {
|
||||
emit started (m_sockServer->serverPort ());
|
||||
}
|
||||
else {
|
||||
emit error (m_sockServer->errorString ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpServer::stop (void)
|
||||
{
|
||||
if (m_sockServer->isListening ())
|
||||
{
|
||||
void QtHttpServer::stop (void) {
|
||||
if (m_sockServer->isListening ()) {
|
||||
m_sockServer->close ();
|
||||
|
||||
// disconnect clients
|
||||
const QList<QTcpSocket*> socks = m_socksClientsHash.keys();
|
||||
for(auto sock : socks)
|
||||
{
|
||||
sock->close();
|
||||
}
|
||||
|
||||
emit stopped ();
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpServer::setServerName (const QString & serverName)
|
||||
{
|
||||
void QtHttpServer::setServerName (const QString & serverName) {
|
||||
m_serverName = serverName;
|
||||
}
|
||||
|
||||
void QtHttpServer::setUseSecure (const bool ssl)
|
||||
{
|
||||
void QtHttpServer::setUseSecure (const bool ssl) {
|
||||
m_useSsl = ssl;
|
||||
m_sockServer->setUseSecure (m_useSsl);
|
||||
}
|
||||
|
||||
void QtHttpServer::setPrivateKey (const QSslKey & key)
|
||||
{
|
||||
void QtHttpServer::setPrivateKey (const QSslKey & key) {
|
||||
m_sslKey = key;
|
||||
}
|
||||
|
||||
void QtHttpServer::setCertificates (const QList<QSslCertificate> & certs)
|
||||
{
|
||||
void QtHttpServer::setCertificates (const QList<QSslCertificate> & certs) {
|
||||
m_sslCerts = certs;
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientConnected (void)
|
||||
{
|
||||
while (m_sockServer->hasPendingConnections ())
|
||||
{
|
||||
if (QTcpSocket * sock = m_sockServer->nextPendingConnection ())
|
||||
void QtHttpServer::onClientConnected (void) {
|
||||
while (m_sockServer->hasPendingConnections ()) {
|
||||
if (QTcpSocket * sock = m_sockServer->nextPendingConnection ()) {
|
||||
if(m_netOrigin->accessAllowed(sock->peerAddress(), sock->localAddress()))
|
||||
{
|
||||
connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
|
||||
if (m_useSsl)
|
||||
{
|
||||
if (QSslSocket * ssl = qobject_cast<QSslSocket *> (sock))
|
||||
{
|
||||
if (m_useSsl) {
|
||||
if (QSslSocket * ssl = qobject_cast<QSslSocket *> (sock)) {
|
||||
connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors);
|
||||
connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted);
|
||||
connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError);
|
||||
@ -122,38 +115,35 @@ void QtHttpServer::onClientConnected (void)
|
||||
ssl->startServerEncryption ();
|
||||
}
|
||||
}
|
||||
QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, this);
|
||||
QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, m_netOrigin->isLocalAddress(sock->peerAddress(), sock->localAddress()), this);
|
||||
m_socksClientsHash.insert (sock, wrapper);
|
||||
emit clientConnected (wrapper->getGuid ());
|
||||
}
|
||||
else
|
||||
{
|
||||
sock->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientSslEncrypted (void)
|
||||
{
|
||||
}
|
||||
void QtHttpServer::onClientSslEncrypted (void) { }
|
||||
|
||||
void QtHttpServer::onClientSslPeerVerifyError (const QSslError & err)
|
||||
{
|
||||
void QtHttpServer::onClientSslPeerVerifyError (const QSslError & err) {
|
||||
Q_UNUSED (err)
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientSslErrors (const QList<QSslError> & errors)
|
||||
{
|
||||
void QtHttpServer::onClientSslErrors (const QList<QSslError> & errors) {
|
||||
Q_UNUSED (errors)
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientSslModeChanged (QSslSocket::SslMode mode)
|
||||
{
|
||||
void QtHttpServer::onClientSslModeChanged (QSslSocket::SslMode mode) {
|
||||
Q_UNUSED (mode)
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientDisconnected (void)
|
||||
{
|
||||
if (QTcpSocket * sockClient = qobject_cast<QTcpSocket *> (sender ()))
|
||||
{
|
||||
if (QtHttpClientWrapper * wrapper = m_socksClientsHash.value (sockClient, Q_NULLPTR))
|
||||
{
|
||||
void QtHttpServer::onClientDisconnected (void) {
|
||||
if (QTcpSocket * sockClient = qobject_cast<QTcpSocket *> (sender ())) {
|
||||
if (QtHttpClientWrapper * wrapper = m_socksClientsHash.value (sockClient, Q_NULLPTR)) {
|
||||
emit clientDisconnected (wrapper->getGuid ());
|
||||
wrapper->deleteLater ();
|
||||
m_socksClientsHash.remove (sockClient);
|
||||
|
@ -17,6 +17,7 @@ class QTcpServer;
|
||||
class QtHttpRequest;
|
||||
class QtHttpReply;
|
||||
class QtHttpClientWrapper;
|
||||
class NetOrigin;
|
||||
|
||||
class QtHttpServerWrapper : public QTcpServer {
|
||||
Q_OBJECT
|
||||
@ -48,8 +49,7 @@ public:
|
||||
|
||||
quint16 getServerPort (void) const;
|
||||
QString getErrorString (void) const;
|
||||
|
||||
bool isListening(void) { return m_sockServer->isListening(); };
|
||||
const bool isListening() { return m_sockServer->isListening(); };
|
||||
|
||||
public slots:
|
||||
void start (quint16 port = 0);
|
||||
@ -80,9 +80,9 @@ private:
|
||||
QSslKey m_sslKey;
|
||||
QList<QSslCertificate> m_sslCerts;
|
||||
QString m_serverName;
|
||||
NetOrigin* m_netOrigin;
|
||||
QtHttpServerWrapper * m_sockServer;
|
||||
QHash<QTcpSocket *, QtHttpClientWrapper *> m_socksClientsHash;
|
||||
};
|
||||
|
||||
#endif // QTHTTPSERVER_H
|
||||
|
||||
|
@ -23,7 +23,7 @@ StaticFileServing::StaticFileServing (QObject * parent)
|
||||
|
||||
StaticFileServing::~StaticFileServing ()
|
||||
{
|
||||
delete _mimeDb;
|
||||
|
||||
}
|
||||
|
||||
void StaticFileServing::setBaseUrl(const QString& url)
|
||||
|
@ -6,22 +6,23 @@
|
||||
|
||||
#include <api/JsonAPI.h>
|
||||
|
||||
WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClientWrapper* parent)
|
||||
WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, const bool& localConnection, QtHttpClientWrapper* parent)
|
||||
: QObject(parent)
|
||||
, _server(server)
|
||||
, _wrapper(parent)
|
||||
, _log(Logger::getInstance("HTTPJSONRPC"))
|
||||
{
|
||||
const QString client = request->getClientInfo().clientAddress.toString();
|
||||
_jsonAPI = new JsonAPI(client, _log, this, true);
|
||||
_jsonAPI = new JsonAPI(client, _log, localConnection, this, true);
|
||||
connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebJsonRpc::handleCallback);
|
||||
}
|
||||
|
||||
void WebJsonRpc::handleMessage(QtHttpRequest* request)
|
||||
{
|
||||
QByteArray header = request->getHeader("Authorization");
|
||||
QByteArray data = request->getRawData();
|
||||
_unlocked = true;
|
||||
_jsonAPI->handleMessage(data);
|
||||
_jsonAPI->handleMessage(data,header);
|
||||
}
|
||||
|
||||
void WebJsonRpc::handleCallback(QJsonObject obj)
|
||||
|
@ -1,9 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
// utils includes
|
||||
#include <utils/Logger.h>
|
||||
|
||||
// qt includes
|
||||
#include <QJsonObject>
|
||||
|
||||
class QtHttpServer;
|
||||
@ -14,7 +12,7 @@ class JsonAPI;
|
||||
class WebJsonRpc : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClientWrapper* parent);
|
||||
WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, const bool& localConnection, QtHttpClientWrapper* parent);
|
||||
|
||||
void handleMessage(QtHttpRequest* request);
|
||||
|
||||
|
@ -18,6 +18,7 @@ WebServer::WebServer(const QJsonDocument& config, QObject * parent)
|
||||
, _log(Logger::getInstance("WEBSERVER"))
|
||||
, _server()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
WebServer::~WebServer()
|
||||
|
@ -1,39 +1,37 @@
|
||||
#include "webserver/WebSocketClient.h"
|
||||
#include "WebSocketClient.h"
|
||||
#include "QtHttpRequest.h"
|
||||
#include "QtHttpHeader.h"
|
||||
|
||||
// hyperion includes
|
||||
#include <hyperion/Hyperion.h>
|
||||
|
||||
// JsonAPI includes
|
||||
#include <api/JsonAPI.h>
|
||||
|
||||
// qt includes
|
||||
#include <QTcpSocket>
|
||||
#include <QtEndian>
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonObject>
|
||||
#include <QHostAddress>
|
||||
|
||||
|
||||
WebSocketClient::WebSocketClient(QByteArray socketKey, QTcpSocket* sock, QObject* parent)
|
||||
WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, const bool& localConnection, QObject* parent)
|
||||
: QObject(parent)
|
||||
, _socket(sock)
|
||||
, _secWebSocketKey(socketKey)
|
||||
, _log(Logger::getInstance("WEBSOCKET"))
|
||||
// , _hyperion(Hyperion::getInstance())
|
||||
{
|
||||
// connect socket; disconnect handled from QtHttpServer
|
||||
connect(_socket, &QTcpSocket::readyRead , this, &WebSocketClient::handleWebSocketFrame);
|
||||
|
||||
const QString client = sock->peerAddress().toString();
|
||||
// QtHttpRequest contains all headers for handshake
|
||||
QByteArray secWebSocketKey = request->getHeader(QtHttpHeader::SecWebSocketKey);
|
||||
const QString client = request->getClientInfo().clientAddress.toString();
|
||||
|
||||
// Json processor
|
||||
_jsonAPI = new JsonAPI(client, _log, this);
|
||||
_jsonAPI = new JsonAPI(client, _log, localConnection, this);
|
||||
connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebSocketClient::sendMessage);
|
||||
|
||||
Debug(_log, "New connection from %s", QSTRING_CSTR(client));
|
||||
|
||||
// do handshake
|
||||
_secWebSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
QByteArray hash = QCryptographicHash::hash(_secWebSocketKey, QCryptographicHash::Sha1).toBase64();
|
||||
secWebSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
QByteArray hash = QCryptographicHash::hash(secWebSocketKey, QCryptographicHash::Sha1).toBase64();
|
||||
|
||||
QString data
|
||||
= QString("HTTP/1.1 101 Switching Protocols\r\n")
|
||||
@ -113,14 +111,14 @@ void WebSocketClient::handleWebSocketFrame(void)
|
||||
if (_wsh.fin)
|
||||
{
|
||||
_onContinuation = false;
|
||||
// if (_wsh.opCode == OPCODE::TEXT)
|
||||
// {
|
||||
if (_wsh.opCode == OPCODE::TEXT)
|
||||
{
|
||||
_jsonAPI->handleMessage(QString(_wsReceiveBuffer));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// handleBinaryMessage(_wsReceiveBuffer);
|
||||
// }
|
||||
}
|
||||
else
|
||||
{
|
||||
handleBinaryMessage(_wsReceiveBuffer);
|
||||
}
|
||||
_wsReceiveBuffer.clear();
|
||||
}
|
||||
}
|
||||
@ -223,7 +221,6 @@ void WebSocketClient::sendClose(int status, QString reason)
|
||||
_socket->close();
|
||||
}
|
||||
|
||||
/*
|
||||
void WebSocketClient::handleBinaryMessage(QByteArray &data)
|
||||
{
|
||||
//uint8_t priority = data.at(0);
|
||||
@ -242,10 +239,9 @@ void WebSocketClient::handleBinaryMessage(QByteArray &data)
|
||||
image.resize(width, height);
|
||||
|
||||
memcpy(image.memptr(), data.data()+4, imgSize);
|
||||
_hyperion->registerInput();
|
||||
_hyperion->setInputImage(priority, image, duration_s*1000);
|
||||
//_hyperion->registerInput();
|
||||
//_hyperion->setInputImage(priority, image, duration_s*1000);
|
||||
}
|
||||
*/
|
||||
|
||||
qint64 WebSocketClient::sendMessage(QJsonObject obj)
|
||||
{
|
||||
|
@ -1,16 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/Logger.h>
|
||||
#include "webserver/WebSocketUtils.h"
|
||||
#include <QJsonObject>
|
||||
#include "WebSocketUtils.h"
|
||||
|
||||
class QTcpSocket;
|
||||
|
||||
class QtHttpRequest;
|
||||
class Hyperion;
|
||||
class JsonAPI;
|
||||
|
||||
class WebSocketClient : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
WebSocketClient(QByteArray socketKey, QTcpSocket* sock, QObject* parent);
|
||||
WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, const bool& localConnection, QObject* parent);
|
||||
|
||||
struct WebSocketHeader
|
||||
{
|
||||
@ -23,13 +25,13 @@ public:
|
||||
|
||||
private:
|
||||
QTcpSocket* _socket;
|
||||
QByteArray _secWebSocketKey;
|
||||
Logger* _log;
|
||||
Hyperion* _hyperion;
|
||||
JsonAPI* _jsonAPI;
|
||||
|
||||
void getWsFrameHeader(WebSocketHeader* header);
|
||||
void sendClose(int status, QString reason = "");
|
||||
// void handleBinaryMessage(QByteArray &data);
|
||||
void handleBinaryMessage(QByteArray &data);
|
||||
qint64 sendMessage_Raw(const char* data, quint64 size);
|
||||
qint64 sendMessage_Raw(QByteArray &data);
|
||||
QByteArray makeFrameHeader(quint8 opCode, quint64 payloadLength, bool lastFrame);
|
@ -543,6 +543,25 @@ void JsonConnection::setVideoMode(QString videoMode)
|
||||
parseReply(reply);
|
||||
}
|
||||
|
||||
void JsonConnection::setToken(const QString &token)
|
||||
{
|
||||
// create command
|
||||
QJsonObject command;
|
||||
command["command"] = QString("authorize");
|
||||
command["subcommand"] = QString("login");
|
||||
|
||||
if (token.size() < 36)
|
||||
throw std::runtime_error("The given token length is too short.");
|
||||
|
||||
command["token"] = token;
|
||||
|
||||
// send command message
|
||||
QJsonObject reply = sendMessage(command);
|
||||
|
||||
// parse reply message
|
||||
parseReply(reply);
|
||||
}
|
||||
|
||||
QJsonObject JsonConnection::sendMessage(const QJsonObject & message)
|
||||
{
|
||||
// serialize message
|
||||
|
@ -171,6 +171,9 @@ public:
|
||||
// sets video mode 3D/2D
|
||||
void setVideoMode(QString videoMode);
|
||||
|
||||
// set the specified authorization token
|
||||
void setToken(const QString &token);
|
||||
|
||||
|
||||
private:
|
||||
///
|
||||
|
@ -59,8 +59,12 @@ int main(int argc, char * argv[])
|
||||
// create the option parser and initialize all parameters
|
||||
Parser parser("Application to send a command to hyperion using the Json interface");
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// art variable definition append art to Parser short-, long option description, optional default value //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Option & argAddress = parser.add<Option> ('a', "address" , "Set the address of the hyperion server [default: %1]", "localhost:19444");
|
||||
IntOption & argPriority = parser.add<IntOption> ('p', "priority" , "Use to the provided priority channel (suggested 2-99) [default: %1]", "50");
|
||||
Option & argToken = parser.add<Option> ('t', "token " , "If authorization tokens are required, this token is used");
|
||||
IntOption & argPriority = parser.add<IntOption> ('p', "priority" , "Used to the provided priority channel (suggested 2-99) [default: %1]", "50");
|
||||
IntOption & argDuration = parser.add<IntOption> ('d', "duration" , "Specify how long the leds should be switched on in milliseconds [default: infinity]");
|
||||
ColorsOption & argColor = parser.add<ColorsOption> ('c', "color" , "Set all leds to a constant color (either RRGGBB hex getColors or a color name. The color may be repeated multiple time like: RRGGBBRRGGBB)");
|
||||
ImageOption & argImage = parser.add<ImageOption> ('i', "image" , "Set the leds to the colors according to the given image file");
|
||||
@ -95,8 +99,8 @@ int main(int argc, char * argv[])
|
||||
Option & argVideoMode = parser.add<Option> ('V', "videoMode" , "Set the video mode valid values: 2D, 3DSBS, 3DTAB");
|
||||
IntOption & argSource = parser.add<IntOption> (0x0, "sourceSelect" , "Set current active priority channel and deactivate auto source switching");
|
||||
BooleanOption & argSourceAuto = parser.add<BooleanOption>(0x0, "sourceAutoSelect" , "Enables auto source, if disabled prio by manual selecting input source");
|
||||
BooleanOption & argOff = parser.add<BooleanOption>(0x0, "off", "deactivates hyperion");
|
||||
BooleanOption & argOn = parser.add<BooleanOption>(0x0, "on", "activates hyperion");
|
||||
BooleanOption & argOff = parser.add<BooleanOption>(0x0, "off" , "Deactivates hyperion");
|
||||
BooleanOption & argOn = parser.add<BooleanOption>(0x0, "on" , "Activates hyperion");
|
||||
BooleanOption & argConfigGet = parser.add<BooleanOption>(0x0, "configGet" , "Print the current loaded Hyperion configuration file");
|
||||
BooleanOption & argSchemaGet = parser.add<BooleanOption>(0x0, "schemaGet" , "Print the json schema for Hyperion configuration");
|
||||
Option & argConfigSet = parser.add<Option> (0x0, "configSet" , "Write to the actual loaded configuration file. Should be a Json object string.");
|
||||
@ -157,6 +161,10 @@ int main(int argc, char * argv[])
|
||||
// create the connection to the hyperion server
|
||||
JsonConnection connection(argAddress.value(parser), parser.isSet(argPrint));
|
||||
|
||||
// authorization token specified. Use it first
|
||||
if (parser.isSet(argToken))
|
||||
connection.setToken(argToken.value(parser));
|
||||
|
||||
// now execute the given command
|
||||
if (parser.isSet(argColor))
|
||||
{
|
||||
|
@ -21,9 +21,11 @@ target_link_libraries(hyperiond
|
||||
webserver
|
||||
bonjour
|
||||
ssdp
|
||||
database
|
||||
python
|
||||
resources
|
||||
${PYTHON_LIBRARIES}
|
||||
Qt5::Widgets
|
||||
)
|
||||
|
||||
if (ENABLE_AMLOGIC)
|
||||
@ -68,8 +70,6 @@ if (ENABLE_QT)
|
||||
target_link_libraries(hyperiond qt-grabber)
|
||||
endif ()
|
||||
|
||||
target_link_libraries(hyperiond Qt5::Widgets)
|
||||
|
||||
install ( TARGETS hyperiond DESTINATION "share/hyperion/bin/" COMPONENT "${PLATFORM}" )
|
||||
install ( DIRECTORY ${CMAKE_SOURCE_DIR}/bin/service DESTINATION "share/hyperion/" COMPONENT "${PLATFORM}" )
|
||||
install ( FILES ${CMAKE_SOURCE_DIR}/effects/readme.txt DESTINATION "share/hyperion/effects" COMPONENT "${PLATFORM}" )
|
||||
|
@ -40,6 +40,12 @@
|
||||
// settings
|
||||
#include <hyperion/SettingsManager.h>
|
||||
|
||||
// AuthManager
|
||||
#include <hyperion/AuthManager.h>
|
||||
|
||||
// NetOrigin checks
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
// Init Python
|
||||
#include <python/PythonInit.h>
|
||||
|
||||
@ -51,7 +57,9 @@ HyperionDaemon* HyperionDaemon::daemon = nullptr;
|
||||
HyperionDaemon::HyperionDaemon(QString configFile, const QString rootPath, QObject *parent, const bool& logLvlOverwrite)
|
||||
: QObject(parent)
|
||||
, _log(Logger::getInstance("DAEMON"))
|
||||
, _authManager(new AuthManager(rootPath, this))
|
||||
, _bonjourBrowserWrapper(new BonjourBrowserWrapper())
|
||||
, _netOrigin(new NetOrigin(this))
|
||||
, _pyInit(new PythonInit())
|
||||
, _webserver(nullptr)
|
||||
, _jsonServer(nullptr)
|
||||
@ -88,6 +96,14 @@ HyperionDaemon::HyperionDaemon(QString configFile, const QString rootPath, QObje
|
||||
EffectFileHandler* efh = new EffectFileHandler(rootPath, getSetting(settings::EFFECTS), this);
|
||||
connect(this, &HyperionDaemon::settingsChanged, efh, &EffectFileHandler::handleSettingsUpdate);
|
||||
|
||||
// connect and apply settings for AuthManager
|
||||
connect(this, &HyperionDaemon::settingsChanged, _authManager, &AuthManager::handleSettingsUpdate);
|
||||
_authManager->handleSettingsUpdate(settings::NETWORK, _settingsManager->getSetting(settings::NETWORK));
|
||||
|
||||
// connect and apply settings for NetOrigin
|
||||
connect(this, &HyperionDaemon::settingsChanged, _netOrigin, &NetOrigin::handleSettingsUpdate);
|
||||
_netOrigin->handleSettingsUpdate(settings::NETWORK, _settingsManager->getSetting(settings::NETWORK));
|
||||
|
||||
// spawn all Hyperion instances before network services
|
||||
_hyperion = Hyperion::initInstance(this, 0, configFile, rootPath);
|
||||
|
||||
|
@ -63,6 +63,8 @@ class PythonInit;
|
||||
class SSDPHandler;
|
||||
class FlatBufferServer;
|
||||
class ProtoServer;
|
||||
class AuthManager;
|
||||
class NetOrigin;
|
||||
|
||||
class HyperionDaemon : public QObject
|
||||
{
|
||||
@ -131,7 +133,9 @@ private:
|
||||
void createGrabberQt(const QJsonObject & grabberConfig);
|
||||
|
||||
Logger* _log;
|
||||
AuthManager* _authManager;
|
||||
BonjourBrowserWrapper* _bonjourBrowserWrapper;
|
||||
NetOrigin* _netOrigin;
|
||||
PythonInit* _pyInit;
|
||||
WebServer* _webserver;
|
||||
JsonServer* _jsonServer;
|
||||
|
Loading…
Reference in New Issue
Block a user