- 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
72 changed files with 2546 additions and 485 deletions

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
}