I have made the following changes intended for : CE:MW:Shared / ssu Please review and accept or decline. BOSS has already run some checks on this request. See the "Messages from BOSS" section below.
https://build.pub.meego.com//request/show/8446 Thank You, aard [This message was auto-generated] --- Request # 8446: Messages from BOSS: State: review at 2013-03-22T13:02:40 by bossbot Reviews: accepted by bossbot : Prechecks succeeded. new for CE-maintainers : Please replace this text with a review and approve/reject the review (not the SR). BOSS will take care of the rest Changes: submit: home:aard:branches:CE:MW:Shared / ssu -> CE:MW:Shared / ssu changes files: -------------- --- ssu.changes +++ ssu.changes @@ -0,0 +1,20 @@ +* Fri Mar 22 2013 Bernd Wachter <[email protected]> - 0.25 +- Add variable substitution + +* Thu Mar 21 2013 Bernd Wachter <[email protected]> - 0.24 +- Add support for arbitrary variables in flavour groups +- Split off device info and settings functionality +- Add support for board-mappings.d + +* Mon Mar 11 2013 Bernd Wachter <[email protected]> - 0.23 +- Add force option to rndssu update + +* Mon Mar 11 2013 Bernd Wachter <[email protected]> - 0.22 +- Add systemd logging to core library +- Move zypper plugin logging to core library +- Fix bug where resolverplugin does not return on credentials update + +* Mon Mar 11 2013 Bernd Wachter <[email protected]> - 0.21 +- Reduce time before credentials get refreshed +- Add logging for url resolver + old: ---- ssu-0.20.tar.gz new: ---- ssu-0.25.tar.gz spec files: ----------- --- ssu.spec +++ ssu.spec @@ -1,9 +1,9 @@ Name: ssu -Version: 0.20 +Version: 0.25 Release: 1 Summary: SSU enabler for RND Group: System/Base -License: Proprietary +License: GPLv2 Source0: %{name}-%{version}.tar.gz URL: https://github.com/nemomobile/ssu BuildRequires: pkgconfig(QtCore) @@ -93,7 +93,7 @@ %prep -%setup -q +%setup -q -n %{name}-%{version} %build other changes: -------------- ++++++ ssu-0.20.tar.gz -> ssu-0.25.tar.gz --- constants.h +++ constants.h @@ -18,6 +18,8 @@ #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 +#define SSU_BOARD_MAPPING_CONFIGURATION_DIR "/usr/share/ssu/board-mappings.d" /// The SSU protocol version used by the ssu client libraries #define SSU_PROTOCOL_VERSION "1" #endif --- libssu/libssu.pro +++ libssu/libssu.pro @@ -1,13 +1,22 @@ BUILD = ../build/libssu HEADERS = ssu.h \ + ssudeviceinfo.h \ + ssulog.h \ + ssuvariables.h \ + ssusettings.h \ ../constants.h -SOURCES = ssu.cpp +SOURCES = ssu.cpp \ + ssudeviceinfo.cpp \ + ssulog.cpp \ + ssuvariables.cpp \ + ssusettings.cpp TEMPLATE = lib TARGET = ssu -CONFIG += dll mobility +CONFIG += dll mobility link_pkgconfig QT -= gui QT += network xml MOBILITY += systeminfo +PKGCONFIG += libsystemd-journal headers.files = ssu.h headers.path = /usr/include --- libssu/ssu.cpp +++ libssu/ssu.cpp @@ -5,16 +5,18 @@ * @date 2012 */ -#include <QSystemDeviceInfo> - #include <QtXml/QDomDocument> + #include "ssu.h" -#include "../constants.h" +#include "ssulog.h" +#include "ssuvariables.h" -QTM_USE_NAMESPACE +#include "../constants.h" -Ssu::Ssu(): QObject(){ +Ssu::Ssu(QString fallbackLog): QObject(){ errorFlag = false; + fallbackLogPath = fallbackLog; + pendingRequests = 0; #ifdef SSUCONFHACK // dirty hack to make sure we can write to the configuration @@ -29,83 +31,8 @@ } #endif - settings = new QSettings(SSU_CONFIGURATION, QSettings::IniFormat); + settings = new SsuSettings(SSU_CONFIGURATION, QSettings::IniFormat, SSU_DEFAULT_CONFIGURATION); repoSettings = new QSettings(SSU_REPO_CONFIGURATION, QSettings::IniFormat); - boardMappings = new QSettings(SSU_BOARD_MAPPING_CONFIGURATION, QSettings::IniFormat); - QSettings defaultSettings(SSU_DEFAULT_CONFIGURATION, QSettings::IniFormat); - - int configVersion=0; - int defaultConfigVersion=0; - if (settings->contains("configVersion")) - configVersion = settings->value("configVersion").toInt(); - if (defaultSettings.contains("configVersion")) - defaultConfigVersion = defaultSettings.value("configVersion").toInt(); - - if (configVersion < defaultConfigVersion){ - qDebug() << "Configuration is outdated, updating from " << configVersion - << " to " << defaultConfigVersion; - - for (int i=configVersion+1;i<=defaultConfigVersion;i++){ - QStringList defaultKeys; - QString currentSection = QString("%1/").arg(i); - - qDebug() << "Processing configuration version " << i; - defaultSettings.beginGroup(currentSection); - defaultKeys = defaultSettings.allKeys(); - defaultSettings.endGroup(); - foreach (const QString &key, defaultKeys){ - // Default keys support both commands and new keys - if (key.compare("cmd-remove", Qt::CaseSensitive) == 0){ - // Remove keys listed in value as string list - QStringList oldKeys = defaultSettings.value(currentSection + key).toStringList(); - foreach (const QString &oldKey, oldKeys){ - if (settings->contains(oldKey)){ - settings->remove(oldKey); - qDebug() << "Removing old key:" << oldKey; - } - } - } else if (!settings->contains(key)){ - // Add new keys.. - settings->setValue(key, defaultSettings.value(currentSection + key)); - qDebug() << "Adding new key: " << key; - } else { - // ... or update the ones where default values has changed. - QVariant oldValue; - - // check if an old value exists in an older configuration version - for (int j=i-1;j>0;j--){ - if (defaultSettings.contains(QString("%1/").arg(j)+key)){ - oldValue = defaultSettings.value(QString("%1/").arg(j)+key); - break; - } - } - - // skip updating if there is no old value, since we can't check if the - // default value has changed - if (oldValue.isNull()) - continue; - - QVariant newValue = defaultSettings.value(currentSection + key); - if (oldValue == newValue){ - // old and new value match, no need to do anything, apart from beating the - // person who added a useless key - continue; - } else { - // default value has changed, so check if the configuration is still - // using the old default value... - QVariant currentValue = settings->value(key); - // testcase: handles properly default update of thing with changed value in ssu.ini? - if (currentValue == oldValue){ - // ...and update the key if it does - settings->setValue(key, newValue); - qDebug() << "Updating " << key << " from " << currentValue << " to " << newValue; - } - } - } - } - settings->setValue("configVersion", i); - } - } #ifdef TARGET_ARCH if (!settings->contains("arch")) @@ -144,117 +71,6 @@ return "your-configuration-is-broken-and-does-not-contain-credentials-url-for-" + scope; } -QString Ssu::deviceFamily(){ - QString model = deviceModel(); - - if (!cachedFamily.isEmpty()) - return cachedFamily; - - cachedFamily = "UNKNOWN"; - - if (boardMappings->contains("variants/" + model)) - model = boardMappings->value("variants/" + model).toString(); - - if (boardMappings->contains(model + "/family")) - cachedFamily = boardMappings->value(model + "/family").toString(); - - return cachedFamily; -} - -QString Ssu::deviceModel(){ - QDir dir; - QFile procCpuinfo; - QStringList keys; - - if (!cachedModel.isEmpty()) - return cachedModel; - - boardMappings->beginGroup("file.exists"); - keys = boardMappings->allKeys(); - - // check if the device can be identified by testing for a file - foreach (const QString &key, keys){ - QString value = boardMappings->value(key).toString(); - if (dir.exists(value)){ - cachedModel = key; - break; - } - } - boardMappings->endGroup(); - if (!cachedModel.isEmpty()) return cachedModel; - - // check if the QSystemInfo model is useful - QSystemDeviceInfo devInfo; - QString model = devInfo.model(); - boardMappings->beginGroup("systeminfo.equals"); - keys = boardMappings->allKeys(); - foreach (const QString &key, keys){ - QString value = boardMappings->value(key).toString(); - if (model == value){ - cachedModel = key; - break; - } - } - boardMappings->endGroup(); - if (!cachedModel.isEmpty()) return cachedModel; - - // check if the device can be identified by a string in /proc/cpuinfo - procCpuinfo.setFileName("/proc/cpuinfo"); - procCpuinfo.open(QIODevice::ReadOnly | QIODevice::Text); - if (procCpuinfo.isOpen()){ - QTextStream in(&procCpuinfo); - QString cpuinfo = in.readAll(); - boardMappings->beginGroup("cpuinfo.contains"); - keys = boardMappings->allKeys(); - - foreach (const QString &key, keys){ - QString value = boardMappings->value(key).toString(); - if (cpuinfo.contains(value)){ - cachedModel = key; - break; - } - } - boardMappings->endGroup(); - } - if (!cachedModel.isEmpty()) return cachedModel; - - - // check if there's a match on arch ofr generic fallback. This probably - // only makes sense for x86 - boardMappings->beginGroup("arch.equals"); - keys = boardMappings->allKeys(); - foreach (const QString &key, keys){ - QString value = boardMappings->value(key).toString(); - if (settings->value("arch").toString() == value){ - cachedModel = key; - break; - } - } - boardMappings->endGroup(); - if (cachedModel.isEmpty()) cachedModel = "UNKNOWN"; - - return cachedModel; -} - -QString Ssu::deviceUid(){ - QString IMEI; - QSystemDeviceInfo devInfo; - - IMEI = devInfo.imei(); - // this might not be completely unique (or might change on reflash), but works for now - if (IMEI == ""){ - if (deviceFamily() == "n950-n9" || deviceFamily() == "n900"){ - bool ok; - QString IMEIenv = getenv("imei"); - IMEIenv.toLongLong(&ok, 10); - if (ok && (IMEIenv.length() == 16 || IMEIenv.length() == 15)) - IMEI = IMEIenv; - } else - IMEI = devInfo.uniqueDeviceID(); - } - return IMEI; -} - bool Ssu::error(){ return errorFlag; } @@ -292,6 +108,7 @@ bool Ssu::registerDevice(QDomDocument *response){ QString certificateString = response->elementsByTagName("certificate").at(0).toElement().text(); QSslCertificate certificate(certificateString.toAscii()); + SsuLog *ssuLog = SsuLog::instance(); if (certificate.isNull()){ // make sure device is in unregistered state on failed registration @@ -314,7 +131,7 @@ // oldUser is just for reference purposes, in case we want to notify // about owner changes for the device QString oldUser = response->elementsByTagName("user").at(0).toElement().text(); - qDebug() << "Old user:" << oldUser; + ssuLog->print(LOG_DEBUG, QString("Old user for your device was: %1").arg(oldUser)); // if we came that far everything required for device registration is done settings->setValue("registered", true); @@ -335,30 +152,29 @@ QString Ssu::repoUrl(QString repoName, bool rndRepo, QHash<QString, QString> repoParameters){ QString r; QStringList configSections; - QStringList repoVariables; + SsuVariables var; errorFlag = false; // fill in all arbitrary variables from ssu.ini - settings->beginGroup("repository-url-variables"); - repoVariables = settings->allKeys(); - foreach (const QString &key, repoVariables){ - repoParameters.insert(key, settings->value(key).toString()); - } - settings->endGroup(); + 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()); - repoParameters.insert("release", settings->value("rndRelease").toString()); 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 { - repoParameters.insert("release", settings->value("release").toString()); configSections << "release" << "all"; } + repoParameters.insert("release", release(rndRepo)); + if (!repoParameters.contains("debugSplit")) repoParameters.insert("debugSplit", "packages"); @@ -366,28 +182,24 @@ repoParameters.insert("arch", settings->value("arch").toString()); repoParameters.insert("adaptation", settings->value("adaptation").toString()); - repoParameters.insert("deviceFamily", deviceFamily()); - repoParameters.insert("deviceModel", deviceModel()); + repoParameters.insert("deviceFamily", deviceInfo.deviceFamily()); + repoParameters.insert("deviceModel", deviceInfo.deviceModel()); + + QStringList keys; + keys << "chip" << "adaptation" << "vendor"; + + foreach(QString key, keys){ + QString value; + if (deviceInfo.getValue(key,value)) + repoParameters.insert(key, value); + } // Domain variables // first read all variables from default-domain - repoSettings->beginGroup("default-domain"); - QStringList defKeys = repoSettings->allKeys(); - foreach (const QString &key, defKeys){ - repoParameters.insert(key, repoSettings->value(key).toString()); - } - repoSettings->endGroup(); + var.resolveSection(repoSettings, "default-domain", &repoParameters); + // then overwrite with domain specific things if that block is available - QString domainSection = domain() + "-domain"; - QStringList sections = repoSettings->childGroups(); - if (sections.contains(domainSection)){ - repoSettings->beginGroup(domainSection); - QStringList domainKeys = repoSettings->allKeys(); - foreach (const QString &key, domainKeys){ - repoParameters.insert(key, repoSettings->value(key).toString()); - } - repoSettings->endGroup(); - } + var.resolveSection(repoSettings, domain()+"-domain", &repoParameters); if (settings->contains("repository-urls/" + repoName)) r = settings->value("repository-urls/" + repoName).toString(); @@ -403,25 +215,19 @@ } } - QHashIterator<QString, QString> i(repoParameters); - while (i.hasNext()){ - i.next(); - r.replace( - QString("%(%1)").arg(i.key()), - i.value()); - } - - return r; + return var.resolveString(r, &repoParameters); } void Ssu::requestFinished(QNetworkReply *reply){ QSslConfiguration sslConfiguration = reply->sslConfiguration(); + SsuLog *ssuLog = SsuLog::instance(); - qDebug() << sslConfiguration.peerCertificate().issuerInfo(QSslCertificate::CommonName); - qDebug() << sslConfiguration.peerCertificate().subjectInfo(QSslCertificate::CommonName); + ssuLog->print(LOG_DEBUG, QString("Certificate used was issued for '%1' by '%2'. Complete chain:") + .arg(sslConfiguration.peerCertificate().subjectInfo(QSslCertificate::CommonName)) + .arg(sslConfiguration.peerCertificate().issuerInfo(QSslCertificate::CommonName))); foreach (const QSslCertificate cert, sslConfiguration.peerCertificateChain()){ - qDebug() << "Cert from chain" << cert.subjectInfo(QSslCertificate::CommonName); + ssuLog->print(LOG_DEBUG, QString("-> %1").arg(cert.subjectInfo(QSslCertificate::CommonName))); } // what sucks more, this or goto? @@ -473,6 +279,8 @@ } while (false); pendingRequests--; + + ssuLog->print(LOG_DEBUG, QString("Request finished, pending requests: %1").arg(pendingRequests)); if (pendingRequests == 0) emit done(); } @@ -483,6 +291,8 @@ QString ssuCaCertificate, ssuRegisterUrl; QString username, domainName; + SsuLog *ssuLog = SsuLog::instance(); + // Username can include also domain, (user@domain), separate those if (usernameDomain.contains('@')) { // separate domain/username and set domain @@ -509,7 +319,7 @@ } else ssuRegisterUrl = settings->value("register-url").toString(); - QString IMEI = deviceUid(); + QString IMEI = deviceInfo.deviceUid(); if (IMEI == ""){ setError("No valid UID available for your device. For phones: is your modem online?"); return; @@ -534,7 +344,7 @@ QUrl form; form.addQueryItem("protocolVersion", SSU_PROTOCOL_VERSION); - form.addQueryItem("deviceModel", deviceModel()); + form.addQueryItem("deviceModel", deviceInfo.deviceModel()); if (!domain().isEmpty()){ form.addQueryItem("domain", domain()); } @@ -553,7 +363,7 @@ // clear header, the other request bits are reusable request.setHeader(QNetworkRequest::ContentTypeHeader, 0); request.setUrl(homeUrl + "/authorized_keys"); - qDebug() << "sending request to " << request.url(); + ssuLog->print(LOG_DEBUG, QString("Trying to get SSH keys from %1").arg(request.url().toString())); pendingRequests++; manager->get(request); } @@ -606,13 +416,19 @@ errorFlag = true; errorString = errorMessage; + SsuLog *ssuLog = SsuLog::instance(); + + // dump error message to systemd journal for easier debugging + ssuLog->print(LOG_WARNING, errorMessage); + // assume that we don't even need to wait for other pending requests, - // and just die. This is only relevant for CLI, which well exit after done() + // and just die. This is only relevant for CLI, which will exit after done() emit done(); } void Ssu::setFlavour(QString flavour){ settings->setValue("flavour", flavour); + settings->sync(); emit flavourChanged(); } @@ -621,6 +437,7 @@ settings->setValue("rndRelease", release); else settings->setValue("release", release); + settings->sync(); } void Ssu::setDomain(QString domain){ @@ -655,7 +472,9 @@ void Ssu::updateCredentials(bool force){ errorFlag = false; - if (deviceUid() == ""){ + SsuLog *ssuLog = SsuLog::instance(); + + if (deviceInfo.deviceUid() == ""){ setError("No valid UID available for your device. For phones: is your modem online?"); return; } @@ -682,12 +501,14 @@ } if (!force){ - // skip updating if the last update was less than a day ago + // skip updating if the last update was less than 30 minutes ago QDateTime now = QDateTime::currentDateTime(); if (settings->contains("lastCredentialsUpdate")){ QDateTime last = settings->value("lastCredentialsUpdate").toDateTime(); - if (last >= now.addDays(-1)){ + if (last >= now.addSecs(-1800)){ + ssuLog->print(LOG_DEBUG, QString("Skipping credentials update, last update was at %1") + .arg(last.toString())); emit done(); return; } @@ -710,9 +531,10 @@ sslConfiguration.setLocalCertificate(certificate); QNetworkRequest request; - request.setUrl(QUrl(ssuCredentialsUrl.arg(deviceUid()))); + request.setUrl(QUrl(ssuCredentialsUrl.arg(deviceInfo.deviceUid()))); - qDebug() << request.url(); + ssuLog->print(LOG_DEBUG, QString("Sending credential update request to %1") + .arg(request.url().toString())); request.setSslConfiguration(sslConfiguration); pendingRequests++; @@ -730,6 +552,7 @@ settings->setValue("privateKey", ""); settings->setValue("certificate", ""); settings->setValue("registered", false); + settings->sync(); emit registrationStatusChanged(); } --- libssu/ssu.h +++ libssu/ssu.h @@ -16,11 +16,14 @@ #include <QtXml/QDomDocument> +#include <ssudeviceinfo.h> +#include <ssusettings.h> + class Ssu: public QObject { Q_OBJECT public: - Ssu(); + Ssu(QString fallbackLog="/tmp/ssu.log"); /** * Find a username/password pair for the given scope * @return a QPair with username and password, or an empty QPair if scope is invalid @@ -41,19 +44,6 @@ */ QString credentialsUrl(QString scope); /** - * Try to find the device family for the system this is running on - */ - Q_INVOKABLE QString deviceFamily(); - /** - * Try to find out ond what kind of system this is running - */ - Q_INVOKABLE QString deviceModel(); - /** - * Calculate the device ID used in SSU requests - * @return QSystemDeviceInfo::imei(), if available, or QSystemDeviceInfo::uniqueDeviceID() - */ - Q_INVOKABLE QString deviceUid(); - /** * Returns if the last operation was successful * @retval true last operation was successful * @retval false last operation failed, you should check lastError() for details @@ -121,13 +111,20 @@ */ Q_INVOKABLE bool useSslVerify(); + + // compat stuff, might go away when refactoring is finished + Q_INVOKABLE QString deviceFamily(){ return deviceInfo.deviceFamily(); }; + Q_INVOKABLE QString deviceModel(){ return deviceInfo.deviceModel(); }; + Q_INVOKABLE QString deviceUid(){ return deviceInfo.deviceUid(); }; + private: - QString errorString; - QString cachedModel, cachedFamily; + QString errorString, fallbackLogPath; bool errorFlag; QNetworkAccessManager *manager; int pendingRequests; - QSettings *settings, *repoSettings, *boardMappings; + QSettings *repoSettings; + SsuSettings *settings; + SsuDeviceInfo deviceInfo; bool registerDevice(QDomDocument *response); bool setCredentials(QDomDocument *response); bool verifyResponse(QDomDocument *response); --- libssu/ssudeviceinfo.cpp +++ libssu/ssudeviceinfo.cpp @@ -0,0 +1,156 @@ +/** + * @file ssudeviceinfo.cpp + * @copyright 2013 2013 Jolla Ltd. + * @author Bernd Wachter <[email protected]> + * @date 2013 + */ + +#include <QSystemDeviceInfo> +#include <QTextStream> +#include <QDir> + +#include "ssudeviceinfo.h" + +#include "../constants.h" + +QTM_USE_NAMESPACE + +SsuDeviceInfo::SsuDeviceInfo(): QObject(){ + + boardMappings = new SsuSettings(SSU_BOARD_MAPPING_CONFIGURATION, SSU_BOARD_MAPPING_CONFIGURATION_DIR); +} + +QString SsuDeviceInfo::deviceFamily(){ + QString model = deviceModel(); + + if (!cachedFamily.isEmpty()) + return cachedFamily; + + cachedFamily = "UNKNOWN"; + + if (boardMappings->contains("variants/" + model)) { + model = boardMappings->value("variants/" + model).toString(); + cachedVariant = model; + } + + if (boardMappings->contains(model + "/family")) + cachedFamily = boardMappings->value(model + "/family").toString(); + + return cachedFamily; +} + +QString SsuDeviceInfo::deviceVariant(){ + if (!cachedVariant.isEmpty()) + return cachedVariant; + + cachedVariant = ""; + + if (boardMappings->contains("variants/" + deviceModel())) { + cachedVariant = boardMappings->value("variants/" + deviceModel()).toString(); + } + + return cachedVariant; +} + +QString SsuDeviceInfo::deviceModel(){ + QDir dir; + QFile procCpuinfo; + QStringList keys; + + if (!cachedModel.isEmpty()) + return cachedModel; + + boardMappings->beginGroup("file.exists"); + keys = boardMappings->allKeys(); + + // check if the device can be identified by testing for a file + foreach (const QString &key, keys){ + QString value = boardMappings->value(key).toString(); + if (dir.exists(value)){ + cachedModel = key; + break; + } + } + boardMappings->endGroup(); + if (!cachedModel.isEmpty()) return cachedModel; + + // check if the QSystemInfo model is useful + QSystemDeviceInfo devInfo; + QString model = devInfo.model(); + boardMappings->beginGroup("systeminfo.equals"); + keys = boardMappings->allKeys(); + foreach (const QString &key, keys){ + QString value = boardMappings->value(key).toString(); + if (model == value){ + cachedModel = key; + break; + } + } + boardMappings->endGroup(); + if (!cachedModel.isEmpty()) return cachedModel; + + // check if the device can be identified by a string in /proc/cpuinfo + procCpuinfo.setFileName("/proc/cpuinfo"); + procCpuinfo.open(QIODevice::ReadOnly | QIODevice::Text); + if (procCpuinfo.isOpen()){ + QTextStream in(&procCpuinfo); + QString cpuinfo = in.readAll(); + boardMappings->beginGroup("cpuinfo.contains"); + keys = boardMappings->allKeys(); + + foreach (const QString &key, keys){ + QString value = boardMappings->value(key).toString(); + if (cpuinfo.contains(value)){ + cachedModel = key; + break; + } + } + boardMappings->endGroup(); + } + if (!cachedModel.isEmpty()) return cachedModel; + + + // check if there's a match on arch ofr generic fallback. This probably + // only makes sense for x86 + boardMappings->beginGroup("arch.equals"); + keys = boardMappings->allKeys(); + + QSettings settings(SSU_CONFIGURATION, QSettings::IniFormat); + foreach (const QString &key, keys){ + QString value = boardMappings->value(key).toString(); + if (settings.value("arch").toString() == value){ + cachedModel = key; + break; + } + } + boardMappings->endGroup(); + if (cachedModel.isEmpty()) cachedModel = "UNKNOWN"; + + return cachedModel; +} + +QString SsuDeviceInfo::deviceUid(){ + QString IMEI; + QSystemDeviceInfo devInfo; + + IMEI = devInfo.imei(); + + // this might not be completely unique (or might change on reflash), but works for now + if (IMEI == ""){ + IMEI = devInfo.uniqueDeviceID(); + } + + return IMEI; +} + +bool SsuDeviceInfo::getValue(const QString& key, QString& value){ + if (boardMappings->contains(deviceVariant()+"/"+key)){ + value = boardMappings->value(deviceVariant()+"/"+key).toString(); + return true; + } + else if (boardMappings->contains(deviceModel()+"/"+key)){ + value = boardMappings->value(deviceModel()+"/"+key).toString(); + return true; + } + return false; +} --- libssu/ssudeviceinfo.h +++ libssu/ssudeviceinfo.h @@ -0,0 +1,45 @@ +/** + * @file ssudeviceinfo.h + * @copyright 2013 2013 Jolla Ltd. + * @author Bernd Wachter <[email protected]> + * @date 2013 + */ + +#ifndef _SSUDEVICEINFO_H +#define _SSUDEVICEINFO_H + +#include <QObject> +#include <QSettings> + +#include <ssusettings.h> + +class SsuDeviceInfo: public QObject { + Q_OBJECT + + public: + SsuDeviceInfo(); + /** + * Try to find the device family for the system this is running on + */ + Q_INVOKABLE QString deviceFamily(); + /** + * Try to find the device variant for the system this is running on. + */ + Q_INVOKABLE QString deviceVariant(); + /** + * Try to find out ond what kind of system this is running + */ + Q_INVOKABLE QString deviceModel(); + /** + * Calculate the device ID used in SSU requests + * @return QSystemDeviceInfo::imei(), if available, or QSystemDeviceInfo::uniqueDeviceID() + */ + Q_INVOKABLE QString deviceUid(); + + bool getValue(const QString& key, QString& value); + + private: + SsuSettings *boardMappings; + QString cachedFamily, cachedModel, cachedVariant; +}; +#endif --- libssu/ssulog.cpp +++ libssu/ssulog.cpp @@ -0,0 +1,37 @@ +/** + * @file ssulog.cpp + * @copyright 2013 Jolla Ltd. + * @author Bernd Wachter <[email protected]> + * @date 2013 + */ + +#include <QFile> +#include <QTextStream> + +#include "ssulog.h" + +SsuLog *SsuLog::ssuLog = 0; + +SsuLog *SsuLog::instance(){ + if (!ssuLog){ + ssuLog = new SsuLog(); + ssuLog->fallbackLogPath = "/tmp/ssu.log"; + } + + return ssuLog; +} + +void SsuLog::print(int priority, QString message){ + QByteArray ba = message.toUtf8(); + const char *ca = ba.constData(); + + if (sd_journal_print(LOG_INFO, "ssu: %s", ca) < 0 && fallbackLogPath != ""){ + QFile logfile; + QTextStream logstream; + logfile.setFileName(fallbackLogPath); + logfile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append); + logstream.setDevice(&logfile); + logstream << message << "\n"; + logstream.flush(); + } +} --- libssu/ssulog.h +++ libssu/ssulog.h @@ -0,0 +1,33 @@ +/** + * @file ssulog.h + * @copyright 2013 Jolla Ltd. + * @author Bernd Wachter <[email protected]> + * @date 2013 + */ + +#ifndef _SSULOG_H +#define _SSULOG_H + +#include <QObject> + +#include <systemd/sd-journal.h> + +class SsuLog { + + public: + static SsuLog *instance(); + /** + * Print a message to systemds journal, or to a text log file, if a fallback is defined + */ + void print(int priority, QString message); + + private: + SsuLog() {}; + SsuLog(const SsuLog &); // hide copy constructor + + static SsuLog *ssuLog; + QString fallbackLogPath; +}; + + +#endif --- libssu/ssusettings.cpp +++ libssu/ssusettings.cpp @@ -0,0 +1,175 @@ +/** + * @file ssusettings.cpp + * @copyright 2013 Jolla Ltd. + * @author Bernd Wachter <[email protected]> + * @date 2013 + */ + +#include <QStringList> +#include <QDirIterator> +#include <QFileInfo> +#include <QDateTime> + +#include "ssusettings.h" +#include "ssulog.h" + +SsuSettings::SsuSettings(): QSettings(){ + +} + +SsuSettings::SsuSettings(const QString &fileName, Format format, QObject *parent): + QSettings(fileName, format, parent){ +} + +SsuSettings::SsuSettings(const QString &fileName, Format format, const QString &defaultFileName, QObject *parent): + QSettings(fileName, format, parent){ + defaultSettingsFile = defaultFileName; + upgrade(); +} + +SsuSettings::SsuSettings(const QString &fileName, const QString &settingsDirectory, QObject *parent): + QSettings(fileName, QSettings::IniFormat, parent){ + settingsd = settingsDirectory; + merge(); +} + +void SsuSettings::merge(){ + if (settingsd == "") + return; + + bool skipMerge = true; + + SsuLog *ssuLog = SsuLog::instance(); + + QDirIterator it(settingsd, QDir::AllEntries|QDir::NoDot|QDir::NoDotDot, QDirIterator::FollowSymlinks); + QStringList settingsFiles; + + QFileInfo oldSettingsInfo(fileName()); + + while (it.hasNext()){ + QString f = it.next(); + + settingsFiles.append(it.filePath()); + + QFileInfo info(it.filePath()); + if (info.lastModified() >= oldSettingsInfo.lastModified()) + skipMerge = false; + } + + if (skipMerge){ + ssuLog->print(LOG_DEBUG, QString("Configuration file is newer than all config.d files, skipping merge")); + return; + } + + settingsFiles.sort(); + + foreach (const QString &settingsFile, settingsFiles){ + QSettings settings(settingsFile, QSettings::IniFormat); + QStringList groups = settings.childGroups(); + + ssuLog->print(LOG_DEBUG, QString("Merging %1 into %2") + .arg(settingsFile) + .arg(fileName())); + + foreach (const QString &group, groups){ + beginGroup(group); + settings.beginGroup(group); + + QStringList keys = settings.allKeys(); + foreach (const QString &key, keys){ + setValue(key, settings.value(key)); + } + + settings.endGroup(); + endGroup(); + } + sync(); + } +} + +void SsuSettings::upgrade(){ + int configVersion=0; + int defaultConfigVersion=0; + + SsuLog *ssuLog = SsuLog::instance(); + + if (defaultSettingsFile == "") + return; + + QSettings defaultSettings(defaultSettingsFile, QSettings::IniFormat); + + if (contains("configVersion")) + configVersion = value("configVersion").toInt(); + if (defaultSettings.contains("configVersion")) + defaultConfigVersion = defaultSettings.value("configVersion").toInt(); + + if (configVersion < defaultConfigVersion){ + ssuLog->print(LOG_DEBUG, QString("Configuration is outdated, updating from %1 to %2") + .arg(configVersion) + .arg(defaultConfigVersion)); + + for (int i=configVersion+1;i<=defaultConfigVersion;i++){ + QStringList defaultKeys; + QString currentSection = QString("%1/").arg(i); + + ssuLog->print(LOG_DEBUG, QString("Processing configuration version %1").arg(i)); + defaultSettings.beginGroup(currentSection); + defaultKeys = defaultSettings.allKeys(); + defaultSettings.endGroup(); + foreach (const QString &key, defaultKeys){ + // Default keys support both commands and new keys + if (key.compare("cmd-remove", Qt::CaseSensitive) == 0){ + // Remove keys listed in value as string list + QStringList oldKeys = defaultSettings.value(currentSection + key).toStringList(); + foreach (const QString &oldKey, oldKeys){ + if (contains(oldKey)){ + remove(oldKey); + ssuLog->print(LOG_DEBUG, QString("Removing old key: %1").arg(oldKey)); + } + } + } else if (!contains(key)){ + // Add new keys.. + setValue(key, defaultSettings.value(currentSection + key)); + ssuLog->print(LOG_DEBUG, QString("Adding key: %1").arg(key)); + } else { + // ... or update the ones where default values has changed. + QVariant oldValue; + + // check if an old value exists in an older configuration version + for (int j=i-1;j>0;j--){ + if (defaultSettings.contains(QString("%1/").arg(j)+key)){ + oldValue = defaultSettings.value(QString("%1/").arg(j)+key); + break; + } + } + + // skip updating if there is no old value, since we can't check if the + // default value has changed + if (oldValue.isNull()) + continue; + + QVariant newValue = defaultSettings.value(currentSection + key); + if (oldValue == newValue){ + // old and new value match, no need to do anything, apart from beating the + // person who added a useless key + continue; + } else { + // default value has changed, so check if the configuration is still + // using the old default value... + QVariant currentValue = value(key); + // testcase: handles properly default update of thing with changed value in ssu.ini? + if (currentValue == oldValue){ + // ...and update the key if it does + setValue(key, newValue); + ssuLog->print(LOG_DEBUG, QString("Updating %1 from %2 to %3") + .arg(key) + .arg(currentValue.toString()) + .arg(newValue.toString())); + } + } + } + } + setValue("configVersion", i); + } + } +} --- libssu/ssusettings.h +++ libssu/ssusettings.h @@ -0,0 +1,37 @@ +/** + * @file ssusettings.h + * @copyright 2013 Jolla Ltd. + * @author Bernd Wachter <[email protected]> + * @date 2013 + */ + +#ifndef _SSUSETTINGS_H +#define _SSUSETTINGS_H + +#include <QSettings> + +class SsuSettings: public QSettings { + Q_OBJECT + + public: + SsuSettings(); + SsuSettings(const QString &fileName, Format format, QObject *parent=0); + /** + * Initialize the settings object with a defaults settings file, resulting in + * update to the configuration file if needed + */ + SsuSettings(const QString &fileName, Format format, const QString &defaultFileName, QObject *parent=0); + /** + * Initialize the settings object from a settings.d structure, if needed. Only INI + * style settings are supported in this mode. + */ + SsuSettings(const QString &fileName, const QString &settingsDirectory, QObject *parent=0); + + private: + QString defaultSettingsFile, settingsd; + void merge(); + void upgrade(); + +}; + +#endif --- libssu/ssuvariables.cpp +++ libssu/ssuvariables.cpp @@ -0,0 +1,104 @@ +/** + * @file ssuvariables.cpp + * @copyright 2013 Jolla Ltd. + * @author Bernd Wachter <[email protected]> + * @date 2013 + */ + +#include <QStringList> +#include <QRegExp> +#include <QStringRef> + +#include "ssuvariables.h" + +SsuVariables::SsuVariables(): QObject() { + +} + +void SsuVariables::resolveSection(QSettings *settings, QString section, QHash<QString, QString> *storageHash){ + QStringList repoVariables; + + settings->beginGroup(section); + repoVariables = settings->allKeys(); + foreach (const QString &key, repoVariables){ + storageHash->insert(key, settings->value(key).toString()); + } + settings->endGroup(); +} + +QString SsuVariables::resolveString(QString pattern, QHash<QString, QString> *variables){ + QRegExp regex("%\\\([^%]*\\\)", Qt::CaseSensitive, QRegExp::RegExp2); + regex.setMinimal(true); + + int pos = 0; + while ((pos = regex.indexIn(pattern, pos)) != -1){ + QString match = regex.cap(0); + + if (match.contains(":")){ + // variable is special, resolve before replacing + QString variable = resolveVariable(match, variables); + pattern.replace(match, variable); + pos += variable.length(); + } else { + // look up variable name in hashmap, and replace it with stored value, + // if found, or "" + QString variableName = match; + variableName.remove(0,2); + variableName.chop(1); + if (variables->contains(variableName)){ + pattern.replace(match, variables->value(variableName)); + pos += variables->value(variableName).length(); + } else + pattern.replace(match, ""); + } + } + + // check if string still contains variables, and recurse + if (regex.indexIn(pattern, 0) != -1) + pattern = resolveString(pattern, variables); + + return pattern; +} + +QString SsuVariables::resolveVariable(QString variable, QHash<QString, QString> *variables){ + QString variableValue = ""; + + if (variable.endsWith(")")) + variable.chop(1); + if (variable.startsWith("%(")) + variable.remove(0,2); + + // hunt for your colon + int magic = variable.indexOf(":"); + + // seems you misplaced your colon + if (magic == -1) return variable; + + QStringRef variableName(&variable, 0, magic); + QStringRef variableSub(&variable, magic + 2, variable.length() - magic - 2); + + // Fill in variable value for later tests, if it exists + if (variables->contains(variableName.toString())) + variableValue = variables->value(variableName.toString()); + + // find the operator who's after your colon + QChar op; + if (variable.length() >= magic +1) + op = variable.at(magic + 1); + + switch(op.toAscii()){ + case '-': + // substitute default value if variable is empty + if (variableValue == "") + return variableSub.toString(); + break; + case '+': + // substitute default value if variable is not empty + if (variableValue != "") + return variableSub.toString(); + break; + } + + // no proper substitution found -> return default value + return variableValue; +} --- libssu/ssuvariables.h +++ libssu/ssuvariables.h @@ -0,0 +1,36 @@ +/** + * @file ssuvariables.h + * @copyright 2013 Jolla Ltd. + * @author Bernd Wachter <[email protected]> + * @date 2013 + */ + +#ifndef _SSUVARIABLES_H +#define _SSUVARIABLES_H + +#include <QObject> +#include <QSettings> +#include <QHash> + +class SsuVariables: public QObject { + Q_OBJECT + + public: + SsuVariables(); + /** + * Look up all variables in the specified configuration file section, + * run them through the variable expander, and add them to the supplied + * QHash + */ + void resolveSection(QSettings *settings, QString section, QHash<QString, QString> *storageHash); + /** + * Resolve a whole string, containing several variables. Variables inside variables are allowed + */ + QString resolveString(QString pattern, QHash<QString, QString> *variables); + /** + * Resolve variables; variable can be passed as %(var) or var + */ + QString resolveVariable(QString variable, QHash<QString, QString> *variables); +}; + +#endif --- repos.ini +++ repos.ini @@ -16,6 +16,13 @@ # %(adaptation) The device specific adaptation, for example 'n900' or 'n950-n9' # # +# Variables may contain other variables. Resolving is done recursively from the +# innermost variable. +# +# Basic variable substitution is supported: +# %(foo:+bar) -- expands to "" if foo is set, bar otherwise +# %(foo:-bar) -- expands to %(foo) if foo is set, bar otherwise +# # Repository lookup will happen based on the 'repo' parameter in repository # URLs. For RnD repositories order will be <flavour> -> rnd -> all, for # release repositories release -> all. --- rndssucli/rndssucli.cpp +++ rndssucli/rndssucli.cpp @@ -6,6 +6,9 @@ */ #include <QCoreApplication> + +#include <termios.h> + #include "rndssucli.h" RndSsuCli::RndSsuCli(): QObject(){ @@ -28,48 +31,132 @@ } -void RndSsuCli::run(){ +void RndSsuCli::optFlavour(QString newFlavour){ QTextStream qout(stdout); - QStringList arguments = QCoreApplication::arguments(); + if (newFlavour != ""){ + qout << "Changing flavour from " << ssu.flavour() + << " to " << newFlavour << endl; + ssu.setFlavour(newFlavour); + } else + qout << "Device flavour is currently: " << ssu.flavour() << endl; + QCoreApplication::exit(0); +} - if (arguments.count() != 2){ - usage(); - return; +void RndSsuCli::optRegister(){ + /* + * register a new device + */ + + QString username, password; + QTextStream qin(stdin); + QTextStream qout(stdout); + + struct termios termNew, termOld; + + qout << "Username: " << flush; + username = qin.readLine(); + + tcgetattr(STDIN_FILENO, &termNew); + termOld = termNew; + termNew.c_lflag &= ~ECHO; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &termNew) == -1) + qout << "WARNING: Unable to disable echo on your terminal, password will echo!" << endl; + + qout << "Password: " << flush; + password = qin.readLine(); + + tcsetattr(STDIN_FILENO, TCSANOW, &termOld); + + ssu.sendRegistration(username, password); +} + +void RndSsuCli::optResolve(){ + /* + * resolve URL and print + * TODO: arguments + */ + + QString repo; + bool rndRepo=false; +/* + repo = arguments.at(2); + + if (arguments.count() >= 3){ + //qout << (arguments.at(3).compare("false")||arguments.at(3).compare("0")); + qout << (arguments.at(3).compare("false")); } - if (arguments.at(1) == "register"){ - QString username, password; - QTextStream qin(stdin); - - qout << "Username: " << flush; - username = qin.readLine(); - qout << "Password: " << flush; - password = qin.readLine(); - - ssu.sendRegistration(username, password); - } else if (arguments.at(1) == "update"){ - if (!ssu.isRegistered()){ + qout << ssu.repoUrl(arguments.at(2)); + QCoreApplication::exit(1); +*/ + + QCoreApplication::exit(1); +} + +void RndSsuCli::optStatus(){ + QTextStream qout(stdout); + + /* + * print device information and registration status + */ + qout << "Device registration status: " + << (ssu.isRegistered() ? "registered" : "not registered") << endl; + qout << "Device family: " << ssu.deviceFamily() << endl; + qout << "Device model: " << ssu.deviceModel() << endl; + qout << "Device UID: " << ssu.deviceUid() << endl; + + QCoreApplication::exit(0); +} + +void RndSsuCli::optUpdate(bool force){ + QTextStream qout(stdout); + /* + * update the credentials + * optional argument: -f + */ + if (!ssu.isRegistered()){ qout << "Device is not registered, can't update credentials" << endl; QCoreApplication::exit(1); - } else { - ssu.updateCredentials(); - } - } else if (arguments.at(1) == "status"){ - qout << "Device registration status: " - << (ssu.isRegistered() ? "registered" : "not registered") << endl; - qout << "Device family: " << ssu.deviceFamily() << endl; - qout << "Device model: " << ssu.deviceModel() << endl; - qout << "Device UID: " << ssu.deviceUid() << endl; - QCoreApplication::exit(1); } else + ssu.updateCredentials(force); + + QCoreApplication::exit(0); +} + +void RndSsuCli::run(){ + QTextStream qout(stdout); + + QStringList arguments = QCoreApplication::arguments(); + + // make sure there's a first argument to parse + if (arguments.count() < 2){ usage(); + 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(); + } else + usage(); } void RndSsuCli::usage(){ QTextStream qout(stdout); - qout << "Usage: rndssu register|update|status" << endl; + qout << "Usage: rndssu flavour [flavour]|register|update [-f]|status" << endl; QCoreApplication::exit(1); } --- rndssucli/rndssucli.h +++ rndssucli/rndssucli.h @@ -27,6 +27,12 @@ Ssu ssu; QSettings settings; void usage(); + void optFlavour(QString newFlavour=""); + void optRegister(); + void optResolve(); + void optStatus(); + void optUpdate(bool force=false); + private slots: void handleResponse(); --- rpm/ssu.changes +++ rpm/ssu.changes @@ -1,3 +1,23 @@ +* Fri Mar 22 2013 Bernd Wachter <[email protected]> - 0.25 +- Add variable substitution + +* Thu Mar 21 2013 Bernd Wachter <[email protected]> - 0.24 +- Add support for arbitrary variables in flavour groups +- Split off device info and settings functionality +- Add support for board-mappings.d + +* Mon Mar 11 2013 Bernd Wachter <[email protected]> - 0.23 +- Add force option to rndssu update + +* Mon Mar 11 2013 Bernd Wachter <[email protected]> - 0.22 +- Add systemd logging to core library +- Move zypper plugin logging to core library +- Fix bug where resolverplugin does not return on credentials update + +* Mon Mar 11 2013 Bernd Wachter <[email protected]> - 0.21 +- Reduce time before credentials get refreshed +- Add logging for url resolver + * Tue Jan 29 2013 Bernd Wachter <[email protected]> - 0.20 - Move ssu resolver log to systemd journal, with fallback in /tmp/ssu.log --- rpm/ssu.spec +++ rpm/ssu.spec @@ -1,9 +1,9 @@ Name: ssu -Version: 0.20 +Version: 0.25 Release: 1 Summary: SSU enabler for RND Group: System/Base -License: Proprietary +License: GPLv2 Source0: %{name}-%{version}.tar.gz URL: https://github.com/nemomobile/ssu BuildRequires: pkgconfig(QtCore) @@ -93,7 +93,7 @@ %prep -%setup -q +%setup -q -n %{name}-%{version} %build --- ssuurlresolver/ssuurlresolver.cpp +++ ssuurlresolver/ssuurlresolver.cpp @@ -9,6 +9,7 @@ #include <systemd/sd-journal.h> #include "ssuurlresolver.h" +#include "ssulog.h" SsuUrlResolver::SsuUrlResolver(): QObject(){ QObject::connect(this,SIGNAL(done()), @@ -16,31 +17,41 @@ Qt::QueuedConnection); } -void SsuUrlResolver::printJournal(int priority, QString message){ - QByteArray ba = message.toUtf8(); - const char *ca = ba.constData(); - - if (sd_journal_print(LOG_INFO, "ssu: %s", ca) < 0){ - QFile logfile; - QTextStream logstream; - logfile.setFileName("/tmp/ssu.log"); - logfile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append); - logstream.setDevice(&logfile); - logstream << message << "\n"; - logstream.flush(); - } +bool SsuUrlResolver::writeCredentials(QString filePath, QString credentialsScope){ + QFile credentialsFile(filePath); + QPair<QString, QString> credentials = ssu.credentials(credentialsScope); + SsuLog *ssuLog = SsuLog::instance(); + + if (credentials.first == "" || credentials.second == ""){ + ssuLog->print(LOG_WARNING, "Returned credentials are empty, skip writing"); + return false; + } + + if (!credentialsFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)){ + ssuLog->print(LOG_WARNING, "Unable to open credentials file for writing"); + return false; + } + + QTextStream out(&credentialsFile); + out << "[" << ssu.credentialsUrl(credentialsScope) << "]\n"; + out << "username=" << credentials.first << "\n"; + out << "password=" << credentials.second << "\n"; + out.flush(); + credentialsFile.close(); + return true; } void SsuUrlResolver::run(){ QHash<QString, QString> repoParameters; QString resolvedUrl, repo; bool isRnd = false; + SsuLog *ssuLog = SsuLog::instance(); PluginFrame in(std::cin); if (in.headerEmpty()){ // FIXME, do something; we need at least repo header - printJournal(LOG_WARNING, "Received empty header list. Most likely your ssu setup is broken"); + ssuLog->print(LOG_WARNING, "Received empty header list. Most likely your ssu setup is broken"); } PluginFrame::HeaderListIterator it; @@ -77,16 +88,25 @@ if (!ssu.useSslVerify()) headerList.append("ssl_verify=no"); - if (isRnd){ + if (ssu.isRegistered()){ SignalWait w; connect(&ssu, SIGNAL(done()), &w, SLOT(finished())); ssu.updateCredentials(); w.sleep(); - } + + // error can be found in ssu.log, so just exit + // TODO: figure out if there's better eror handling for + // zypper plugins than 'blow up' + if (ssu.error()){ + emit done(); + return; + } + } else + ssuLog->print(LOG_DEBUG, "Device not registered -- skipping credential update"); // TODO: check for credentials scope required for repository; check if the file exists; // compare with configuration, and dump credentials to file if necessary - printJournal(LOG_DEBUG, QString("Requesting credentials for '%1' with RND status %2...").arg(repo).arg(isRnd)); + ssuLog->print(LOG_DEBUG, QString("Requesting credentials for '%1' with RND status %2...").arg(repo).arg(isRnd)); QString credentialsScope = ssu.credentialsScope(repo, isRnd); if (!credentialsScope.isEmpty()){ headerList.append(QString("credentials=%1").arg(credentialsScope)); @@ -94,17 +114,10 @@ QFileInfo credentialsFileInfo("/etc/zypp/credentials.d/" + credentialsScope); if (!credentialsFileInfo.exists() || credentialsFileInfo.lastModified() <= ssu.lastCredentialsUpdate()){ - QFile credentialsFile(credentialsFileInfo.filePath()); - credentialsFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate); - QTextStream out(&credentialsFile); - QPair<QString, QString> credentials = ssu.credentials(credentialsScope); - out << "[" << ssu.credentialsUrl(credentialsScope) << "]\n"; - out << "username=" << credentials.first << "\n"; - out << "password=" << credentials.second << "\n"; - out.flush(); - credentialsFile.close(); + writeCredentials(credentialsFileInfo.filePath(), credentialsScope); } - } + } else + ssuLog->print(LOG_DEBUG, "Skipping credential update due to missing credentials scope"); if (headerList.isEmpty()){ resolvedUrl = ssu.repoUrl(repo, isRnd, repoParameters); @@ -114,7 +127,9 @@ .arg(headerList.join("&")); } - printJournal(LOG_INFO, QString("%1 resolved to %2").arg(repo).arg(resolvedUrl)); + // TODO, we should bail out here if the configuration specifies that the repo + // is protected, but device is not registered and/or we don't have credentials + ssuLog->print(LOG_INFO, QString("%1 resolved to %2").arg(repo).arg(resolvedUrl)); PluginFrame out("RESOLVEDURL"); out.setBody(resolvedUrl.toStdString()); --- ssuurlresolver/ssuurlresolver.h +++ ssuurlresolver/ssuurlresolver.h @@ -53,6 +53,7 @@ private: Ssu ssu; void printJournal(int priority, QString message); + bool writeCredentials(QString filePath, QString credentialsScope); public slots: void run(); --- tests/tests.pro +++ tests/tests.pro @@ -1,6 +1,6 @@ TEMPLATE = subdirs CONFIG += qt ordered coverage debug -SUBDIRS = ut_urlresolver +SUBDIRS = ut_urlresolver ut_variables !include( tests.pri ) { error("Unable to find tests include") } --- tests/tests.xml +++ tests/tests.xml @@ -8,5 +8,10 @@ <step expected_result="0">/opt/tests/ssu/ut_urlresolver</step> </case> </set> + <set name="variables" description="Test to determine if variable resolving works properly" feature="variables"> + <case name="ut_variables" type="Functional" description="Variable resolver tests" timeout="1000" subfeature=""> + <step expected_result="0">/opt/tests/ssu/ut_variables</step> + </case> + </set> </suite> </testdefinition> --- tests/ut_variables +++ tests/ut_variables +(directory) --- tests/ut_variables/main.cpp +++ tests/ut_variables/main.cpp @@ -0,0 +1,19 @@ +/** + * @file main.cpp + * @copyright 2012 Jolla Ltd. + * @author Bernd Wachter <[email protected]> + * @date 2012 + */ + +#include <QtTest/QtTest> + +#include "variablestest.h" + +int main(int argc, char **argv){ + VariablesTest variablesTest; + + if (QTest::qExec(&variablesTest, argc, argv)) + return 1; + + return 0; +} --- tests/ut_variables/ut_variables.pro +++ tests/ut_variables/ut_variables.pro @@ -0,0 +1,17 @@ +HEADERS = variablestest.h +SOURCES = main.cpp \ + variablestest.cpp +TEMPLATE = app +TARGET = ut_variables +LIBS += -lssu +CONFIG -= app_bundle +CONFIG += console qtestlib +QT -= gui +QT += network testlib + +!include( ../tests.pri ) { error("Unable to find tests include") } + +unix:target.path = $${PREFIX}/$$TESTS_PATH +INSTALLS += target + +!include( ../../buildpath.pri ) { error("Unable to find build path specification") } --- tests/ut_variables/variablestest.cpp +++ tests/ut_variables/variablestest.cpp @@ -0,0 +1,63 @@ +/** + * @file variablestest.cpp + * @copyright 2013 Jolla Ltd. + * @author Bernd Wachter <[email protected]> + * @date 2013 + */ + +#include "variablestest.h" + +void VariablesTest::initTestCase(){ + variables.insert("packagesDomain", "packages.example.com"); + variables.insert("releaseDomain", "releases.example.com"); + variables.insert("rndProtocol", "https"); + variables.insert("release", "devel"); + variables.insert("arch", "armv8"); + variables.insert("flavourName", "flavour"); + + urls.insert("http://%(packagesDomain)/releases/%(release)/jolla/%(arch)/", + "http://packages.example.com/releases/devel/jolla/armv8/"); + urls.insert("%(rndProtocol)://%(releaseDomain)/nemo/%(release)-%(flavourName)/platform/%(arch)/", + "https://releases.example.com/nemo/devel-flavour/platform/armv8/"); + // test missing operator, which should fall back to just variable value + urls.insert("%(rndProtocol)://%(unsetDomain:)/nemo/%(release)-%(flavourName)/platform/%(arch)/", + "https:///nemo/devel-flavour/platform/armv8/"); + urls.insert("%(rndProtocol)://%(releaseDomain:)/nemo/%(release)-%(flavourName)/platform/%(arch)/", + "https://releases.example.com/nemo/devel-flavour/platform/armv8/"); + urls.insert("%(rndProtocol)://%(releaseDomain:unset.example.com)/nemo/%(release)-%(flavourName)/platform/%(arch)/", + "https://releases.example.com/nemo/devel-flavour/platform/armv8/"); + // check if :- works + urls.insert("%(rndProtocol)://%(releaseDomain:-unset.example.com)/nemo/%(release)-%(flavourName)/platform/%(arch)/", + "https://releases.example.com/nemo/devel-flavour/platform/armv8/"); + urls.insert("%(rndProtocol)://%(unsetDomain:-unset.example.com)/nemo/%(release)-%(flavourName)/platform/%(arch)/", + "https://unset.example.com/nemo/devel-flavour/platform/armv8/"); + // test with empty replacement pattern + urls.insert("%(rndProtocol)://%(unsetDomain:-)/nemo/%(release)-%(flavourName)/platform/%(arch)/", + "https:///nemo/devel-flavour/platform/armv8/"); + // check if :+ works + // substitution of variable with set.example.com + urls.insert("%(rndProtocol)://%(releaseDomain:+set.example.com)/nemo/%(release)-%(flavourName)/platform/%(arch)/", + "https://set.example.com/nemo/devel-flavour/platform/armv8/"); + // substitution of variable with variable + /set + urls.insert("%(rndProtocol)://%(releaseDomain:+%(releaseDomain)/set)/nemo/%(release)-%(flavourName)/platform/%(arch)/", + "https://releases.example.com/set/nemo/devel-flavour/platform/armv8/"); + // substitution of variable with empty variable + /set -- should substitute to "" + urls.insert("%(rndProtocol)://%(unsetDomain:+%(unsetDomain)/set)/nemo/%(release)-%(flavourName)/platform/%(arch)/", + "https:///nemo/devel-flavour/platform/armv8/"); + +} + +void VariablesTest::cleanupTestCase(){ + +} + +void VariablesTest::checkResolveString(){ + QHashIterator<QString, QString> i(urls); + + while (i.hasNext()){ + i.next(); + QString result = var.resolveString(i.key(), &variables); + qDebug() << i.key() << " resolved to " << result; + QCOMPARE(result, i.value()); + } +} --- tests/ut_variables/variablestest.h +++ tests/ut_variables/variablestest.h @@ -0,0 +1,33 @@ +/** + * @file variablestest.h + * @copyright 2013 Jolla Ltd. + * @author Bernd Wachter <[email protected]> + * @date 2013 + */ + +#ifndef _VARIABLESTEST_H +#define _VARIABLESTEST_H + +#include <QObject> +#include <QtTest/QtTest> +#include <QHash> + +#include <ssu.h> +#include <ssuvariables.h> + +class VariablesTest: public QObject { + Q_OBJECT + + private slots: + void initTestCase(); + void cleanupTestCase(); + void checkResolveString(); + + + private: + Ssu ssu; + SsuVariables var; + QHash <QString, QString> variables, urls; +}; + +#endif
