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