From 38362440f1ab4d9949b21f3103f0bb36c5455a9e Mon Sep 17 00:00:00 2001 From: Bernd Wachter Date: Fri, 29 Mar 2013 14:14:59 +0200 Subject: [PATCH] Use only one instance of global settings, removing sync issues and unnecessary reads Some bits got moved to more appropriate modules, others (like credentials handling) still need to be moved. ssu CLI got a bit more readable. This is part of a big refactoring due to SSU doing mostly repo management and URL handling instead of the originally planned credentials management with URL handling as a nice bonus feature. --- constants.h | 4 - libssu/libssu.pro | 2 + libssu/ssu.cpp | 276 +++++++++----------------------------- libssu/ssu.h | 94 +++++-------- libssu/ssucoreconfig.cpp | 131 ++++++++++++++++++ libssu/ssucoreconfig.h | 112 ++++++++++++++++ libssu/ssudeviceinfo.cpp | 17 +-- libssu/ssurepomanager.cpp | 154 ++++++++++++++++++++- libssu/ssurepomanager.h | 7 + rndssucli/rndssucli.cpp | 204 ++++++++++++++++++++++++---- rndssucli/rndssucli.h | 26 +++- 11 files changed, 708 insertions(+), 319 deletions(-) create mode 100644 libssu/ssucoreconfig.cpp create mode 100644 libssu/ssucoreconfig.h diff --git a/constants.h b/constants.h index 0392308..0826cc8 100644 --- a/constants.h +++ b/constants.h @@ -11,11 +11,7 @@ /// The group ID SSU expects to run as. This is usually the GID of the main phone user #define SSU_GROUP_ID 1000 /// Path to the main SSU configuration file -#define SSU_CONFIGURATION "/etc/ssu/ssu.ini" -/// Path to the main SSU configuration file #define SSU_REPO_CONFIGURATION "/usr/share/ssu/repos.ini" -/// Path to the main SSU configuration file -#define SSU_DEFAULT_CONFIGURATION "/usr/share/ssu/ssu-defaults.ini" /// Path to board / device family mappings file #define SSU_BOARD_MAPPING_CONFIGURATION "/usr/share/ssu/board-mappings.ini" /// Path to config.d for board mappings diff --git a/libssu/libssu.pro b/libssu/libssu.pro index 6599f00..00bf26e 100644 --- a/libssu/libssu.pro +++ b/libssu/libssu.pro @@ -1,5 +1,6 @@ BUILD = ../build/libssu HEADERS = ssu.h \ + ssucoreconfig.h \ ssudeviceinfo.h \ ssulog.h \ ssuvariables.h \ @@ -7,6 +8,7 @@ HEADERS = ssu.h \ ssurepomanager.h \ ../constants.h SOURCES = ssu.cpp \ + ssucoreconfig.cpp \ ssudeviceinfo.cpp \ ssulog.cpp \ ssuvariables.cpp \ diff --git a/libssu/ssu.cpp b/libssu/ssu.cpp index 8192011..74f66d5 100644 --- a/libssu/ssu.cpp +++ b/libssu/ssu.cpp @@ -10,12 +10,13 @@ #include "ssu.h" #include "ssulog.h" #include "ssuvariables.h" +#include "ssucoreconfig.h" +#include "ssurepomanager.h" #include "../constants.h" -Ssu::Ssu(QString fallbackLog): QObject(){ +Ssu::Ssu(): QObject(){ errorFlag = false; - fallbackLogPath = fallbackLog; pendingRequests = 0; #ifdef SSUCONFHACK @@ -31,8 +32,7 @@ Ssu::Ssu(QString fallbackLog): QObject(){ } #endif - settings = new SsuSettings(SSU_CONFIGURATION, QSettings::IniFormat, SSU_DEFAULT_CONFIGURATION); - repoSettings = new QSettings(SSU_REPO_CONFIGURATION, QSettings::IniFormat); + SsuCoreConfig *settings = SsuCoreConfig::instance(); #ifdef TARGET_ARCH if (!settings->contains("arch")) @@ -43,72 +43,94 @@ Ssu::Ssu(QString fallbackLog): QObject(){ #endif settings->sync(); + + manager = new QNetworkAccessManager(this); connect(manager, SIGNAL(finished(QNetworkReply *)), SLOT(requestFinished(QNetworkReply *))); } +// FIXME, the whole credentials stuff needs reworking +// should probably be part of repo handling instead of core configuration QPair Ssu::credentials(QString scope){ - QPair ret; - settings->beginGroup("credentials-" + scope); - ret.first = settings->value("username").toString(); - ret.second = settings->value("password").toString(); - settings->endGroup(); - return ret; + SsuCoreConfig *settings = SsuCoreConfig::instance(); + return settings->credentials(scope); } QString Ssu::credentialsScope(QString repoName, bool rndRepo){ - if (settings->contains("credentials-scope")) - return settings->value("credentials-scope").toString(); - else - return "your-configuration-is-broken-and-does-not-contain-credentials-scope"; + SsuCoreConfig *settings = SsuCoreConfig::instance(); + return settings->credentialsScope(repoName, rndRepo); } QString Ssu::credentialsUrl(QString scope){ - if (settings->contains("credentials-url-" + scope)) - return settings->value("credentials-url-" + scope).toString(); - else - return "your-configuration-is-broken-and-does-not-contain-credentials-url-for-" + scope; + SsuCoreConfig *settings = SsuCoreConfig::instance(); + return settings->credentialsUrl(scope); } bool Ssu::error(){ return errorFlag; } +// Wrappers around SsuCoreConfig QString Ssu::flavour(){ - if (settings->contains("flavour")) - return settings->value("flavour").toString(); - else - return "release"; + SsuCoreConfig *settings = SsuCoreConfig::instance(); + return settings->flavour(); } int Ssu::deviceMode(){ - if (!settings->contains("deviceMode")){ - settings->setValue("deviceMode", ReleaseMode); - return ReleaseMode; - } else - return settings->value("deviceMode").toInt(); + SsuCoreConfig *settings = SsuCoreConfig::instance(); + return settings->deviceMode(); } QString Ssu::domain(){ - if (settings->contains("domain")) - return settings->value("domain").toString(); - else - return ""; + SsuCoreConfig *settings = SsuCoreConfig::instance(); + return settings->domain(); } bool Ssu::isRegistered(){ - if (!settings->contains("privateKey")) - return false; - if (!settings->contains("certificate")) - return false; - return settings->value("registered").toBool(); + SsuCoreConfig *settings = SsuCoreConfig::instance(); + return settings->isRegistered(); } QDateTime Ssu::lastCredentialsUpdate(){ - return settings->value("lastCredentialsUpdate").toDateTime(); + SsuCoreConfig *settings = SsuCoreConfig::instance(); + return settings->lastCredentialsUpdate(); +} + +QString Ssu::release(bool rnd){ + SsuCoreConfig *settings = SsuCoreConfig::instance(); + return settings->release(rnd); +} + +void Ssu::setDeviceMode(int mode, int editMode){ + SsuCoreConfig *settings = SsuCoreConfig::instance(); + settings->setDeviceMode(mode, editMode); +} + +void Ssu::setFlavour(QString flavour){ + SsuCoreConfig *settings = SsuCoreConfig::instance(); + settings->setFlavour(flavour); +} + +void Ssu::setRelease(QString release, bool rnd){ + SsuCoreConfig *settings = SsuCoreConfig::instance(); + settings->setRelease(release, rnd); +} + +void Ssu::setDomain(QString domain){ + SsuCoreConfig *settings = SsuCoreConfig::instance(); + setDomain(domain); +} + +bool Ssu::useSslVerify(){ + SsuCoreConfig *settings = SsuCoreConfig::instance(); + return settings->useSslVerify(); } +//... + + + QString Ssu::lastError(){ return errorString; } @@ -117,6 +139,7 @@ bool Ssu::registerDevice(QDomDocument *response){ QString certificateString = response->elementsByTagName("certificate").at(0).toElement().text(); QSslCertificate certificate(certificateString.toAscii()); SsuLog *ssuLog = SsuLog::instance(); + SsuCoreConfig *settings = SsuCoreConfig::instance(); if (certificate.isNull()){ // make sure device is in unregistered state on failed registration @@ -148,143 +171,19 @@ bool Ssu::registerDevice(QDomDocument *response){ return true; } -QString Ssu::release(bool rnd){ - if (rnd) - return settings->value("rndRelease").toString(); - else - return settings->value("release").toString(); -} - // RND repos have flavour (devel, testing, release), and release (latest, next) // Release repos only have release (latest, next, version number) QString Ssu::repoUrl(QString repoName, bool rndRepo, QHash repoParameters, QHash parametersOverride){ - QString r; - QStringList configSections; - SsuVariables var; - SsuLog *ssuLog = SsuLog::instance(); - - errorFlag = false; - - // fill in all arbitrary variables from ssu.ini - var.resolveSection(settings, "repository-url-variables", &repoParameters); - - // add/overwrite some of the variables with sane ones - if (rndRepo){ - repoParameters.insert("flavour", repoSettings->value(flavour()+"-flavour/flavour-pattern").toString()); - repoParameters.insert("flavourPattern", repoSettings->value(flavour()+"-flavour/flavour-pattern").toString()); - repoParameters.insert("flavourName", flavour()); - configSections << flavour()+"-flavour" << "rnd" << "all"; - - // Make it possible to give any values with the flavour as well. - // These values can be overridden later with domain if needed. - var.resolveSection(repoSettings, flavour()+"-flavour", &repoParameters); - } else { - configSections << "release" << "all"; - } - - repoParameters.insert("release", release(rndRepo)); - - if (!repoParameters.contains("debugSplit")) - repoParameters.insert("debugSplit", "packages"); - - if (!repoParameters.contains("arch")) - repoParameters.insert("arch", settings->value("arch").toString()); - - // Override device model (and therefore all the family, ... stuff) - if (parametersOverride.contains("model")) - deviceInfo.setDeviceModel(parametersOverride.value("model")); - - QStringList adaptationRepos = deviceInfo.adaptationRepos(); - - // read adaptation from settings, in case it can't be determined from - // board mappings. this is obsolete, and will be dropped soon - if (settings->contains("adaptation")) - repoParameters.insert("adaptation", settings->value("adaptation").toString()); - - repoParameters.insert("deviceFamily", deviceInfo.deviceFamily()); - repoParameters.insert("deviceModel", deviceInfo.deviceModel()); - - // Those keys have now been obsoleted by generic variables, support for - // it will be removed soon - QStringList keys; - keys << "chip" << "adaptation" << "vendor"; - foreach(QString key, keys){ - QString value; - if (deviceInfo.getValue(key,value)) - repoParameters.insert(key, value); - } - - // special handling for adaptation-repositories - // - check if repo is in right format (adaptation\d*) - // - check if the configuration has that many adaptation repos - // - export the entry in the adaptation list as %(adaptation) - // - look up variables for that adaptation, and export matching - // adaptation variable - QRegExp regex("adaptation\\\d*", Qt::CaseSensitive, QRegExp::RegExp2); - if (regex.exactMatch(repoName)){ - regex.setPattern("\\\d*"); - regex.lastIndexIn(repoName); - int n = regex.cap().toInt(); - - if (!adaptationRepos.isEmpty()){ - if (adaptationRepos.size() <= n) { - ssuLog->print(LOG_INFO, "Note: repo index out of bounds, substituting 0" + repoName); - n = 0; - } - - QString adaptationRepo = adaptationRepos.at(n); - repoParameters.insert("adaptation", adaptationRepo); - ssuLog->print(LOG_DEBUG, "Found first adaptation " + repoName); - - QHash h = deviceInfo.variableSection(adaptationRepo); - - QHash::const_iterator i = h.constBegin(); - while (i != h.constEnd()){ - repoParameters.insert(i.key(), i.value()); - i++; - } - } else - ssuLog->print(LOG_INFO, "Note: adaptation repo for invalid repo requested " + repoName); - - repoName = "adaptation"; - } - - // Domain variables - // first read all variables from default-domain - var.resolveSection(repoSettings, "default-domain", &repoParameters); - - // then overwrite with domain specific things if that block is available - var.resolveSection(repoSettings, domain()+"-domain", &repoParameters); - - // override arbitrary variables, mostly useful for generating mic URLs - QHash::const_iterator i = parametersOverride.constBegin(); - while (i != parametersOverride.constEnd()){ - repoParameters.insert(i.key(), i.value()); - i++; - } - - if (settings->contains("repository-urls/" + repoName)) - r = settings->value("repository-urls/" + repoName).toString(); - else { - foreach (const QString §ion, configSections){ - repoSettings->beginGroup(section); - if (repoSettings->contains(repoName)){ - r = repoSettings->value(repoName).toString(); - repoSettings->endGroup(); - break; - } - repoSettings->endGroup(); - } - } - - return var.resolveString(r, &repoParameters); + SsuRepoManager manager; + return manager.url(repoName, rndRepo, repoParameters, parametersOverride); } void Ssu::requestFinished(QNetworkReply *reply){ QSslConfiguration sslConfiguration = reply->sslConfiguration(); SsuLog *ssuLog = SsuLog::instance(); + SsuCoreConfig *settings = SsuCoreConfig::instance(); ssuLog->print(LOG_DEBUG, QString("Certificate used was issued for '%1' by '%2'. Complete chain:") .arg(sslConfiguration.peerCertificate().subjectInfo(QSslCertificate::CommonName)) @@ -356,6 +255,7 @@ void Ssu::sendRegistration(QString usernameDomain, QString password){ QString username, domainName; SsuLog *ssuLog = SsuLog::instance(); + SsuCoreConfig *settings = SsuCoreConfig::instance(); // Username can include also domain, (user@domain), separate those if (usernameDomain.contains('@')) { @@ -434,6 +334,7 @@ void Ssu::sendRegistration(QString usernameDomain, QString password){ } bool Ssu::setCredentials(QDomDocument *response){ + SsuCoreConfig *settings = SsuCoreConfig::instance(); // generate list with all scopes for generic section, add sections QDomNodeList credentialsList = response->elementsByTagName("credentials"); QStringList credentialScopes; @@ -476,20 +377,6 @@ bool Ssu::setCredentials(QDomDocument *response){ return true; } -void Ssu::setDeviceMode(int mode, int editMode){ - int oldMode = settings->value("deviceMode").toInt(); - - if ((editMode & Add) == Add){ - oldMode |= mode; - } else if ((editMode & Remove) == Remove){ - oldMode &= ~mode; - } else - oldMode = mode; - - settings->setValue("deviceMode", oldMode); - settings->sync(); -} - void Ssu::setError(QString errorMessage){ errorFlag = true; errorString = errorMessage; @@ -504,31 +391,6 @@ void Ssu::setError(QString errorMessage){ emit done(); } -void Ssu::setFlavour(QString flavour){ - settings->setValue("flavour", flavour); - // flavour is RnD only, so enable RnD mode - setDeviceMode(RndMode, Add); - settings->sync(); - emit flavourChanged(); -} - -void Ssu::setRelease(QString release, bool rnd){ - if (rnd) { - settings->setValue("rndRelease", release); - // switch rndMode on/off when setting releases - setDeviceMode(RndMode, Add); - } else { - settings->setValue("release", release); - setDeviceMode(RndMode, Remove); - } - settings->sync(); -} - -void Ssu::setDomain(QString domain){ - settings->setValue("domain", domain); - settings->sync(); -} - void Ssu::storeAuthorizedKeys(QByteArray data){ QDir dir; @@ -554,6 +416,7 @@ void Ssu::storeAuthorizedKeys(QByteArray data){ } void Ssu::updateCredentials(bool force){ + SsuCoreConfig *settings = SsuCoreConfig::instance(); errorFlag = false; SsuLog *ssuLog = SsuLog::instance(); @@ -625,14 +488,9 @@ void Ssu::updateCredentials(bool force){ manager->get(request); } -bool Ssu::useSslVerify(){ - if (settings->contains("ssl-verify")) - return settings->value("ssl-verify").toBool(); - else - return true; -} void Ssu::unregister(){ + SsuCoreConfig *settings = SsuCoreConfig::instance(); settings->setValue("privateKey", ""); settings->setValue("certificate", ""); settings->setValue("registered", false); diff --git a/libssu/ssu.h b/libssu/ssu.h index e3133a3..040d661 100644 --- a/libssu/ssu.h +++ b/libssu/ssu.h @@ -23,7 +23,7 @@ class Ssu: public QObject { Q_OBJECT public: - Ssu(QString fallbackLog="/tmp/ssu.log"); + Ssu(); /** * Find a username/password pair for the given scope * @return a QPair with username and password, or an empty QPair if scope is invalid @@ -49,41 +49,12 @@ class Ssu: public QObject { * @retval false last operation failed, you should check lastError() for details */ Q_INVOKABLE bool error(); - /** - * Get the current flavour when RnD repositories are used - * @return current flavour (usually something like testing, release, ..) - */ - Q_INVOKABLE QString flavour(); - /** - * Get the current mode bits for the device - */ - Q_INVOKABLE int deviceMode(); - /** - * Get the current domain used in registration - * @return domain, or "" if not set - */ - Q_INVOKABLE QString domain(); - /** - * Return devices RND registration status - * @retval true device is registered - * @retval false device is not registered - */ - Q_INVOKABLE bool isRegistered(); - /** - * Return the date/time when the credentials to access internal - * SSU servers were updated the last time - */ - Q_INVOKABLE QDateTime lastCredentialsUpdate(); /** * Return an error message for the last error encountered. The message * will not be cleared, check error() to see if the last operation was * successful. */ Q_INVOKABLE QString lastError(); - /** - * Return the release version string for either a release, or a RnD snapshot - */ - Q_INVOKABLE QString release(bool rnd=false); /** * Resolve a repository url * @return the repository URL on success, an empty string on error @@ -91,22 +62,6 @@ class Ssu: public QObject { QString repoUrl(QString repoName, bool rndRepo=false, QHash repoParameters=QHash(), QHash parametersOverride=QHash()); - /** - * Set mode bits for the device - */ - Q_INVOKABLE void setDeviceMode(int mode, int editMode=Replace); - /** - * Set the flavour used when resolving RND repositories - */ - Q_INVOKABLE void setFlavour(QString flavour); - /** - * Set the release version string for either a release, or a RnD snapshot - */ - Q_INVOKABLE void setRelease(QString release, bool rnd=false); - /** - * Set the domain string (usually something like nemo, jolla, ..) - */ - Q_INVOKABLE void setDomain(QString domain); /** * Unregister a device. This will clean all registration data from a device, * though will not touch the information on SSU server; the information there @@ -114,13 +69,35 @@ class Ssu: public QObject { * overwritten next time the device gets registered */ Q_INVOKABLE void unregister(); - /** - * Return configuration settings regarding ssl verification - * @retval true SSL verification must be used; that's the default if not configured - * @retval false SSL verification should be disabled - */ + + // wrappers around SsuCoreConfig + // not all of those belong into SsuCoreConfig, but will go there + // in the first phase of refactoring + + /// See SsuCoreConfig::flavour + Q_INVOKABLE QString flavour(); + /// See SsuCoreConfig::deviceMode + Q_INVOKABLE int deviceMode(); + /// See SsuCoreConfig::domain + Q_INVOKABLE QString domain(); + /// See SsuCoreConfig::isRegistered + Q_INVOKABLE bool isRegistered(); + /// See SsuCoreConfig::lastCredentialsUpdate + Q_INVOKABLE QDateTime lastCredentialsUpdate(); + /// See SsuCoreConfig::release + Q_INVOKABLE QString release(bool rnd=false); + /// See SsuCoreConfig::setDeviceMode + Q_INVOKABLE void setDeviceMode(int mode, int editMode=Replace); + /// See SsuCoreConfig::setFlavour + Q_INVOKABLE void setFlavour(QString flavour); + /// See SsuCoreConfig::setRelease + Q_INVOKABLE void setRelease(QString release, bool rnd=false); + /// See SsuCoreConfig::setDomain + Q_INVOKABLE void setDomain(QString domain); + /// See SsuCoreConfig::useSslVerify Q_INVOKABLE bool useSslVerify(); + /** * List of possible device modes * @@ -144,18 +121,20 @@ class Ssu: public QObject { Remove = 0x4 ///< Make sure the given value is not set in the bitmask }; + // compat stuff, might go away when refactoring is finished + /// See SsuDeviceInfo::deviceFamily Q_INVOKABLE QString deviceFamily(){ return deviceInfo.deviceFamily(); }; + /// See SsuDeviceInfo::deviceModel Q_INVOKABLE QString deviceModel(){ return deviceInfo.deviceModel(); }; + /// See SsuDeviceInfo::deviceUid Q_INVOKABLE QString deviceUid(){ return deviceInfo.deviceUid(); }; private: - QString errorString, fallbackLogPath; + QString errorString; bool errorFlag; QNetworkAccessManager *manager; int pendingRequests; - QSettings *repoSettings; - SsuSettings *settings; SsuDeviceInfo deviceInfo; bool registerDevice(QDomDocument *response); bool setCredentials(QDomDocument *response); @@ -199,12 +178,11 @@ class Ssu: public QObject { * Emitted after an asynchronous operation finished */ void done(); - // we don't get notifications from settings -> this won't work over different instances (yet) - void flavourChanged(); - void releaseChanged(); + /** + * Emitted after the devices registration status has changed + */ void registrationStatusChanged(); void credentialsChanged(); - }; #endif diff --git a/libssu/ssucoreconfig.cpp b/libssu/ssucoreconfig.cpp new file mode 100644 index 0000000..c9203f7 --- /dev/null +++ b/libssu/ssucoreconfig.cpp @@ -0,0 +1,131 @@ +/** + * @file ssucoreconfig.cpp + * @copyright 2013 Jolla Ltd. + * @author Bernd Wachter + * @date 2013 + */ + +#include +#include + +#include "ssucoreconfig.h" +#include "ssulog.h" + +SsuCoreConfig *SsuCoreConfig::ssuCoreConfig = 0; + +SsuCoreConfig *SsuCoreConfig::instance(){ + if (!ssuCoreConfig) + ssuCoreConfig = new SsuCoreConfig(); + + return ssuCoreConfig; +} + +QPair SsuCoreConfig::credentials(QString scope){ + QPair ret; + beginGroup("credentials-" + scope); + ret.first = value("username").toString(); + ret.second = value("password").toString(); + endGroup(); + return ret; +} + +QString SsuCoreConfig::credentialsScope(QString repoName, bool rndRepo){ + if (contains("credentials-scope")) + return value("credentials-scope").toString(); + else + return "your-configuration-is-broken-and-does-not-contain-credentials-scope"; +} + +QString SsuCoreConfig::credentialsUrl(QString scope){ + if (contains("credentials-url-" + scope)) + return value("credentials-url-" + scope).toString(); + else + return "your-configuration-is-broken-and-does-not-contain-credentials-url-for-" + scope; +} + +QString SsuCoreConfig::flavour(){ + if (contains("flavour")) + return value("flavour").toString(); + else + return "release"; +} + +int SsuCoreConfig::deviceMode(){ + if (!contains("deviceMode")){ + setValue("deviceMode", Ssu::ReleaseMode); + sync(); + return Ssu::ReleaseMode; + } else + return value("deviceMode").toInt(); +} + +QString SsuCoreConfig::domain(){ + if (contains("domain")) + return value("domain").toString(); + else + return ""; +} + +bool SsuCoreConfig::isRegistered(){ + if (!contains("privateKey")) + return false; + if (!contains("certificate")) + return false; + return value("registered").toBool(); +} + +QDateTime SsuCoreConfig::lastCredentialsUpdate(){ + return value("lastCredentialsUpdate").toDateTime(); +} + +QString SsuCoreConfig::release(bool rnd){ + if (rnd) + return value("rndRelease").toString(); + else + return value("release").toString(); +} + +void SsuCoreConfig::setDeviceMode(int mode, int editMode){ + int oldMode = value("deviceMode").toInt(); + + if ((editMode & Ssu::Add) == Ssu::Add){ + oldMode |= mode; + } else if ((editMode & Ssu::Remove) == Ssu::Remove){ + oldMode &= ~mode; + } else + oldMode = mode; + + setValue("deviceMode", oldMode); + sync(); +} + +void SsuCoreConfig::setFlavour(QString flavour){ + setValue("flavour", flavour); + // flavour is RnD only, so enable RnD mode + setDeviceMode(Ssu::RndMode, Ssu::Add); + sync(); +} + +void SsuCoreConfig::setRelease(QString release, bool rnd){ + if (rnd) { + setValue("rndRelease", release); + // switch rndMode on/off when setting releases + setDeviceMode(Ssu::RndMode, Ssu::Add); + } else { + setValue("release", release); + setDeviceMode(Ssu::RndMode, Ssu::Remove); + } + sync(); +} + +void SsuCoreConfig::setDomain(QString domain){ + setValue("domain", domain); + sync(); +} + +bool SsuCoreConfig::useSslVerify(){ + if (contains("ssl-verify")) + return value("ssl-verify").toBool(); + else + return true; +} diff --git a/libssu/ssucoreconfig.h b/libssu/ssucoreconfig.h new file mode 100644 index 0000000..f773c28 --- /dev/null +++ b/libssu/ssucoreconfig.h @@ -0,0 +1,112 @@ +/** + * @file ssucoreconfig.h + * @copyright 2013 Jolla Ltd. + * @author Bernd Wachter + * @date 2013 + */ + +#ifndef _SSUCORECONFIG_H +#define _SSUCORECONFIG_H + +#include +#include + +#include +#include + +#ifndef SSU_CONFIGURATION +/// Path to the main SSU configuration file +#define SSU_CONFIGURATION "/etc/ssu/ssu.ini" +#endif + +#ifndef SSU_DEFAULT_CONFIGURATION +/// Path to the main SSU configuration file +#define SSU_DEFAULT_CONFIGURATION "/usr/share/ssu/ssu-defaults.ini" +#endif + +class SsuCoreConfig: public SsuSettings { + Q_OBJECT + + public: + static SsuCoreConfig *instance(); + /** + * Find a username/password pair for the given scope + * @return a QPair with username and password, or an empty QPair if scope is invalid + */ + QPair credentials(QString scope); + /** + * Get the scope for a repository, taking into account different scopes for + * release and RnD repositories + * + * Please note that variable scope is not yet implemented -- one default scope is + * read from the configuration file. + * + * @return a string containing the scope; it can be used to look up login credentials using credentials() + */ + QString credentialsScope(QString repoName, bool rndRepo=false); + /** + * Return the URL for which credentials scope is valid + */ + QString credentialsUrl(QString scope); + /** + * Get the current flavour when RnD repositories are used + * @return current flavour (usually something like testing, release, ..) + */ + Q_INVOKABLE QString flavour(); + /** + * Get the current mode bits for the device + */ + Q_INVOKABLE int deviceMode(); + /** + * Get the current domain used in registration + * @return domain, or "" if not set + */ + Q_INVOKABLE QString domain(); + /** + * Return devices RND registration status + * @retval true device is registered + * @retval false device is not registered + */ + Q_INVOKABLE bool isRegistered(); + /** + * Return the date/time when the credentials to access internal + * SSU servers were updated the last time + */ + Q_INVOKABLE QDateTime lastCredentialsUpdate(); + /** + * Return the release version string for either a release, or a RnD snapshot + */ + Q_INVOKABLE QString release(bool rnd=false); + /** + * Set mode bits for the device + */ + Q_INVOKABLE void setDeviceMode(int mode, int editMode=Ssu::Replace); + /** + * Set the flavour used when resolving RND repositories + */ + Q_INVOKABLE void setFlavour(QString flavour); + /** + * Set the release version string for either a release, or a RnD snapshot + */ + Q_INVOKABLE void setRelease(QString release, bool rnd=false); + /** + * Set the domain string (usually something like nemo, jolla, ..) + */ + Q_INVOKABLE void setDomain(QString domain); + /** + * Return configuration settings regarding ssl verification + * @retval true SSL verification must be used; that's the default if not configured + * @retval false SSL verification should be disabled + */ + Q_INVOKABLE bool useSslVerify(); + + + private: + SsuCoreConfig(): SsuSettings(SSU_CONFIGURATION, QSettings::IniFormat, SSU_DEFAULT_CONFIGURATION) {}; + SsuCoreConfig(const SsuCoreConfig &); // hide copy constructor + + static SsuCoreConfig *ssuCoreConfig; +}; + + +#endif diff --git a/libssu/ssudeviceinfo.cpp b/libssu/ssudeviceinfo.cpp index 60066a9..29cabdc 100644 --- a/libssu/ssudeviceinfo.cpp +++ b/libssu/ssudeviceinfo.cpp @@ -10,6 +10,7 @@ #include #include "ssudeviceinfo.h" +#include "ssucoreconfig.h" #include "../constants.h" @@ -124,10 +125,10 @@ QString SsuDeviceInfo::deviceModel(){ boardMappings->beginGroup("arch.equals"); keys = boardMappings->allKeys(); - QSettings settings(SSU_CONFIGURATION, QSettings::IniFormat); + SsuCoreConfig *settings = SsuCoreConfig::instance(); foreach (const QString &key, keys){ QString value = boardMappings->value(key).toString(); - if (settings.value("arch").toString() == value){ + if (settings->value("arch").toString() == value){ cachedModel = key; break; } @@ -185,10 +186,10 @@ QStringList SsuDeviceInfo::repos(bool rnd){ // read user-defined repositories from ssu.ini // TODO: in strict mode, filter the repository list from there - QSettings ssuSettings(SSU_CONFIGURATION, QSettings::IniFormat); - ssuSettings.beginGroup("repository-urls"); - result.append(ssuSettings.allKeys()); - ssuSettings.endGroup(); + SsuCoreConfig *ssuSettings = SsuCoreConfig::instance(); + ssuSettings->beginGroup("repository-urls"); + result.append(ssuSettings->allKeys()); + ssuSettings->endGroup(); result.removeDuplicates(); @@ -197,8 +198,8 @@ QStringList SsuDeviceInfo::repos(bool rnd){ result.removeAll(key); // read the disabled repositories from user configuration - if (ssuSettings.contains("disabled-repos")){ - foreach (const QString &key, ssuSettings.value("disabled-repos").toStringList()) + if (ssuSettings->contains("disabled-repos")){ + foreach (const QString &key, ssuSettings->value("disabled-repos").toStringList()) result.removeAll(key); } diff --git a/libssu/ssurepomanager.cpp b/libssu/ssurepomanager.cpp index 80084cd..4ae0157 100644 --- a/libssu/ssurepomanager.cpp +++ b/libssu/ssurepomanager.cpp @@ -11,7 +11,9 @@ #include "ssudeviceinfo.h" #include "ssurepomanager.h" +#include "ssucoreconfig.h" #include "ssulog.h" +#include "ssuvariables.h" #include "ssu.h" #include "../constants.h" @@ -28,8 +30,8 @@ void SsuRepoManager::update(){ SsuDeviceInfo deviceInfo; QStringList ssuFilters; - QSettings ssuSettings(SSU_CONFIGURATION, QSettings::IniFormat); - int deviceMode = ssuSettings.value("deviceMode").toInt(); + SsuCoreConfig *ssuSettings = SsuCoreConfig::instance(); + int deviceMode = ssuSettings->value("deviceMode").toInt(); SsuLog *ssuLog = SsuLog::instance(); @@ -44,8 +46,13 @@ void SsuRepoManager::update(){ if ((deviceMode & Ssu::RndMode) == Ssu::RndMode) rndMode = true; - // strict mode enabled -> delete all repositories not prefixed by ssu_ - if ((deviceMode & Ssu::StrictMode) == Ssu::StrictMode){ + // get list of device-specific repositories... + QStringList repos = deviceInfo.repos(rndMode); + + // strict mode enabled -> delete all repositories not prefixed by ssu + // assume configuration error if there are no device repos, and don't delete + // anything, even in strict mode + if ((deviceMode & Ssu::StrictMode) == Ssu::StrictMode && !repos.isEmpty()){ QDirIterator it(ZYPP_REPO_PATH, QDir::AllEntries|QDir::NoDot|QDir::NoDotDot); while (it.hasNext()){ it.next(); @@ -56,9 +63,6 @@ void SsuRepoManager::update(){ } } - // get list of device-specific repositories... - QStringList repos = deviceInfo.repos(rndMode); - // ... delete all ssu-managed repositories not valid for this device ... ssuFilters.append("ssu_*"); QDirIterator it(ZYPP_REPO_PATH, ssuFilters); @@ -102,3 +106,139 @@ void SsuRepoManager::update(){ } } } + +// RND repos have flavour (devel, testing, release), and release (latest, next) +// Release repos only have release (latest, next, version number) +QString SsuRepoManager::url(QString repoName, bool rndRepo, + QHash repoParameters, + QHash parametersOverride){ + QString r; + QStringList configSections; + SsuVariables var; + SsuLog *ssuLog = SsuLog::instance(); + SsuCoreConfig *settings = SsuCoreConfig::instance(); + QSettings *repoSettings = new QSettings(SSU_REPO_CONFIGURATION, QSettings::IniFormat); + SsuDeviceInfo deviceInfo; + + //errorFlag = false; + + settings->sync(); + + // fill in all arbitrary variables from ssu.inie + var.resolveSection(settings, "repository-url-variables", &repoParameters); + + // add/overwrite some of the variables with sane ones + if (rndRepo){ + repoParameters.insert("flavour", + repoSettings->value( + settings->flavour()+"-flavour/flavour-pattern").toString()); + repoParameters.insert("flavourPattern", + repoSettings->value( + settings->flavour()+"-flavour/flavour-pattern").toString()); + repoParameters.insert("flavourName", settings->flavour()); + configSections << settings->flavour()+"-flavour" << "rnd" << "all"; + + // Make it possible to give any values with the flavour as well. + // These values can be overridden later with domain if needed. + var.resolveSection(repoSettings, settings->flavour()+"-flavour", &repoParameters); + } else { + configSections << "release" << "all"; + } + + repoParameters.insert("release", settings->release(rndRepo)); + + if (!repoParameters.contains("debugSplit")) + repoParameters.insert("debugSplit", "packages"); + + if (!repoParameters.contains("arch")) + repoParameters.insert("arch", settings->value("arch").toString()); + + // Override device model (and therefore all the family, ... stuff) + if (parametersOverride.contains("model")) + deviceInfo.setDeviceModel(parametersOverride.value("model")); + + QStringList adaptationRepos = deviceInfo.adaptationRepos(); + + // read adaptation from settings, in case it can't be determined from + // board mappings. this is obsolete, and will be dropped soon + if (settings->contains("adaptation")) + repoParameters.insert("adaptation", settings->value("adaptation").toString()); + + repoParameters.insert("deviceFamily", deviceInfo.deviceFamily()); + repoParameters.insert("deviceModel", deviceInfo.deviceModel()); + + // Those keys have now been obsoleted by generic variables, support for + // it will be removed soon + QStringList keys; + keys << "chip" << "adaptation" << "vendor"; + foreach(QString key, keys){ + QString value; + if (deviceInfo.getValue(key,value)) + repoParameters.insert(key, value); + } + + // special handling for adaptation-repositories + // - check if repo is in right format (adaptation\d*) + // - check if the configuration has that many adaptation repos + // - export the entry in the adaptation list as %(adaptation) + // - look up variables for that adaptation, and export matching + // adaptation variable + QRegExp regex("adaptation\\\d*", Qt::CaseSensitive, QRegExp::RegExp2); + if (regex.exactMatch(repoName)){ + regex.setPattern("\\\d*"); + regex.lastIndexIn(repoName); + int n = regex.cap().toInt(); + + if (!adaptationRepos.isEmpty()){ + if (adaptationRepos.size() <= n) { + ssuLog->print(LOG_INFO, "Note: repo index out of bounds, substituting 0" + repoName); + n = 0; + } + + QString adaptationRepo = adaptationRepos.at(n); + repoParameters.insert("adaptation", adaptationRepo); + ssuLog->print(LOG_DEBUG, "Found first adaptation " + repoName); + + QHash h = deviceInfo.variableSection(adaptationRepo); + + QHash::const_iterator i = h.constBegin(); + while (i != h.constEnd()){ + repoParameters.insert(i.key(), i.value()); + i++; + } + } else + ssuLog->print(LOG_INFO, "Note: adaptation repo for invalid repo requested " + repoName); + + repoName = "adaptation"; + } + + // Domain variables + // first read all variables from default-domain + var.resolveSection(repoSettings, "default-domain", &repoParameters); + + // then overwrite with domain specific things if that block is available + var.resolveSection(repoSettings, settings->domain()+"-domain", &repoParameters); + + // override arbitrary variables, mostly useful for generating mic URLs + QHash::const_iterator i = parametersOverride.constBegin(); + while (i != parametersOverride.constEnd()){ + repoParameters.insert(i.key(), i.value()); + i++; + } + + if (settings->contains("repository-urls/" + repoName)) + r = settings->value("repository-urls/" + repoName).toString(); + else { + foreach (const QString §ion, configSections){ + repoSettings->beginGroup(section); + if (repoSettings->contains(repoName)){ + r = repoSettings->value(repoName).toString(); + repoSettings->endGroup(); + break; + } + repoSettings->endGroup(); + } + } + + return var.resolveString(r, &repoParameters); +} diff --git a/libssu/ssurepomanager.h b/libssu/ssurepomanager.h index cee2817..b3df28c 100644 --- a/libssu/ssurepomanager.h +++ b/libssu/ssurepomanager.h @@ -23,6 +23,13 @@ class SsuRepoManager: public QObject { * QHash */ void update(); + /** + * Resolve a repository url + * @return the repository URL on success, an empty string on error + */ + QString url(QString repoName, bool rndRepo=false, + QHash repoParameters=QHash(), + QHash parametersOverride=QHash()); }; #endif diff --git a/rndssucli/rndssucli.cpp b/rndssucli/rndssucli.cpp index c087c00..4098d69 100644 --- a/rndssucli/rndssucli.cpp +++ b/rndssucli/rndssucli.cpp @@ -10,6 +10,9 @@ #include #include +#include + +#include #include "rndssucli.h" @@ -18,6 +21,7 @@ RndSsuCli::RndSsuCli(): QObject(){ QCoreApplication::instance(),SLOT(quit()), Qt::DirectConnection); connect(&ssu, SIGNAL(done()), this, SLOT(handleResponse())); + state = Idle; } void RndSsuCli::handleResponse(){ @@ -33,17 +37,45 @@ void RndSsuCli::handleResponse(){ } -void RndSsuCli::optFlavour(QString newFlavour){ +void RndSsuCli::optFlavour(QStringList opt){ QTextStream qout(stdout); - if (newFlavour != ""){ + if (opt.count() == 3){ qout << "Changing flavour from " << ssu.flavour() - << " to " << newFlavour << endl; - ssu.setFlavour(newFlavour); - } else + << " to " << opt.at(2) << endl; + ssu.setFlavour(opt.at(2)); + state = Idle; + } else if (opt.count() == 2) { qout << "Device flavour is currently: " << ssu.flavour() << endl; + state = Idle; + } +} - QCoreApplication::exit(0); +void RndSsuCli::optMode(QStringList opt){ + QTextStream qout(stdout); + qout << "Mode handling is currently not implemented" << endl; + state = Idle; +} + +void RndSsuCli::optModifyRepo(int action, QStringList opt){ + SsuRepoManager repoManager; + + QTextStream qout(stdout); + qout << "Repository management is currently not implemented" << endl; + + if (opt.count() == 3){ + + } else if (opt.count() == 4){ + + } +/* + switch(action){ + case Add: + case Remove: + case Disable: + case Enable: + } +*/ } void RndSsuCli::optRegister(){ @@ -72,9 +104,58 @@ void RndSsuCli::optRegister(){ tcsetattr(STDIN_FILENO, TCSANOW, &termOld); ssu.sendRegistration(username, password); + state = Busy; +} + +void RndSsuCli::optRelease(QStringList opt){ + QTextStream qout(stdout); + + if (opt.count() == 3){ + if (opt.at(2) == "-r"){ + qout << "Device release (RnD) is currently: " << ssu.release(true) << endl; + state = Idle; + } else { + qout << "Changing release from " << ssu.release() + << " to " << opt.at(2) << endl; + ssu.setRelease(opt.at(2)); + state = Idle; + } + } else if (opt.count() == 2) { + qout << "Device release is currently: " << ssu.release() << endl; + state = Idle; + } else if (opt.count() == 4 && opt.at(2) == "-r"){ + qout << "Changing release (RnD) from " << ssu.release(true) + << " to " << opt.at(3) << endl; + ssu.setRelease(opt.at(3), true); + state = Idle; + } +} + +void RndSsuCli::optRepos(QStringList opt){ + SsuRepoManager repoManager; + SsuDeviceInfo deviceInfo; + QHash repoParameters; + bool rndRepo = false; + + QHash repoOverride; + repoOverride.insert("release", "@RELEASE@"); + repoOverride.insert("rndRelease", "@RNDRELEASE@"); + repoOverride.insert("flavour", "@FLAVOUR@"); + repoOverride.insert("arch", "@ARCH@"); + //repoOverride.insert("", ""); + + QStringList repos = deviceInfo.repos(rndRepo); + + // for each repository, print repo and resolve url + foreach (const QString &repo, repos){ + QString repoUrl = ssu.repoUrl(repo, rndRepo, repoParameters, repoOverride); + qDebug() << "Resolved to " << repoUrl; + } + + state = Idle; } -void RndSsuCli::optResolve(){ +void RndSsuCli::optResolve(QStringList opt){ /* * resolve URL and print * TODO: arguments @@ -94,7 +175,6 @@ void RndSsuCli::optResolve(){ QCoreApplication::exit(1); */ - QCoreApplication::exit(1); } void RndSsuCli::optStatus(){ @@ -110,23 +190,31 @@ void RndSsuCli::optStatus(){ qout << "Device model: " << deviceInfo.deviceModel() << endl; qout << "Device variant: " << deviceInfo.deviceVariant() << endl; qout << "Device UID: " << deviceInfo.deviceUid() << endl; - - QCoreApplication::exit(0); } -void RndSsuCli::optUpdate(bool force){ +void RndSsuCli::optUpdateCredentials(QStringList opt){ QTextStream qout(stdout); /* * update the credentials * optional argument: -f */ + bool force=false; + if (opt.count() == 3 && opt.at(2) == "-f") + force=true; + if (!ssu.isRegistered()){ qout << "Device is not registered, can't update credentials" << endl; + state = Idle; QCoreApplication::exit(1); - } else + } else { ssu.updateCredentials(force); + state = Busy; + } +} - QCoreApplication::exit(0); +void RndSsuCli::optUpdateRepos(){ + SsuRepoManager repoManager; + repoManager.update(); } void RndSsuCli::run(){ @@ -143,27 +231,85 @@ void RndSsuCli::run(){ return; } - if (arguments.at(1) == "register" && arguments.count() == 2){ - optRegister(); - } else if (arguments.at(1) == "flavour" && - (arguments.count() == 2 || arguments.count() == 3)){ - if (arguments.count() == 2) optFlavour(); - else optFlavour(arguments.at(2)); - } else if (arguments.at(1) == "update" && - (arguments.count() == 2 || arguments.count() == 3)){ - if (arguments.count() == 3 && arguments.at(2) == "-f") - optUpdate(true); - else optUpdate(false); - } else if (arguments.at(1) == "resolve"){ - optResolve(); - } else if (arguments.at(1) == "status" && arguments.count() == 2){ - optStatus(); + // everything without additional arguments gets handled here + // functions with arguments need to take care of argument validation themselves + if (arguments.count() == 2){ + if (arguments.at(1) == "register" || arguments.at(1) == "r") + optRegister(); + else if (arguments.at(1) == "status" || arguments.at(1) == "s") + optStatus(); + else if (arguments.at(1) == "updaterepos" || arguments.at(1) == "ur") + optUpdateRepos(); + else + state = UserError; + } else if (arguments.count() >= 3){ + if (arguments.at(1) == "addrepo" || arguments.at(1) == "ar") + optModifyRepo(Add, arguments); + else if (arguments.at(1) == "removerepo" || arguments.at(1) == "rr") + optModifyRepo(Remove, arguments); + else if (arguments.at(1) == "enablerepo" || arguments.at(1) == "er") + optModifyRepo(Enable, arguments); + else if (arguments.at(1) == "disablerepo" || arguments.at(1) == "dr") + optModifyRepo(Disable, arguments); + else if (arguments.at(1) == "resolve") + optResolve(arguments); + else + state = UserError; } else + state = UserError; + + // functions accepting 0 or more arguments; those need to set state to Idle + // on success + if (arguments.at(1) == "repos" || arguments.at(1) == "lr") + optRepos(arguments); + else if (arguments.at(1) == "flavour" || arguments.at(1) == "fl") + optFlavour(arguments); + else if (arguments.at(1) == "mode" || arguments.at(1) == "m") + optMode(arguments); + else if (arguments.at(1) == "release" || arguments.at(1) == "re") + optRelease(arguments); + else if (arguments.at(1) == "update" || arguments.at(1) == "up") + optUpdateCredentials(arguments); + + // functions that need to wait for a response from ssu should set a flag so + // we can do default exit catchall here + if (state == Idle) + QCoreApplication::exit(0); + else if (state == UserError) usage(); } void RndSsuCli::usage(){ QTextStream qout(stdout); - qout << "Usage: rndssu flavour [flavour]|register|update [-f]|status" << endl; + qout << "\nUsage: ssu [-command-options] [arguments]" << endl + << endl + << "Repository management:" << endl + << "\tupdaterepos, ur \tupdate repository files" << endl + << "\trepos, lr \tlist configured repositories" << endl + << "\t [-m] \tformat output suitable for kickstart" << endl + << "\t [device] \tuse repos for 'device'" << endl + << "\t [flags] \tadditional flags" << endl + << "\taddrepo, ar \tadd this repository" << endl + << "\t [url] \tspecify URL, if not configured" << endl + << "\tremoverepo, rr \tremove this repository from configuration" << endl + << "\tenablerepo, er \tenable this repository" << endl + << "\tdisablerepo, dr \tdisable this repository" << endl + << endl + << "Configuration management:" << endl + << "\tflavour, fl \tdisplay flavour used (RnD only)" << endl + << "\t [newflavour] \tset new flavour" << endl + << "\trelease, re \tdisplay release used" << endl + << "\t [-r] \tuse RnD release" << endl + << "\t [newrelease] \tset new (RnD)release" << endl + << "\tmode, m \tdisplay current device mode" << endl + << "\t [newmode] \tset new device mode" << endl + << endl + << "Device management:" << endl + << "\tstatus, s \tprint registration status and device information" << endl + << "\tregister, r \tregister this device" << endl + << "\tupdate, up \tupdate repository credentials" << endl + << "\t [-f] \tforce update" << endl + << endl; + qout.flush(); QCoreApplication::exit(1); } diff --git a/rndssucli/rndssucli.h b/rndssucli/rndssucli.h index bfecbf8..1276e32 100644 --- a/rndssucli/rndssucli.h +++ b/rndssucli/rndssucli.h @@ -26,13 +26,31 @@ class RndSsuCli: public QObject { private: Ssu ssu; QSettings settings; + int state; void usage(); - void optFlavour(QString newFlavour=""); + void optFlavour(QStringList opt); + void optMode(QStringList opt); + void optModifyRepo(int action, QStringList opt); void optRegister(); - void optResolve(); + void optRelease(QStringList opt); + void optRepos(QStringList opt); + void optResolve(QStringList opt); void optStatus(); - void optUpdate(bool force=false); - + void optUpdateCredentials(QStringList opt); + void optUpdateRepos(); + + enum Actions { + Remove, + Add, + Disable, + Enable + }; + + enum State { + Idle, + Busy, + UserError + }; private slots: void handleResponse();