- 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:
Paulchen-Panther 2019-07-12 16:54:26 +02:00
parent 4fc745e748
commit ea796160af
No known key found for this signature in database
GPG Key ID: 84E3B692456B6840
72 changed files with 2546 additions and 485 deletions

76
.codedocs Normal file
View 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 =
#

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -10,11 +10,11 @@ body{font-family:Roboto,"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:15
padding-bottom:50px;
}
body{
overflow-y: scroll;
overflow-y: scroll;
}
.btn{margin: 2px 0;}
/*
#page-wrapper a[target=_blank]::after {
#page-wrapper a[target=_blank]::after {
content:"\f08e";
position:relative;
font:normal normal normal 10px/1 FontAwesome;
@ -109,7 +109,7 @@ table label{margin:0}
.colorpicker-2x .colorpicker-saturation {width: 200px;height: 200px;}
.colorpicker-2x .colorpicker-hue,.colorpicker-2x .colorpicker-alpha {width: 30px;height: 200px;}
.colorpicker-2x .colorpicker-color,.colorpicker-2x .colorpicker-color div {height: 30px;}
/*Hint*/
.info-hint{
background-color:rgb(236,236,236);
@ -123,7 +123,7 @@ table label{margin:0}
box-shadow: 1px 1px 1px 0px rgba(0,0,0,0.25);
font-size:97%;
}
/*Support page*/
.unlink,.unlink:hover{color:#333;text-decoration:none;}
.support-container ul{padding-left:0px;list-style-type: none;}

View File

@ -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",

View File

@ -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",

View File

@ -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();
});

View File

@ -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) {

View File

@ -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'));
@ -54,7 +61,20 @@ $(document).ready( function() {
if(storedAccess != 'default')
$('#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");

View File

@ -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"]');

View File

@ -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)
@ -133,6 +134,7 @@ $(document).ready( function() {
hyperionAddress = 'http://'+window.wSess[i].address+':'+window.wSess[i].port
$('#id_select').append(createSelOpt(hyperionAddress, window.wSess[i].host))
}
}
$('#id_btn_saveset').off().on('click',function() {

View File

@ -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);

View File

@ -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}" )

View File

@ -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()

View File

@ -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" :
{

View File

@ -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,

View File

@ -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()

View File

@ -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>

View File

@ -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

View File

@ -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
View 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
View 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
View 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;
};

View File

@ -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;

View 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();
};

View File

@ -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,43 +18,38 @@ 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 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
/// @param config The entire config object
/// @param correct If true will correct json against schema before save
/// @return True on success else false
/// @return True on success else false
///
bool saveSettings(QJsonObject config, const bool& correct = false);
///
/// @brief get a single setting json from config
/// @param type The settings::type from enum
/// @return The requested json data as QJsonDocument
/// @param type The settings::type from enum
/// @return The requested json data as QJsonDocument
///
const QJsonDocument getSetting(const settings::type& type);
///
/// @brief get the full settings object of this instance (with global settings)
/// @return The requested json
/// @return The requested json
///
const QJsonObject & getSettings() { return _qconfig; };
signals:
///
/// @brief Emits whenever a config part changed.
/// @param type The settings type from enum
/// @param data The data as QJsonDocument
/// @param type The settings type from enum
/// @param data The data as QJsonDocument
///
void settingsChanged(const settings::type& type, const QJsonDocument& data);
@ -64,6 +60,9 @@ private:
/// Logger instance
Logger* _log;
/// instance of database table interface
SettingsTable* _sTable;
/// the schema
static QJsonObject schemaJson;

View File

@ -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;

View File

@ -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
View 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;
};

View File

@ -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

View File

@ -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)

View 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
}

View File

@ -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"]
}
}
}

View File

@ -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>

View 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;
@ -84,6 +99,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);
@ -104,13 +142,14 @@ 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
// handle not implemented commands
else handleNotImplemented ();
else handleNotImplemented();
}
void JsonAPI::handleColorCommand(const QJsonObject& message, const QString& command, const int tan)
@ -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
View 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
View 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;
}
}
}

View File

@ -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
@ -58,11 +62,16 @@ void FlatBufferServer::newConnection()
{
if(QTcpSocket* socket = _server->nextPendingConnection())
{
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
FlatBufferClient *client = new FlatBufferClient(socket, _timeout, this);
// internal
connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected);
_openConnections.append(client);
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);
// internal
connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected);
_openConnections.append(client);
}
else
socket->close();
}
}
}

View 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();
}

View File

@ -25,5 +25,6 @@ target_link_libraries(hyperion
bonjour
boblightserver
effectengine
database
${QT_LIBRARIES}
)

View File

@ -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();

View File

@ -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"))
{
Q_INIT_RESOURCE(resource);
// get schema
if(schemaJson.isEmpty())
// transform json to string lists
QStringList keyList = defaultConfig.keys();
QStringList defValueList;
for(const auto key : keyList)
{
try
if(defaultConfig[key].isObject())
{
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;
}

View File

@ -79,6 +79,10 @@
{
"$ref": "schema-instCapture.json"
},
"network":
{
"$ref": "schema-network.json"
},
"ledConfig":
{
"$ref": "schema-ledConfig.json"

View File

@ -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>

View 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
}

View File

@ -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,37 +23,21 @@ 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"))
// raw socket data, handling as usual
int bytes = _receiveBuffer.indexOf('\n') + 1;
while(bytes > 0)
{
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)
{
// create message string
QString message(QByteArray(_receiveBuffer.data(), bytes));
// create message string
QString message(QByteArray(_receiveBuffer.data(), bytes));
// remove message data from buffer
_receiveBuffer = _receiveBuffer.mid(bytes);
// remove message data from buffer
_receiveBuffer = _receiveBuffer.mid(bytes);
// handle message
_jsonAPI->handleMessage(message);
// handle message
_jsonAPI->handleMessage(message);
// try too look up '\n' again
bytes = _receiveBuffer.indexOf('\n') + 1;
}
// try too look up '\n' again
bytes = _receiveBuffer.indexOf('\n') + 1;
}
}

View File

@ -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;

View File

@ -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");
@ -95,12 +97,17 @@ void JsonServer::newConnection()
{
if (QTcpSocket * socket = _server->nextPendingConnection())
{
Debug(_log, "New connection from: %s ",socket->localAddress().toString().toStdString().c_str());
JsonClientConnection * connection = new JsonClientConnection(socket);
_openConnections.insert(connection);
if(_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress()))
{
Debug(_log, "New connection from: %s ",socket->localAddress().toString().toStdString().c_str());
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);
// register slot for cleaning up after the connection closed
connect(connection, &JsonClientConnection::connectionClosed, this, &JsonServer::closedConnection);
}
else
socket->close();
}
}
}

View File

@ -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");
}

View File

@ -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;
};

View File

@ -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
@ -58,11 +62,16 @@ void ProtoServer::newConnection()
{
if(QTcpSocket * socket = _server->nextPendingConnection())
{
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
ProtoClientConnection * client = new ProtoClientConnection(socket, _timeout, this);
// internal
connect(client, &ProtoClientConnection::clientDisconnected, this, &ProtoServer::clientDisconnected);
_openConnections.append(client);
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);
// internal
connect(client, &ProtoClientConnection::clientDisconnected, this, &ProtoServer::clientDisconnected);
_openConnections.append(client);
}
else
socket->close();
}
}
}

View File

@ -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,7 +120,9 @@ void UDPListener::readPendingDatagrams()
quint16 senderPort;
_server->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
processTheDatagram(&datagram, &sender);
if(_netOrigin->accessAllowed(sender, _listenAddress))
processTheDatagram(&datagram, &sender);
}
}

View 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;
}
}
}

View File

@ -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() );

View File

@ -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,15 +16,16 @@
const QByteArray & QtHttpClientWrapper::CRLF = QByteArrayLiteral ("\r\n");
QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * parent)
: QObject (parent)
, m_guid ("")
, m_parsingStatus (AwaitingRequest)
, m_sockClient (sock)
, m_currentRequest (Q_NULLPTR)
, m_serverHandle (parent)
, m_websocketClient(nullptr)
, m_webJsonRpc (nullptr)
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)
{
connect (m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
}
@ -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;

View File

@ -13,43 +13,44 @@ class WebSocketClient;
class WebJsonRpc;
class QtHttpClientWrapper : public QObject {
Q_OBJECT
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 = ':';
static const QByteArray & CRLF;
static const char SPACE = ' ';
static const char COLON = ':';
static const QByteArray & CRLF;
enum ParsingStatus {
ParsingError = -1,
AwaitingRequest = 0,
AwaitingHeaders = 1,
AwaitingContent = 2,
RequestParsed = 3
};
enum ParsingStatus {
ParsingError = -1,
AwaitingRequest = 0,
AwaitingHeaders = 1,
AwaitingContent = 2,
RequestParsed = 3
};
QString getGuid (void);
QString getGuid (void);
/// @brief Wrapper for sendReplyToClient(), handles m_parsingStatus and signal connect
void sendToClientWithReply (QtHttpReply * reply);
private slots:
void onClientDataReceived (void);
void onClientDataReceived (void);
protected:
ParsingStatus sendReplyToClient (QtHttpReply * reply);
ParsingStatus sendReplyToClient (QtHttpReply * reply);
protected slots:
void onReplySendHeadersRequested (void);
void onReplySendDataRequested (void);
void onReplySendHeadersRequested (void);
void onReplySendDataRequested (void);
private:
QString m_guid;
ParsingStatus m_parsingStatus;
QTcpSocket * m_sockClient;
QtHttpRequest * m_currentRequest;
QtHttpServer * m_serverHandle;
QString m_guid;
ParsingStatus m_parsingStatus;
QTcpSocket * m_sockClient;
QtHttpRequest * m_currentRequest;
QtHttpServer * m_serverHandle;
const bool m_localConnection;
WebSocketClient * m_websocketClient;
WebJsonRpc * m_webJsonRpc;
};

View File

@ -1,3 +1,4 @@
#include "QtHttpServer.h"
#include "QtHttpRequest.h"
#include "QtHttpReply.h"
@ -5,158 +6,147 @@
#include <QUrlQuery>
#include <utils/NetOrigin.h>
const QString & QtHttpServer::HTTP_VERSION = QStringLiteral ("HTTP/1.1");
QtHttpServerWrapper::QtHttpServerWrapper (QObject * parent)
: QTcpServer (parent)
, m_useSsl (false)
{
}
: QTcpServer (parent)
, m_useSsl (false)
{ }
QtHttpServerWrapper::~QtHttpServerWrapper (void)
{
}
QtHttpServerWrapper::~QtHttpServerWrapper (void) { }
void QtHttpServerWrapper::setUseSecure (const bool ssl) {
m_useSsl = ssl;
m_useSsl = ssl;
}
void QtHttpServerWrapper::incomingConnection (qintptr handle)
{
QTcpSocket * sock = (m_useSsl
? new QSslSocket (this)
: new QTcpSocket (this));
(sock->setSocketDescriptor (handle))
? addPendingConnection (sock)
: delete sock;
void QtHttpServerWrapper::incomingConnection (qintptr handle) {
QTcpSocket * sock = (m_useSsl
? new QSslSocket (this)
: new QTcpSocket (this));
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"))
: 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);
m_sockServer = new QtHttpServerWrapper (this);
connect (m_sockServer, &QtHttpServerWrapper::newConnection, this, &QtHttpServer::onClientConnected);
}
const QString & QtHttpServer::getServerName (void) const
{
return m_serverName;
const QString & QtHttpServer::getServerName (void) const {
return m_serverName;
}
quint16 QtHttpServer::getServerPort (void) const
{
return m_sockServer->serverPort ();
quint16 QtHttpServer::getServerPort (void) const {
return m_sockServer->serverPort ();
}
QString QtHttpServer::getErrorString (void) const
{
return m_sockServer->errorString ();
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 ())
{
m_sockServer->close ();
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 ();
}
emit stopped ();
}
}
void QtHttpServer::setServerName (const QString & serverName)
{
m_serverName = serverName;
void QtHttpServer::setServerName (const QString & serverName) {
m_serverName = serverName;
}
void QtHttpServer::setUseSecure (const bool ssl)
{
m_useSsl = ssl;
m_sockServer->setUseSecure (m_useSsl);
void QtHttpServer::setUseSecure (const bool ssl) {
m_useSsl = ssl;
m_sockServer->setUseSecure (m_useSsl);
}
void QtHttpServer::setPrivateKey (const QSslKey & key)
{
m_sslKey = key;
void QtHttpServer::setPrivateKey (const QSslKey & key) {
m_sslKey = key;
}
void QtHttpServer::setCertificates (const QList<QSslCertificate> & certs)
{
m_sslCerts = 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 ())
{
connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
if (m_useSsl)
void QtHttpServer::onClientConnected (void) {
while (m_sockServer->hasPendingConnections ()) {
if (QTcpSocket * sock = m_sockServer->nextPendingConnection ()) {
if(m_netOrigin->accessAllowed(sock->peerAddress(), sock->localAddress()))
{
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);
connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged);
ssl->setLocalCertificateChain (m_sslCerts);
ssl->setPrivateKey (m_sslKey);
ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer);
ssl->startServerEncryption ();
}
connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
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);
connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged);
ssl->setLocalCertificateChain (m_sslCerts);
ssl->setPrivateKey (m_sslKey);
ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer);
ssl->startServerEncryption ();
}
}
QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, m_netOrigin->isLocalAddress(sock->peerAddress(), sock->localAddress()), this);
m_socksClientsHash.insert (sock, wrapper);
emit clientConnected (wrapper->getGuid ());
}
QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, 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) {
Q_UNUSED (err)
}
void QtHttpServer::onClientSslPeerVerifyError (const QSslError & err)
{
Q_UNUSED (err)
void QtHttpServer::onClientSslErrors (const QList<QSslError> & errors) {
Q_UNUSED (errors)
}
void QtHttpServer::onClientSslErrors (const QList<QSslError> & errors)
{
Q_UNUSED (errors)
void QtHttpServer::onClientSslModeChanged (QSslSocket::SslMode mode) {
Q_UNUSED (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))
{
emit clientDisconnected (wrapper->getGuid ());
wrapper->deleteLater ();
m_socksClientsHash.remove (sockClient);
}
}
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);
}
}
}

View File

@ -17,72 +17,72 @@ class QTcpServer;
class QtHttpRequest;
class QtHttpReply;
class QtHttpClientWrapper;
class NetOrigin;
class QtHttpServerWrapper : public QTcpServer {
Q_OBJECT
Q_OBJECT
public:
explicit QtHttpServerWrapper (QObject * parent = Q_NULLPTR);
virtual ~QtHttpServerWrapper (void);
explicit QtHttpServerWrapper (QObject * parent = Q_NULLPTR);
virtual ~QtHttpServerWrapper (void);
void setUseSecure (const bool ssl = true);
void setUseSecure (const bool ssl = true);
protected:
void incomingConnection (qintptr handle) Q_DECL_OVERRIDE;
void incomingConnection (qintptr handle) Q_DECL_OVERRIDE;
private:
bool m_useSsl;
bool m_useSsl;
};
class QtHttpServer : public QObject {
Q_OBJECT
Q_OBJECT
public:
explicit QtHttpServer (QObject * parent = Q_NULLPTR);
explicit QtHttpServer (QObject * parent = Q_NULLPTR);
static const QString & HTTP_VERSION;
static const QString & HTTP_VERSION;
typedef void (QSslSocket::* SslErrorSignal) (const QList<QSslError> &);
typedef void (QSslSocket::* SslErrorSignal) (const QList<QSslError> &);
const QString & getServerName (void) const;
const QString & getServerName (void) const;
quint16 getServerPort (void) const;
QString getErrorString (void) const;
bool isListening(void) { return m_sockServer->isListening(); };
quint16 getServerPort (void) const;
QString getErrorString (void) const;
const bool isListening() { return m_sockServer->isListening(); };
public slots:
void start (quint16 port = 0);
void stop (void);
void setServerName (const QString & serverName);
void setUseSecure (const bool ssl = true);
void setPrivateKey (const QSslKey & key);
void setCertificates (const QList<QSslCertificate> & certs);
void start (quint16 port = 0);
void stop (void);
void setServerName (const QString & serverName);
void setUseSecure (const bool ssl = true);
void setPrivateKey (const QSslKey & key);
void setCertificates (const QList<QSslCertificate> & certs);
signals:
void started (quint16 port);
void stopped (void);
void error (const QString & msg);
void clientConnected (const QString & guid);
void clientDisconnected (const QString & guid);
void requestNeedsReply (QtHttpRequest * request, QtHttpReply * reply);
void started (quint16 port);
void stopped (void);
void error (const QString & msg);
void clientConnected (const QString & guid);
void clientDisconnected (const QString & guid);
void requestNeedsReply (QtHttpRequest * request, QtHttpReply * reply);
private slots:
void onClientConnected (void);
void onClientDisconnected (void);
void onClientSslEncrypted (void);
void onClientSslPeerVerifyError (const QSslError & err);
void onClientSslErrors (const QList<QSslError> & errors);
void onClientSslModeChanged (QSslSocket::SslMode mode);
void onClientConnected (void);
void onClientDisconnected (void);
void onClientSslEncrypted (void);
void onClientSslPeerVerifyError (const QSslError & err);
void onClientSslErrors (const QList<QSslError> & errors);
void onClientSslModeChanged (QSslSocket::SslMode mode);
private:
bool m_useSsl;
QSslKey m_sslKey;
QList<QSslCertificate> m_sslCerts;
QString m_serverName;
QtHttpServerWrapper * m_sockServer;
QHash<QTcpSocket *, QtHttpClientWrapper *> m_socksClientsHash;
bool m_useSsl;
QSslKey m_sslKey;
QList<QSslCertificate> m_sslCerts;
QString m_serverName;
NetOrigin* m_netOrigin;
QtHttpServerWrapper * m_sockServer;
QHash<QTcpSocket *, QtHttpClientWrapper *> m_socksClientsHash;
};
#endif // QTHTTPSERVER_H

View File

@ -23,7 +23,7 @@ StaticFileServing::StaticFileServing (QObject * parent)
StaticFileServing::~StaticFileServing ()
{
delete _mimeDb;
}
void StaticFileServing::setBaseUrl(const QString& url)

View File

@ -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)

View File

@ -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);

View File

@ -13,11 +13,12 @@
WebServer::WebServer(const QJsonDocument& config, QObject * parent)
: QObject(parent)
: QObject(parent)
, _config(config)
, _log(Logger::getInstance("WEBSERVER"))
, _server()
{
}
WebServer::~WebServer()

View File

@ -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)
{

View File

@ -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);

View File

@ -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

View File

@ -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:
///

View File

@ -59,47 +59,51 @@ 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");
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");
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");
Option & argEffect = parser.add<Option> ('e', "effect" , "Enable the effect with the given name");
Option & argEffectFile = parser.add<Option> (0x0, "effectFile", "Arguments to use in combination with --createEffect");
Option & argEffectArgs = parser.add<Option> (0x0, "effectArgs", "Arguments to use in combination with the specified effect. Should be a Json object string.", "");
Option & argCreateEffect= parser.add<Option> (0x0, "createEffect","Write a new Json Effect configuration file.\nFirst parameter = Effect name.\nSecond parameter = Effect file (--effectFile).\nLast parameter = Effect arguments (--effectArgs.)", "");
Option & argDeleteEffect= parser.add<Option> (0x0, "deleteEffect","Delete a custom created Json Effect configuration file.");
BooleanOption & argServerInfo = parser.add<BooleanOption>('l', "list" , "List server info and active effects with priority and duration");
BooleanOption & argSysInfo = parser.add<BooleanOption>('s', "sysinfo" , "show system info");
BooleanOption & argClear = parser.add<BooleanOption>('x', "clear" , "Clear data for the priority channel provided by the -p option");
BooleanOption & argClearAll = parser.add<BooleanOption>(0x0, "clearall" , "Clear data for all active priority channels");
Option & argEnableComponent = parser.add<Option> ('E', "enable" , "Enable the Component with the given name. Available Components are [SMOOTHING, BLACKBORDER, FORWARDER, UDPLISTENER, BOBLIGHT_SERVER, GRABBER, V4L, LEDDEVICE]");
Option & argDisableComponent = parser.add<Option> ('D', "disable" , "Disable the Component with the given name. Available Components are [SMOOTHING, BLACKBORDER, FORWARDER, UDPLISTENER, BOBLIGHT_SERVER, GRABBER, V4L, LEDDEVICE]");
Option & argId = parser.add<Option> ('q', "qualifier" , "Identifier(qualifier) of the adjustment to set");
IntOption & argBrightness = parser.add<IntOption> ('L', "brightness" , "Set the brightness gain of the leds");
IntOption & argBrightnessC = parser.add<IntOption> (0x0, "brightnessCompensation" , "Set the brightness compensation");
IntOption & argBacklightThreshold= parser.add<IntOption> ('n', "backlightThreshold" , "threshold for activating backlight (minimum brightness)");
IntOption & argBacklightColored = parser.add<IntOption> (0x0, "backlightColored" , "0 = white backlight; 1 = colored backlight");
DoubleOption & argGamma = parser.add<DoubleOption> ('g', "gamma" , "Set the overall gamma of the leds");
BooleanOption & argPrint = parser.add<BooleanOption>(0x0, "print" , "Print the json input and output messages on stdout");
BooleanOption & argHelp = parser.add<BooleanOption>('h', "help" , "Show this help message and exit");
ColorOption & argRAdjust = parser.add<ColorOption> ('R', "redAdjustment" , "Set the adjustment of the red color (requires colors in hex format as RRGGBB)");
ColorOption & argGAdjust = parser.add<ColorOption> ('G', "greenAdjustment", "Set the adjustment of the green color (requires colors in hex format as RRGGBB)");
ColorOption & argBAdjust = parser.add<ColorOption> ('B', "blueAdjustment", "Set the adjustment of the blue color (requires colors in hex format as RRGGBB)");
ColorOption & argCAdjust = parser.add<ColorOption> ('C', "cyanAdjustment" , "Set the adjustment of the cyan color (requires colors in hex format as RRGGBB)");
ColorOption & argMAdjust = parser.add<ColorOption> ('M', "magentaAdjustment", "Set the adjustment of the magenta color (requires colors in hex format as RRGGBB)");
ColorOption & argYAdjust = parser.add<ColorOption> ('Y', "yellowAdjustment", "Set the adjustment of the yellow color (requires colors in hex format as RRGGBB)");
ColorOption & argWAdjust = parser.add<ColorOption> ('W', "whiteAdjustment", "Set the adjustment of the white color (requires colors in hex format as RRGGBB)");
ColorOption & argbAdjust = parser.add<ColorOption> ('b', "blackAdjustment", "Set the adjustment of the black color (requires colors in hex format as RRGGBB)");
Option & argMapping = parser.add<Option> ('m', "ledMapping" , "Set the methode for image to led mapping valid values: multicolor_mean, unicolor_mean");
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 & 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.");
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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");
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");
Option & argEffect = parser.add<Option> ('e', "effect" , "Enable the effect with the given name");
Option & argEffectFile = parser.add<Option> (0x0, "effectFile" , "Arguments to use in combination with --createEffect");
Option & argEffectArgs = parser.add<Option> (0x0, "effectArgs" , "Arguments to use in combination with the specified effect. Should be a Json object string.", "");
Option & argCreateEffect = parser.add<Option> (0x0, "createEffect" , "Write a new Json Effect configuration file.\nFirst parameter = Effect name.\nSecond parameter = Effect file (--effectFile).\nLast parameter = Effect arguments (--effectArgs.)", "");
Option & argDeleteEffect = parser.add<Option> (0x0, "deleteEffect" , "Delete a custom created Json Effect configuration file.");
BooleanOption & argServerInfo = parser.add<BooleanOption>('l', "list" , "List server info and active effects with priority and duration");
BooleanOption & argSysInfo = parser.add<BooleanOption>('s', "sysinfo" , "show system info");
BooleanOption & argClear = parser.add<BooleanOption>('x', "clear" , "Clear data for the priority channel provided by the -p option");
BooleanOption & argClearAll = parser.add<BooleanOption>(0x0, "clearall" , "Clear data for all active priority channels");
Option & argEnableComponent = parser.add<Option> ('E', "enable" , "Enable the Component with the given name. Available Components are [SMOOTHING, BLACKBORDER, FORWARDER, UDPLISTENER, BOBLIGHT_SERVER, GRABBER, V4L, LEDDEVICE]");
Option & argDisableComponent = parser.add<Option> ('D', "disable" , "Disable the Component with the given name. Available Components are [SMOOTHING, BLACKBORDER, FORWARDER, UDPLISTENER, BOBLIGHT_SERVER, GRABBER, V4L, LEDDEVICE]");
Option & argId = parser.add<Option> ('q', "qualifier" , "Identifier(qualifier) of the adjustment to set");
IntOption & argBrightness = parser.add<IntOption> ('L', "brightness" , "Set the brightness gain of the leds");
IntOption & argBrightnessC = parser.add<IntOption> (0x0, "brightnessCompensation" , "Set the brightness compensation");
IntOption & argBacklightThreshold = parser.add<IntOption> ('n', "backlightThreshold" , "threshold for activating backlight (minimum brightness)");
IntOption & argBacklightColored = parser.add<IntOption> (0x0, "backlightColored" , "0 = white backlight; 1 = colored backlight");
DoubleOption & argGamma = parser.add<DoubleOption> ('g', "gamma" , "Set the overall gamma of the leds");
BooleanOption & argPrint = parser.add<BooleanOption>(0x0, "print" , "Print the json input and output messages on stdout");
BooleanOption & argHelp = parser.add<BooleanOption>('h', "help" , "Show this help message and exit");
ColorOption & argRAdjust = parser.add<ColorOption> ('R', "redAdjustment" , "Set the adjustment of the red color (requires colors in hex format as RRGGBB)");
ColorOption & argGAdjust = parser.add<ColorOption> ('G', "greenAdjustment" , "Set the adjustment of the green color (requires colors in hex format as RRGGBB)");
ColorOption & argBAdjust = parser.add<ColorOption> ('B', "blueAdjustment" , "Set the adjustment of the blue color (requires colors in hex format as RRGGBB)");
ColorOption & argCAdjust = parser.add<ColorOption> ('C', "cyanAdjustment" , "Set the adjustment of the cyan color (requires colors in hex format as RRGGBB)");
ColorOption & argMAdjust = parser.add<ColorOption> ('M', "magentaAdjustment" , "Set the adjustment of the magenta color (requires colors in hex format as RRGGBB)");
ColorOption & argYAdjust = parser.add<ColorOption> ('Y', "yellowAdjustment" , "Set the adjustment of the yellow color (requires colors in hex format as RRGGBB)");
ColorOption & argWAdjust = parser.add<ColorOption> ('W', "whiteAdjustment" , "Set the adjustment of the white color (requires colors in hex format as RRGGBB)");
ColorOption & argbAdjust = parser.add<ColorOption> ('b', "blackAdjustment" , "Set the adjustment of the black color (requires colors in hex format as RRGGBB)");
Option & argMapping = parser.add<Option> ('m', "ledMapping" , "Set the methode for image to led mapping valid values: multicolor_mean, unicolor_mean");
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 & 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.");
// parse all _options
parser.process(app);
@ -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))
{

View File

@ -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}" )

View File

@ -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);

View File

@ -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;
@ -158,8 +162,8 @@ private:
int _grabber_ge2d_mode;
QString _grabber_device;
QString _prevType;
QString _prevType;
VideoMode _currVideoMode;
SettingsManager* _settingsManager;
VideoMode _currVideoMode;
SettingsManager* _settingsManager;
};