core: Allow configuring DB/auth from environment
authorJanne Koschinski <janne@kuschku.de>
Tue, 8 May 2018 21:49:11 +0000 (16:49 -0500)
committerManuel Nickschas <sputnick@quassel-irc.org>
Wed, 23 May 2018 22:33:28 +0000 (00:33 +0200)
Add support for environment variables to configure database and
authenticator backends.  This improves using Quassel in a container
-based system as no configuration file needs to be written.

Toggle use of environment variables via a new CLI switch,
'--config-from-environment', which will disregard the core settings
file (if it exists) and use environment variables instead.

New environment variables:
> Backend selection
DB_BACKEND = ["SQLite", "PostgreSQL"]
AUTH_AUTHENTICATOR = ["Database", "LDAP"]
> PostgreSQL connection properties
DB_PGSQL_USERNAME
DB_PGSQL_PASSWORD
DB_PGSQL_HOSTNAME
DB_PGSQL_PORT
DB_PGSQL_DATABASE
> LDAP connection properties
AUTH_LDAP_HOSTNAME
AUTH_LDAP_PORT
AUTH_LDAP_BIND_DN
AUTH_LDAP_BIND_PASSWORD
AUTH_LDAP_BASE_DN
AUTH_LDAP_FILTER
AUTH_LDAP_UID_ATTRIBUTE

Closes GH-341.

14 files changed:
src/common/main.cpp
src/core/abstractsqlstorage.cpp
src/core/abstractsqlstorage.h
src/core/authenticator.h
src/core/core.cpp
src/core/core.h
src/core/ldapauthenticator.cpp
src/core/ldapauthenticator.h
src/core/postgresqlstorage.cpp
src/core/postgresqlstorage.h
src/core/sqlauthenticator.cpp
src/core/sqlauthenticator.h
src/core/sqlitestorage.h
src/core/storage.h

index a2cb574..79a8cfe 100644 (file)
@@ -163,6 +163,7 @@ int main(int argc, char **argv)
     cliParser->addSwitch("syslog", 0, "Log to syslog");
 #endif
     cliParser->addOption("logfile", 'l', "Log to a file", "path");
+    cliParser->addSwitch("config-from-environment", 0, "Load configuration from environment variables");
     cliParser->addOption("select-backend", 0, "Switch storage backend (migrating data if possible)", "backendidentifier");
     cliParser->addOption("select-authenticator", 0, "Select authentication backend", "authidentifier");
     cliParser->addSwitch("add-user", 0, "Starts an interactive session to add a new core user");
index cb8fbd1..9fe95b9 100644 (file)
@@ -116,9 +116,11 @@ void AbstractSqlStorage::dbConnect(QSqlDatabase &db)
 }
 
 
-Storage::State AbstractSqlStorage::init(const QVariantMap &settings)
+Storage::State AbstractSqlStorage::init(const QVariantMap &settings,
+                                        const QProcessEnvironment &environment,
+                                        bool loadFromEnvironment)
 {
-    setConnectionProperties(settings);
+    setConnectionProperties(settings, environment, loadFromEnvironment);
 
     _debug = Quassel::isOptionSet("debug");
 
@@ -192,9 +194,10 @@ QStringList AbstractSqlStorage::setupQueries()
 }
 
 
-bool AbstractSqlStorage::setup(const QVariantMap &settings)
+bool AbstractSqlStorage::setup(const QVariantMap &settings, const QProcessEnvironment &environment,
+                               bool loadFromEnvironment)
 {
-    setConnectionProperties(settings);
+    setConnectionProperties(settings, environment, loadFromEnvironment);
     QSqlDatabase db = logDb();
     if (!db.isOpen()) {
         qCritical() << "Unable to setup Logging Backend!";
index e894555..5e4c360 100644 (file)
@@ -43,8 +43,12 @@ public:
     virtual std::unique_ptr<AbstractSqlMigrationWriter> createMigrationWriter() { return {}; }
 
 public slots:
-    virtual State init(const QVariantMap &settings = QVariantMap());
-    virtual bool setup(const QVariantMap &settings = QVariantMap());
+    virtual State init(const QVariantMap &settings = QVariantMap(),
+                       const QProcessEnvironment &environment = {},
+                       bool loadFromEnvironment = false);
+    virtual bool setup(const QVariantMap &settings = QVariantMap(),
+                       const QProcessEnvironment &environment = {},
+                       bool loadFromEnvironment = false);
 
 protected:
     inline virtual void sync() {};
@@ -82,7 +86,9 @@ protected:
     virtual bool updateSchemaVersion(int newVersion) = 0;
     virtual bool setupSchemaVersion(int version) = 0;
 
-    virtual void setConnectionProperties(const QVariantMap &properties) = 0;
+    virtual void setConnectionProperties(const QVariantMap &properties,
+                                         const QProcessEnvironment &environment,
+                                         bool loadFromEnvironment) = 0;
     virtual QString driverName() = 0;
     inline virtual QString hostName() { return QString(); }
     inline virtual int port() { return -1; }
index df5d198..ebea4c9 100644 (file)
@@ -21,6 +21,7 @@
 #pragma once
 
 #include <QObject>
+#include <QProcessEnvironment>
 #include <QString>
 #include <QStringList>
 #include <QVariant>
@@ -81,13 +82,17 @@ public slots:
      *  \param settings   Hostname, port, username, password, ...
      *  \return True if and only if the authenticator provider was initialized successfully.
      */
-    virtual bool setup(const QVariantMap &settings = QVariantMap()) = 0;
+    virtual bool setup(const QVariantMap &settings = QVariantMap(),
+                       const QProcessEnvironment &environment = {},
+                       bool loadFromEnvironment = false) = 0;
 
     //! Initialize the authenticator provider
     /** \param settings   Hostname, port, username, password, ...
      *  \return the State the authenticator backend is now in (see authenticator::State)
      */
-    virtual State init(const QVariantMap &settings = QVariantMap()) = 0;
+    virtual State init(const QVariantMap &settings = QVariantMap(),
+                       const QProcessEnvironment &environment = {},
+                       bool loadFromEnvironment = false) = 0;
 
     //! Validate a username with a given password.
     /** \param user     The username to validate
index 7fbd4e7..37dfe78 100644 (file)
@@ -184,15 +184,40 @@ Core::Core()
 
 void Core::init()
 {
-    CoreSettings cs;
+    QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
+    bool config_from_environment = Quassel::isOptionSet("config-from-environment");
+
+    QString db_backend;
+    QVariantMap db_connectionProperties;
+
+    QString auth_authenticator;
+    QVariantMap auth_properties;
+
+    bool writeError = false;
+
+    if (config_from_environment) {
+        db_backend = environment.value("DB_BACKEND");
+        auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
+    } else {
+        CoreSettings cs;
+
+        QVariantMap dbsettings = cs.storageSettings().toMap();
+        db_backend = dbsettings.value("Backend").toString();
+        db_connectionProperties = dbsettings.value("ConnectionProperties").toMap();
+
+        QVariantMap authSettings = cs.authSettings().toMap();
+        auth_authenticator = authSettings.value("Authenticator", "Database").toString();
+        auth_properties = authSettings.value("AuthProperties").toMap();
+
+        writeError = !cs.isWritable();
+    }
+
     // legacy
-    QVariantMap dbsettings = cs.storageSettings().toMap();
-    _configured = initStorage(dbsettings.value("Backend").toString(), dbsettings.value("ConnectionProperties").toMap());
+    _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
 
     // Not entirely sure what is 'legacy' about the above, but it seems to be the way things work!
     if (_configured) {
-        QVariantMap authSettings = cs.authSettings().toMap();
-        initAuthenticator(authSettings.value("Authenticator", "Database").toString(), authSettings.value("AuthProperties").toMap());
+        initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
     }
 
     if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
@@ -206,20 +231,30 @@ void Core::init()
     }
 
     if (!_configured) {
-        if (_registeredStorageBackends.size() == 0) {
-            quWarning() << qPrintable(tr("Could not initialize any storage backend! Exiting..."));
-            quWarning() << qPrintable(tr("Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
-                                        "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
-                                        "to work."));
-            exit(EXIT_FAILURE); // TODO make this less brutal (especially for mono client -> popup)
-        }
-        quWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
+        if (config_from_environment) {
+            _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
+            initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
 
-        if (!cs.isWritable()) {
-            qWarning() << "Cannot write quasselcore configuration; probably a permission problem.";
-            exit(EXIT_FAILURE);
-        }
+            if (!_configured) {
+                qWarning() << "Cannot configure from environment";
+                exit(EXIT_FAILURE);
+            }
+        } else {
+            if (_registeredStorageBackends.empty()) {
+                quWarning() << qPrintable(tr("Could not initialize any storage backend! Exiting..."));
+                quWarning()
+                        << qPrintable(tr("Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
+                                         "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
+                                         "to work."));
+                exit(EXIT_FAILURE); // TODO make this less brutal (especially for mono client -> popup)
+            }
+            quWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
 
+            if (writeError) {
+                qWarning() << "Cannot write quasselcore configuration; probably a permission problem.";
+                exit(EXIT_FAILURE);
+            }
+        }
     }
 
     if (Quassel::isOptionSet("add-user")) {
@@ -314,12 +349,12 @@ QString Core::setupCore(const QString &adminUser, const QString &adminPassword,
     if (adminUser.isEmpty() || adminPassword.isEmpty()) {
         return tr("Admin user or password not set.");
     }
-    if (!(_configured = initStorage(backend, setupData, true))) {
+    if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
         return tr("Could not setup storage!");
     }
 
     quInfo() << "Selected authenticator:" << authenticator;
-    if (!(_configured = initAuthenticator(authenticator, authSetupData, true)))
+    if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true)))
     {
         return tr("Could not setup authenticator!");
     }
@@ -386,7 +421,8 @@ DeferredSharedPtr<Storage> Core::storageBackend(const QString &backendId) const
 
 // old db settings:
 // "Type" => "sqlite"
-bool Core::initStorage(const QString &backend, const QVariantMap &settings, bool setup)
+bool Core::initStorage(const QString &backend, const QVariantMap &settings,
+                       const QProcessEnvironment &environment, bool loadFromEnvironment, bool setup)
 {
     if (backend.isEmpty()) {
         quWarning() << "No storage backend selected!";
@@ -399,13 +435,13 @@ bool Core::initStorage(const QString &backend, const QVariantMap &settings, bool
         return false;
     }
 
-    Storage::State storageState = storage->init(settings);
+    Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
     switch (storageState) {
     case Storage::NeedsSetup:
         if (!setup)
             return false;  // trigger setup process
-        if (storage->setup(settings))
-            return initStorage(backend, settings, false);
+        if (storage->setup(settings, environment, loadFromEnvironment))
+            return initStorage(backend, settings, environment, loadFromEnvironment, false);
         return false;
 
     // if initialization wasn't successful, we quit to keep from coming up unconfigured
@@ -483,7 +519,9 @@ DeferredSharedPtr<Authenticator> Core::authenticator(const QString &backendId) c
 
 // FIXME: Apparently, this is the legacy way of initting storage backends?
 // If there's a not-legacy way, it should be used here
-bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings, bool setup)
+bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings,
+                             const QProcessEnvironment &environment, bool loadFromEnvironment,
+                             bool setup)
 {
     if (backend.isEmpty()) {
         quWarning() << "No authenticator selected!";
@@ -496,13 +534,13 @@ bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings
         return false;
     }
 
-    Authenticator::State authState = auth->init(settings);
+    Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
     switch (authState) {
     case Authenticator::NeedsSetup:
         if (!setup)
             return false;  // trigger setup process
-        if (auth->setup(settings))
-            return initAuthenticator(backend, settings, false);
+        if (auth->setup(settings, environment, loadFromEnvironment))
+            return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
         return false;
 
     // if initialization wasn't successful, we quit to keep from coming up unconfigured
index 0814e7c..45e1596 100644 (file)
@@ -718,8 +718,12 @@ private slots:
     void incomingConnection();
     void clientDisconnected();
 
-    bool initStorage(const QString &backend, const QVariantMap &settings, bool setup = false);
-    bool initAuthenticator(const QString &backend, const QVariantMap &settings, bool setup = false);
+    bool initStorage(const QString &backend, const QVariantMap &settings,
+                     const QProcessEnvironment &environment, bool loadFromEnvironment,
+                     bool setup = false);
+    bool initAuthenticator(const QString &backend, const QVariantMap &settings,
+                           const QProcessEnvironment &environment, bool loadFromEnvironment,
+                           bool setup = false);
 
     void socketError(QAbstractSocket::SocketError err, const QString &errorString);
     void setupClientSession(RemotePeer *, UserId);
index 772b767..620ae51 100644 (file)
@@ -100,15 +100,27 @@ QVariantList LdapAuthenticator::setupData() const
 }
 
 
-void LdapAuthenticator::setAuthProperties(const QVariantMap &properties)
+void LdapAuthenticator::setAuthProperties(const QVariantMap &properties,
+                                          const QProcessEnvironment &environment,
+                                          bool loadFromEnvironment)
 {
-    _hostName = properties["Hostname"].toString();
-    _port = properties["Port"].toInt();
-    _bindDN = properties["BindDN"].toString();
-    _bindPassword = properties["BindPassword"].toString();
-    _baseDN = properties["BaseDN"].toString();
-    _filter = properties["Filter"].toString();
-    _uidAttribute = properties["UidAttribute"].toString();
+    if (loadFromEnvironment) {
+        _hostName = environment.value("AUTH_LDAP_HOSTNAME");
+        _port = environment.value("AUTH_LDAP_PORT").toInt();
+        _bindDN = environment.value("AUTH_LDAP_BIND_DN");
+        _bindPassword = environment.value("AUTH_LDAP_BIND_PASSWORD");
+        _baseDN = environment.value("AUTH_LDAP_BASE_DN");
+        _filter = environment.value("AUTH_LDAP_FILTER");
+        _uidAttribute = environment.value("AUTH_LDAP_UID_ATTRIBUTE");
+    } else {
+        _hostName = properties["Hostname"].toString();
+        _port = properties["Port"].toInt();
+        _bindDN = properties["BindDN"].toString();
+        _bindPassword = properties["BindPassword"].toString();
+        _baseDN = properties["BaseDN"].toString();
+        _filter = properties["Filter"].toString();
+        _uidAttribute = properties["UidAttribute"].toString();
+    }
 }
 
 // TODO: this code is sufficiently general that in the future, perhaps an abstract
@@ -142,17 +154,21 @@ UserId LdapAuthenticator::validateUser(const QString &username, const QString &p
 }
 
 
-bool LdapAuthenticator::setup(const QVariantMap &settings)
+bool LdapAuthenticator::setup(const QVariantMap &settings,
+                              const QProcessEnvironment &environment,
+                              bool loadFromEnvironment)
 {
-    setAuthProperties(settings);
+    setAuthProperties(settings, environment, loadFromEnvironment);
     bool status = ldapConnect();
     return status;
 }
 
 
-Authenticator::State LdapAuthenticator::init(const QVariantMap &settings)
+Authenticator::State LdapAuthenticator::init(const QVariantMap &settings,
+                                             const QProcessEnvironment &environment,
+                                             bool loadFromEnvironment)
 {
-    setAuthProperties(settings);
+    setAuthProperties(settings, environment, loadFromEnvironment);
 
     bool status = ldapConnect();
     if (!status) {
index 1bff10f..1b0b639 100644 (file)
@@ -63,12 +63,15 @@ public slots:
 
     bool canChangePassword() const override { return false; }
 
-    bool setup(const QVariantMap &settings = {}) override;
-    State init(const QVariantMap &settings = {}) override;
+    bool setup(const QVariantMap &settings, const QProcessEnvironment &environment,
+               bool loadFromEnvironment) override;
+    State init(const QVariantMap &settings, const QProcessEnvironment &environment,
+               bool loadFromEnvironment) override;
     UserId validateUser(const QString &user, const QString &password) override;
 
 protected:
-    void setAuthProperties(const QVariantMap &properties);
+    void setAuthProperties(const QVariantMap &properties, const QProcessEnvironment &environment,
+                           bool loadFromEnvironment);
     bool ldapConnect();
     void ldapDisconnect();
     bool ldapAuth(const QString &username, const QString &password);
index a6f2a72..5bad758 100644 (file)
@@ -47,7 +47,7 @@ std::unique_ptr<AbstractSqlMigrationWriter> PostgreSqlStorage::createMigrationWr
     properties["Hostname"] = _hostName;
     properties["Port"] = _port;
     properties["Database"] = _databaseName;
-    writer->setConnectionProperties(properties);
+    writer->setConnectionProperties(properties, {}, false);
     return std::unique_ptr<AbstractSqlMigrationWriter>{writer};
 }
 
@@ -147,13 +147,23 @@ bool PostgreSqlStorage::initDbSession(QSqlDatabase &db)
 }
 
 
-void PostgreSqlStorage::setConnectionProperties(const QVariantMap &properties)
+void PostgreSqlStorage::setConnectionProperties(const QVariantMap &properties,
+                                                const QProcessEnvironment &environment,
+                                                bool loadFromEnvironment)
 {
-    _userName = properties["Username"].toString();
-    _password = properties["Password"].toString();
-    _hostName = properties["Hostname"].toString();
-    _port = properties["Port"].toInt();
-    _databaseName = properties["Database"].toString();
+    if (loadFromEnvironment) {
+        _userName = environment.value("DB_PGSQL_USERNAME");
+        _password = environment.value("DB_PGSQL_PASSWORD");
+        _hostName = environment.value("DB_PGSQL_HOSTNAME");
+        _port = environment.value("DB_PGSQL_PORT").toInt();
+        _databaseName = environment.value("DB_PGSQL_DATABASE");
+    } else {
+        _userName = properties["Username"].toString();
+        _password = properties["Password"].toString();
+        _hostName = properties["Hostname"].toString();
+        _port = properties["Port"].toInt();
+        _databaseName = properties["Database"].toString();
+    }
 }
 
 
index 1c6afba..f27675d 100644 (file)
@@ -124,7 +124,9 @@ public slots:
 
 protected:
     bool initDbSession(QSqlDatabase &db) override;
-    void setConnectionProperties(const QVariantMap &properties) override;
+    void setConnectionProperties(const QVariantMap &properties,
+                                 const QProcessEnvironment &environment,
+                                 bool loadFromEnvironment) override;
     QString driverName()  override { return "QPSQL"; }
     QString hostName()  override { return _hostName; }
     int port()  override { return _port; }
index 92bc569..57a6aa1 100644 (file)
@@ -72,16 +72,23 @@ UserId SqlAuthenticator::validateUser(const QString &user, const QString &passwo
 }
 
 
-bool SqlAuthenticator::setup(const QVariantMap &settings)
+bool SqlAuthenticator::setup(const QVariantMap &settings, const QProcessEnvironment &environment,
+                             bool loadFromEnvironment)
 {
     Q_UNUSED(settings)
+    Q_UNUSED(environment)
+    Q_UNUSED(loadFromEnvironment)
     return true;
 }
 
 
-Authenticator::State SqlAuthenticator::init(const QVariantMap &settings)
+Authenticator::State SqlAuthenticator::init(const QVariantMap &settings,
+                                            const QProcessEnvironment &environment,
+                                            bool loadFromEnvironment)
 {
     Q_UNUSED(settings)
+    Q_UNUSED(environment)
+    Q_UNUSED(loadFromEnvironment)
 
     // TODO: FIXME: this should check if the storage provider is ready, but I don't
     // know if there's an exposed way to do that at the moment.
index eb33360..79b204f 100644 (file)
@@ -40,8 +40,10 @@ public slots:
 
     virtual inline bool canChangePassword() const { return true; }
 
-    bool setup(const QVariantMap &settings = QVariantMap());
-    State init(const QVariantMap &settings = QVariantMap());
+    bool setup(const QVariantMap &settings, const QProcessEnvironment &environment,
+               bool loadFromEnvironment);
+    State init(const QVariantMap &settings, const QProcessEnvironment &environment,
+               bool loadFromEnvironment);
     UserId validateUser(const QString &user, const QString &password);
 
     /* User handling */
index 5d396ad..9a37160 100644 (file)
@@ -124,7 +124,14 @@ public slots:
     QString getAuthUserName(UserId user) override;
 
 protected:
-    void setConnectionProperties(const QVariantMap & /* properties */)  override {}
+    void setConnectionProperties(const QVariantMap &properties,
+                                 const QProcessEnvironment &environment,
+                                 bool loadFromEnvironment) override {
+        Q_UNUSED(properties);
+        Q_UNUSED(environment);
+        Q_UNUSED(loadFromEnvironment);
+    }
+    // SQLite does not have any connection properties to set
     QString driverName()  override { return "QSQLITE"; }
     QString databaseName()  override { return backlogFile(); }
     int installedSchemaVersion() override;
index 5a0a640..f912c8b 100644 (file)
@@ -85,13 +85,17 @@ public slots:
      *  \param settings   Hostname, port, username, password, ...
      *  \return True if and only if the storage provider was initialized successfully.
      */
-    virtual bool setup(const QVariantMap &settings = QVariantMap()) = 0;
+    virtual bool setup(const QVariantMap &settings = QVariantMap(),
+                       const QProcessEnvironment &environment = {},
+                       bool loadFromEnvironment = false) = 0;
 
     //! Initialize the storage provider
     /** \param settings   Hostname, port, username, password, ...
      *  \return the State the storage backend is now in (see Storage::State)
      */
-    virtual State init(const QVariantMap &settings = QVariantMap()) = 0;
+    virtual State init(const QVariantMap &settings = QVariantMap(),
+                       const QProcessEnvironment &environment = {},
+                       bool loadFromEnvironment = false) = 0;
 
     //! Makes temp data persistent
     /** This Method is periodically called by the Quassel Core to make temporary