From 844f7a53a88019f1c3e8acd0f5a31c8aaa743d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 26 Jun 2018 19:57:47 +0200 Subject: [PATCH] Add user database rotation if the database is broken. --- libnymea-core/nymeacore.cpp | 2 +- libnymea-core/usermanager.cpp | 66 ++++++++++++-- libnymea-core/usermanager.h | 5 +- tests/auto/auto.pro | 1 + tests/auto/userloading/testuserloading.cpp | 85 +++++++++++++++++++ tests/auto/userloading/user-db-broken.sqlite | Bin 0 -> 6144 bytes tests/auto/userloading/userloading.pro | 6 ++ tests/auto/userloading/userloading.qrc | 5 ++ 8 files changed, 158 insertions(+), 12 deletions(-) create mode 100644 tests/auto/userloading/testuserloading.cpp create mode 100755 tests/auto/userloading/user-db-broken.sqlite create mode 100644 tests/auto/userloading/userloading.pro create mode 100644 tests/auto/userloading/userloading.qrc diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index b3d67e64..38629b75 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -514,7 +514,7 @@ void NymeaCore::init() { m_ruleEngine = new RuleEngine(this); qCDebug(dcApplication()) << "Creating User Manager"; - m_userManager = new UserManager(this); + m_userManager = new UserManager(NymeaSettings::settingsPath() + "/user-db.sqlite", this); qCDebug(dcApplication) << "Creating Server Manager"; m_serverManager = new ServerManager(m_configuration, this); diff --git a/libnymea-core/usermanager.cpp b/libnymea-core/usermanager.cpp index c7304c82..fdfca4c5 100644 --- a/libnymea-core/usermanager.cpp +++ b/libnymea-core/usermanager.cpp @@ -35,16 +35,28 @@ namespace nymeaserver { -UserManager::UserManager(QObject *parent) : QObject(parent) +UserManager::UserManager(const QString &dbName, QObject *parent): + QObject(parent) { m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), "users"); - m_db.setDatabaseName(NymeaSettings::settingsPath() + "/user-db.sqlite"); + m_db.setDatabaseName(dbName); - if (!m_db.open()) { - qCWarning(dcUserManager) << "Error opening users database:" << m_db.lastError().driverText(); - return; + qCDebug(dcUserManager()) << "Opening user database" << m_db.databaseName(); + + if (!m_db.isValid()) { + qCWarning(dcUserManager()) << "The database is not valid:" << m_db.lastError().driverText() << m_db.lastError().databaseText(); + rotate(m_db.databaseName()); + } + + if (!initDB()) { + qCWarning(dcUserManager()) << "Error initializing user database. Trying to correct it."; + if (QFileInfo(m_db.databaseName()).exists()) { + rotate(m_db.databaseName()); + if (!initDB()) { + qCWarning(dcLogEngine()) << "Error fixing user database. Giving up. Users can't be stored."; + } + } } - initDB(); m_pushButtonDBusService = new PushButtonDBusService("/io/guh/nymead/UserManager", this); connect(m_pushButtonDBusService, &PushButtonDBusService::pushButtonPressed, this, &UserManager::onPushButtonPressed); @@ -286,13 +298,49 @@ bool UserManager::verifyToken(const QByteArray &token) return true; } -void UserManager::initDB() +bool UserManager::initDB() { - if (!m_db.tables().contains("users")) { - m_db.exec("CREATE TABLE users (username VARCHAR(40) UNIQUE, password VARCHAR(100), salt VARCHAR(100));"); + m_db.close(); + + if (!m_db.open()) { + qCWarning(dcUserManager()) << "Can't open user database. Init failed."; + return false; } + + if (!m_db.tables().contains("users")) { + qCDebug(dcUserManager()) << "Empty user database. Setting up metadata..."; + m_db.exec("CREATE TABLE users (username VARCHAR(40) UNIQUE, password VARCHAR(100), salt VARCHAR(100));"); + if (m_db.lastError().isValid()) { + qCWarning(dcUserManager) << "Error initualizing user database. Driver error:" << m_db.lastError().driverText() << "Database error:" << m_db.lastError().databaseText(); + return false; + } + } + if (!m_db.tables().contains("tokens")) { + qCDebug(dcUserManager()) << "Empty user database. Setting up metadata..."; m_db.exec("CREATE TABLE tokens (id VARCHAR(40) UNIQUE, username VARCHAR(40), token VARCHAR(100) UNIQUE, creationdate DATETIME, devicename VARCHAR(40));"); + if (m_db.lastError().isValid()) { + qCWarning(dcUserManager()) << "Error initualizing user database. Driver error:" << m_db.lastError().driverText() << "Database error:" << m_db.lastError().databaseText(); + return false; + } + } + + qCDebug(dcUserManager()) << "User database initialized successfully."; + return true; +} + +void UserManager::rotate(const QString &dbName) +{ + int index = 1; + while (QFileInfo(QString("%1.%2").arg(dbName).arg(index)).exists()) { + index++; + } + qCDebug(dcUserManager()) << "Backing up old database file to" << QString("%1.%2").arg(dbName).arg(index); + QFile f(dbName); + if (!f.rename(QString("%1.%2").arg(dbName).arg(index))) { + qCWarning(dcUserManager()) << "Error backing up old database."; + } else { + qCDebug(dcUserManager()) << "Successfully moved old database"; } } diff --git a/libnymea-core/usermanager.h b/libnymea-core/usermanager.h index 1041978f..116c6841 100644 --- a/libnymea-core/usermanager.h +++ b/libnymea-core/usermanager.h @@ -45,7 +45,7 @@ public: UserErrorPermissionDenied }; - explicit UserManager(QObject *parent = 0); + explicit UserManager(const QString &dbName, QObject *parent = 0); bool initRequired() const; QStringList users() const; @@ -68,7 +68,8 @@ signals: void pushButtonAuthFinished(int transactionId, bool success, const QByteArray &token); private: - void initDB(); + bool initDB(); + void rotate(const QString &dbName); bool validateUsername(const QString &username) const; bool validateToken(const QByteArray &token) const; diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 41d560bd..5a06bc65 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -21,3 +21,4 @@ SUBDIRS = versioning \ #coap \ # temporary removed until fixed configurations \ timemanager \ + userloading \ diff --git a/tests/auto/userloading/testuserloading.cpp b/tests/auto/userloading/testuserloading.cpp new file mode 100644 index 00000000..2f4498cc --- /dev/null +++ b/tests/auto/userloading/testuserloading.cpp @@ -0,0 +1,85 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2018 Simon Stürz * + * * + * This file is part of nymea. * + * * + * nymea is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + ** + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with nymea. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include "logging/logengine.h" +#include "logging/logvaluetool.h" + +#include "usermanager.h" + +using namespace nymeaserver; + +class TestUserLoading: public QObject +{ + Q_OBJECT +public: + TestUserLoading(QObject* parent = nullptr); + +protected slots: + void initTestCase(); + +private slots: + void testLogfileRotation(); + +}; + +TestUserLoading::TestUserLoading(QObject *parent): QObject(parent) +{ + + Q_INIT_RESOURCE(userloading); +} + +void TestUserLoading::initTestCase() +{ + // Important for settings + QCoreApplication::instance()->setOrganizationName("nymea-test"); +} + +void TestUserLoading::testLogfileRotation() +{ + // Create UserManager with log db from resource file + QString temporaryDbName = "/tmp/nymea-test/user-db-broken.sqlite"; + QString rotatedDbName = "/tmp/nymea-test/user-db-broken.sqlite.1"; + + // Remove the files if there are some left + if (QFile::exists(temporaryDbName)) + QVERIFY(QFile(temporaryDbName).remove()); + + if (QFile::exists(rotatedDbName)) + QVERIFY(QFile(rotatedDbName).remove()); + + // Copy broken user db from resources to default settings path and set permissions + qDebug() << "Copy broken user db to" << temporaryDbName; + QVERIFY(QFile::copy(":/user-db-broken.sqlite", temporaryDbName)); + QVERIFY(QFile::setPermissions(temporaryDbName, QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::ReadOther)); + + QVERIFY(!QFile::exists(rotatedDbName)); + UserManager *userManager = new UserManager(temporaryDbName, this); + QVERIFY(QFile::exists(rotatedDbName)); + + delete userManager; + + QVERIFY(QFile(temporaryDbName).remove()); + QVERIFY(QFile(rotatedDbName).remove()); +} + +#include "testuserloading.moc" +QTEST_MAIN(TestUserLoading) diff --git a/tests/auto/userloading/user-db-broken.sqlite b/tests/auto/userloading/user-db-broken.sqlite new file mode 100755 index 0000000000000000000000000000000000000000..0dfe20ef682ece042d36ec931bce138e7bc7bcd7 GIT binary patch literal 6144 zcmeH|O-sWt7{}9>bt3E>b_?iXWfM2`DpF^`!4x((VO~mHBNVsRHQDq!Kb2p?597_F zP0MB%9PB9c3FPIU=B58%@{qe;HxP_GMe&pha*9l(>Bu1jA*3hSO7bd6ZLUO8Yq|Ee zrjCxkJ}SmHD(NF+JQ$z)$T%|8(Yvgq&H??u2`mqdGOkv&_X9y6Crm`oEaX{fw)?I# zaLK^A?7AfDBelRM_fEfk?euGBO`8mFJH4UXAal;*kWN|tV$oc9Gg&F4qWNDCe(H{L4rBZ4gc;VgrF~>lQx8+svd}d9AdP zA-E?mP(>E`sFoTM5C8&&Ah0i8Li`uvtidD@Pzk{MuMz + + user-db-broken.sqlite + +