1 /***************************************************************************
2 * Copyright (C) 2005-2020 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
25 #include <QCoreApplication>
27 #include "coreauthhandler.h"
28 #include "coresession.h"
29 #include "coresettings.h"
30 #include "internalpeer.h"
32 #include "postgresqlstorage.h"
34 #include "sqlauthenticator.h"
35 #include "sqlitestorage.h"
40 # include "ldapauthenticator.h"
52 // ==============================
54 // ==============================
55 const int Core::AddClientEventId = QEvent::registerEventType();
57 class AddClientEvent : public QEvent
60 AddClientEvent(RemotePeer* p, UserId uid)
61 : QEvent(QEvent::Type(Core::AddClientEventId))
69 // ==============================
71 // ==============================
74 : Singleton<Core>{this}
78 // Parent all QObject-derived attributes, so when the Core instance gets moved into another
79 // thread, they get moved with it
80 _server.setParent(this);
81 _v6server.setParent(this);
82 _storageSyncTimer.setParent(this);
87 qDeleteAll(_connectingClients);
88 qDeleteAll(_sessions);
94 _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
96 // check settings version
97 // so far, we only have 1
99 if (s.version() != 1) {
100 throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
103 // Set up storage and authentication backends
104 registerStorageBackends();
105 registerAuthenticators();
107 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
108 bool config_from_environment = Quassel::isOptionSet("config-from-environment");
111 QVariantMap db_connectionProperties;
113 QString auth_authenticator;
114 QVariantMap auth_properties;
116 bool writeError = false;
118 if (config_from_environment) {
119 db_backend = environment.value("DB_BACKEND");
120 auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
125 QVariantMap dbsettings = cs.storageSettings().toMap();
126 db_backend = dbsettings.value("Backend").toString();
127 db_connectionProperties = dbsettings.value("ConnectionProperties").toMap();
129 QVariantMap authSettings = cs.authSettings().toMap();
130 auth_authenticator = authSettings.value("Authenticator", "Database").toString();
131 auth_properties = authSettings.value("AuthProperties").toMap();
133 writeError = !cs.isWritable();
137 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
139 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
142 catch (ExitException) {
147 if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
149 if (Quassel::isOptionSet("select-backend")) {
150 success &= selectBackend(Quassel::optionValue("select-backend"));
152 if (Quassel::isOptionSet("select-authenticator")) {
153 success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
155 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
159 if (config_from_environment) {
161 _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
163 _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
166 catch (ExitException e) {
167 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
171 throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
175 if (_registeredStorageBackends.empty()) {
176 throw ExitException{EXIT_FAILURE,
177 tr("Could not initialize any storage backend! Exiting...\n"
178 "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
179 "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
184 throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
187 qInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
191 // This checks separately because config-from-environment might have only configured the core just now
193 if (Quassel::isOptionSet("add-user")) {
194 bool success = createUser();
195 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
198 if (Quassel::isOptionSet("change-userpass")) {
199 bool success = changeUserPass(Quassel::optionValue("change-userpass"));
200 throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
203 _strictIdentEnabled = Quassel::isOptionSet("strict-ident");
204 if (_strictIdentEnabled) {
208 if (Quassel::isOptionSet("oidentd")) {
209 _oidentdConfigGenerator = new OidentdConfigGenerator(this);
212 if (Quassel::isOptionSet("ident-daemon")) {
213 _identServer = new IdentServer(this);
216 if (Quassel::isOptionSet("metrics-daemon")) {
217 _metricsServer = new MetricsServer(this);
218 _server.setMetricsServer(_metricsServer);
219 _v6server.setMetricsServer(_metricsServer);
222 Quassel::registerReloadHandler([]() {
223 // Currently, only reloading SSL certificates and the sysident cache is supported
224 if (Core::instance()) {
225 Core::instance()->cacheSysIdent();
226 Core::instance()->reloadCerts();
232 connect(&_storageSyncTimer, &QTimer::timeout, this, &Core::syncStorage);
233 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
236 connect(&_server, &QTcpServer::newConnection, this, &Core::incomingConnection);
237 connect(&_v6server, &QTcpServer::newConnection, this, &Core::incomingConnection);
239 if (!startListening()) {
240 throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
243 if (_configured && !Quassel::isOptionSet("norestore")) {
244 Core::restoreState();
249 if (_pendingInternalConnection) {
250 connectInternalPeer(_pendingInternalConnection);
251 _pendingInternalConnection = {};
255 void Core::initAsync()
260 catch (ExitException e) {
261 emit exitRequested(e.exitCode, e.errorString);
265 void Core::shutdown()
267 qInfo() << "Core shutting down...";
271 for (auto&& client : _connectingClients) {
272 client->deleteLater();
274 _connectingClients.clear();
276 if (_sessions.isEmpty()) {
277 emit shutdownComplete();
281 for (auto&& session : _sessions) {
282 connect(session, &SessionThread::shutdownComplete, this, &Core::onSessionShutdown);
287 void Core::onSessionShutdown(SessionThread* session)
289 _sessions.take(_sessions.key(session))->deleteLater();
290 if (_sessions.isEmpty()) {
291 qInfo() << "Core shutdown complete!";
292 emit shutdownComplete();
296 /*** Session Restore ***/
298 void Core::saveState()
301 QVariantList activeSessions;
302 for (auto&& user : instance()->_sessions.keys())
303 activeSessions << QVariant::fromValue(user);
304 _storage->setCoreState(activeSessions);
308 void Core::restoreState()
311 qWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
314 if (_sessions.count()) {
315 qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
320 /* We don't check, since we are at the first version since switching to Git
321 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
323 qWarning() << qPrintable(tr("Core state too old, ignoring..."));
328 const QList<QVariant>& activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
329 QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
331 if (activeSessions.count() > 0) {
332 qInfo() << "Restoring previous core state...";
333 for (auto&& v : activeSessions) {
334 UserId user = v.value<UserId>();
335 sessionForUser(user, true);
342 QString Core::setup(const QString& adminUser,
343 const QString& adminPassword,
344 const QString& backend,
345 const QVariantMap& setupData,
346 const QString& authenticator,
347 const QVariantMap& authSetupData)
349 return instance()->setupCore(adminUser, adminPassword, backend, setupData, authenticator, authSetupData);
352 QString Core::setupCore(const QString& adminUser,
353 const QString& adminPassword,
354 const QString& backend,
355 const QVariantMap& setupData,
356 const QString& authenticator,
357 const QVariantMap& authSetupData)
360 return tr("Core is already configured! Not configuring again...");
362 if (adminUser.isEmpty() || adminPassword.isEmpty()) {
363 return tr("Admin user or password not set.");
366 if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
367 return tr("Could not setup storage!");
370 qInfo() << "Selected authenticator:" << authenticator;
371 if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true))) {
372 return tr("Could not setup authenticator!");
375 catch (ExitException e) {
376 // Event loop is running, so trigger an exit rather than throwing an exception
377 QCoreApplication::exit(e.exitCode);
378 return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
381 if (!saveBackendSettings(backend, setupData)) {
382 return tr("Could not save backend settings, probably a permission problem.");
384 saveAuthenticatorSettings(authenticator, authSetupData);
386 qInfo() << qPrintable(tr("Creating admin user..."));
387 _storage->addUser(adminUser, adminPassword);
389 startListening(); // TODO check when we need this
393 QString Core::setupCoreForInternalUsage()
395 Q_ASSERT(!_registeredStorageBackends.empty());
397 qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
399 for (int i = 0; i < 10; i++) {
401 pass += qrand() % 10;
404 // mono client currently needs sqlite
405 return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "Database", QVariantMap());
408 /*** Storage Handling ***/
410 template<typename Storage>
411 void Core::registerStorageBackend()
413 auto backend = makeDeferredShared<Storage>(this);
414 if (backend->isAvailable())
415 _registeredStorageBackends.emplace_back(std::move(backend));
417 backend->deleteLater();
420 void Core::registerStorageBackends()
422 if (_registeredStorageBackends.empty()) {
423 registerStorageBackend<SqliteStorage>();
424 registerStorageBackend<PostgreSqlStorage>();
428 DeferredSharedPtr<Storage> Core::storageBackend(const QString& backendId) const
430 auto it = std::find_if(_registeredStorageBackends.begin(),
431 _registeredStorageBackends.end(),
432 [backendId](const DeferredSharedPtr<Storage>& backend) { return backend->displayName() == backendId; });
433 return it != _registeredStorageBackends.end() ? *it : nullptr;
436 bool Core::initStorage(
437 const QString& backend, const QVariantMap& settings, const QProcessEnvironment& environment, bool loadFromEnvironment, bool setup)
439 if (backend.isEmpty()) {
440 qWarning() << "No storage backend selected!";
444 auto storage = storageBackend(backend);
446 qCritical() << "Selected storage backend is not available:" << backend;
450 connect(storage.get(), &Storage::dbUpgradeInProgress, this, &Core::dbUpgradeInProgress);
452 Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
453 switch (storageState) {
454 case Storage::NeedsSetup:
456 return false; // trigger setup process
457 if (storage->setup(settings, environment, loadFromEnvironment))
458 return initStorage(backend, settings, environment, loadFromEnvironment, false);
461 case Storage::NotAvailable:
463 // If initialization wasn't successful, we quit to keep from coming up unconfigured
464 throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
466 qCritical() << "Selected storage backend is not available:" << backend;
469 case Storage::IsReady:
470 // delete all other backends
471 _registeredStorageBackends.clear();
472 connect(storage.get(), &Storage::bufferInfoUpdated, this, &Core::bufferInfoUpdated);
475 _storage = std::move(storage);
479 void Core::syncStorage()
485 /*** Storage Access ***/
486 bool Core::createNetwork(UserId user, NetworkInfo& info)
488 NetworkId networkId = instance()->_storage->createNetwork(user, info);
489 if (!networkId.isValid())
492 info.networkId = networkId;
496 /*** Authenticators ***/
498 // Authentication handling, now independent from storage.
499 template<typename Authenticator>
500 void Core::registerAuthenticator()
502 auto authenticator = makeDeferredShared<Authenticator>(this);
503 if (authenticator->isAvailable())
504 _registeredAuthenticators.emplace_back(std::move(authenticator));
506 authenticator->deleteLater();
509 void Core::registerAuthenticators()
511 if (_registeredAuthenticators.empty()) {
512 registerAuthenticator<SqlAuthenticator>();
514 registerAuthenticator<LdapAuthenticator>();
519 DeferredSharedPtr<Authenticator> Core::authenticator(const QString& backendId) const
521 auto it = std::find_if(_registeredAuthenticators.begin(),
522 _registeredAuthenticators.end(),
523 [backendId](const DeferredSharedPtr<Authenticator>& authenticator) {
524 return authenticator->backendId() == backendId;
526 return it != _registeredAuthenticators.end() ? *it : nullptr;
529 // FIXME: Apparently, this is the legacy way of initting storage backends?
530 // If there's a not-legacy way, it should be used here
531 bool Core::initAuthenticator(
532 const QString& backend, const QVariantMap& settings, const QProcessEnvironment& environment, bool loadFromEnvironment, bool setup)
534 if (backend.isEmpty()) {
535 qWarning() << "No authenticator selected!";
539 auto auth = authenticator(backend);
541 qCritical() << "Selected auth backend is not available:" << backend;
545 Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
547 case Authenticator::NeedsSetup:
549 return false; // trigger setup process
550 if (auth->setup(settings, environment, loadFromEnvironment))
551 return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
554 case Authenticator::NotAvailable:
556 // If initialization wasn't successful, we quit to keep from coming up unconfigured
557 throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
559 qCritical() << "Selected auth backend is not available:" << backend;
562 case Authenticator::IsReady:
563 // delete all other backends
564 _registeredAuthenticators.clear();
567 _authenticator = std::move(auth);
571 /*** Network Management ***/
573 bool Core::sslSupported()
575 auto* sslServer = qobject_cast<SslServer*>(&instance()->_server);
576 return sslServer && sslServer->isCertValid();
579 bool Core::reloadCerts()
581 auto* sslServerv4 = qobject_cast<SslServer*>(&_server);
582 bool retv4 = sslServerv4->reloadCerts();
584 auto* sslServerv6 = qobject_cast<SslServer*>(&_v6server);
585 bool retv6 = sslServerv6->reloadCerts();
587 return retv4 && retv6;
590 void Core::cacheSysIdent()
592 if (isConfigured()) {
593 _authUserNames = _storage->getAllAuthUserNames();
597 QString Core::strictSysIdent(UserId user) const
599 if (_authUserNames.contains(user)) {
600 return _authUserNames[user];
603 // A new user got added since we last pulled our cache from the database.
604 // There's no way to avoid a database hit - we don't even know the authname!
605 instance()->cacheSysIdent();
607 if (_authUserNames.contains(user)) {
608 return _authUserNames[user];
611 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
612 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
613 return "unknown"; // Should we just terminate the program instead?
616 bool Core::startListening()
618 // in mono mode we only start a local port if a port is specified in the cli call
619 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
622 bool success = false;
623 uint port = Quassel::optionValue("port").toUInt();
625 const QString listen = Quassel::optionValue("listen");
626 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
627 if (listen_list.size() > 0) {
628 foreach (const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
630 if (!addr.setAddress(listen_term)) {
631 qCritical() << qPrintable(tr("Invalid listen address %1").arg(listen_term));
634 switch (addr.protocol()) {
635 case QAbstractSocket::IPv6Protocol:
636 if (_v6server.listen(addr, port)) {
637 qInfo() << qPrintable(tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
638 .arg(addr.toString())
639 .arg(_v6server.serverPort())
640 .arg(Quassel::buildInfo().protocolVersion));
644 qWarning() << qPrintable(tr("Could not open IPv6 interface %1:%2: %3").arg(addr.toString()).arg(port).arg(_v6server.errorString()));
646 case QAbstractSocket::IPv4Protocol:
647 if (_server.listen(addr, port)) {
648 qInfo() << qPrintable(tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
649 .arg(addr.toString())
650 .arg(_server.serverPort())
651 .arg(Quassel::buildInfo().protocolVersion));
655 // if v6 succeeded on Any, the port will be already in use - don't display the error then
656 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
657 qWarning() << qPrintable(tr("Could not open IPv4 interface %1:%2: %3").arg(addr.toString()).arg(port).arg(_server.errorString()));
661 qCritical() << qPrintable(tr("Invalid listen address %1, unknown network protocol").arg(listen_term));
668 qCritical() << qPrintable(tr("Could not open any network interfaces to listen on!"));
671 _identServer->startListening();
674 if (_metricsServer) {
675 _metricsServer->startListening();
681 void Core::stopListening(const QString& reason)
684 _identServer->stopListening(reason);
687 if (_metricsServer) {
688 _metricsServer->stopListening(reason);
691 bool wasListening = false;
692 if (_server.isListening()) {
696 if (_v6server.isListening()) {
701 if (reason.isEmpty())
702 qInfo() << "No longer listening for GUI clients.";
704 qInfo() << qPrintable(reason);
708 void Core::incomingConnection()
710 auto* server = qobject_cast<QTcpServer*>(sender());
712 while (server->hasPendingConnections()) {
713 QTcpSocket* socket = server->nextPendingConnection();
715 auto* handler = new CoreAuthHandler(socket, this);
716 _connectingClients.insert(handler);
718 connect(handler, &AuthHandler::disconnected, this, &Core::clientDisconnected);
719 connect(handler, &AuthHandler::socketError, this, &Core::socketError);
720 connect(handler, &CoreAuthHandler::handshakeComplete, this, &Core::setupClientSession);
722 qInfo() << qPrintable(tr("Client connected from")) << qPrintable(handler->hostAddress().toString());
725 stopListening(tr("Closing server for basic setup."));
730 // Potentially called during the initialization phase (before handing the connection off to the session)
731 void Core::clientDisconnected()
733 auto* handler = qobject_cast<CoreAuthHandler*>(sender());
736 qInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->hostAddress().toString());
737 _connectingClients.remove(handler);
738 handler->deleteLater();
740 // make server listen again if still not configured
745 // TODO remove unneeded sessions - if necessary/possible...
746 // Suggestion: kill sessions if they are not connected to any network and client.
749 void Core::setupClientSession(RemotePeer* peer, UserId uid)
751 auto* handler = qobject_cast<CoreAuthHandler*>(sender());
754 // From now on everything is handled by the client session
755 disconnect(handler, nullptr, this, nullptr);
756 _connectingClients.remove(handler);
757 handler->deleteLater();
759 // Find or create session for validated user
762 // as we are currently handling an event triggered by incoming data on this socket
763 // it is unsafe to directly move the socket to the client thread.
764 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
767 void Core::customEvent(QEvent* event)
769 if (event->type() == AddClientEventId) {
770 auto* addClientEvent = static_cast<AddClientEvent*>(event);
771 addClientHelper(addClientEvent->peer, addClientEvent->userId);
776 void Core::addClientHelper(RemotePeer* peer, UserId uid)
778 // Find or create session for validated user
779 SessionThread* session = sessionForUser(uid);
780 session->addClient(peer);
783 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
785 if (_initialized && peer) {
786 setupInternalClientSession(peer);
789 _pendingInternalConnection = peer;
793 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
797 auto errorString = setupCoreForInternalUsage();
798 if (!errorString.isEmpty()) {
799 emit exitRequested(EXIT_FAILURE, errorString);
806 uid = _storage->internalUser();
809 qWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
810 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
815 qWarning() << "Client peer went away, not starting a session";
819 auto* corePeer = new InternalPeer(this);
820 corePeer->setPeer(clientPeer);
821 clientPeer->setPeer(corePeer);
823 // Find or create session for validated user
824 SessionThread* sessionThread = sessionForUser(uid);
825 sessionThread->addClient(corePeer);
828 SessionThread* Core::sessionForUser(UserId uid, bool restore)
830 if (_sessions.contains(uid))
831 return _sessions[uid];
833 return (_sessions[uid] = new SessionThread(uid, restore, strictIdentEnabled(), this));
836 void Core::socketError(QAbstractSocket::SocketError err, const QString& errorString)
838 qWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
841 QVariantList Core::backendInfo()
843 instance()->registerStorageBackends();
845 QVariantList backendInfos;
846 for (auto&& backend : instance()->_registeredStorageBackends) {
848 v["BackendId"] = backend->backendId();
849 v["DisplayName"] = backend->displayName();
850 v["Description"] = backend->description();
851 v["SetupData"] = backend->setupData(); // ignored by legacy clients
853 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
854 const auto& setupData = backend->setupData();
855 QStringList setupKeys;
856 QVariantMap setupDefaults;
857 for (int i = 0; i + 2 < setupData.size(); i += 3) {
858 setupKeys << setupData[i].toString();
859 setupDefaults[setupData[i].toString()] = setupData[i + 2];
861 v["SetupKeys"] = setupKeys;
862 v["SetupDefaults"] = setupDefaults;
863 // TODO Protocol Break: Remove
864 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
871 QVariantList Core::authenticatorInfo()
873 instance()->registerAuthenticators();
875 QVariantList authInfos;
876 for (auto&& backend : instance()->_registeredAuthenticators) {
878 v["BackendId"] = backend->backendId();
879 v["DisplayName"] = backend->displayName();
880 v["Description"] = backend->description();
881 v["SetupData"] = backend->setupData();
887 // migration / backend selection
888 bool Core::selectBackend(const QString& backend)
890 // reregister all storage backends
891 registerStorageBackends();
892 auto storage = storageBackend(backend);
894 QStringList backends;
895 std::transform(_registeredStorageBackends.begin(),
896 _registeredStorageBackends.end(),
897 std::back_inserter(backends),
898 [](const DeferredSharedPtr<Storage>& backend) { return backend->displayName(); });
899 qWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
900 qWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
904 QVariantMap settings = promptForSettings(storage.get());
906 Storage::State storageState = storage->init(settings);
907 switch (storageState) {
908 case Storage::IsReady:
909 if (!saveBackendSettings(backend, settings)) {
910 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
912 qWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
913 qWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
915 case Storage::NotAvailable:
916 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
918 case Storage::NeedsSetup:
919 if (!storage->setup(settings)) {
920 qWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
924 if (storage->init(settings) != Storage::IsReady) {
925 qWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
929 if (!saveBackendSettings(backend, settings)) {
930 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
932 qWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
936 // let's see if we have a current storage object we can migrate from
937 auto reader = getMigrationReader(_storage.get());
938 auto writer = getMigrationWriter(storage.get());
939 if (reader && writer) {
940 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
943 if (reader->migrateTo(writer.get())) {
944 qDebug() << "Migration finished!";
945 qDebug() << qPrintable(tr("Migration finished!"));
946 if (!saveBackendSettings(backend, settings)) {
947 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
952 qWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
956 // inform the user why we cannot merge
958 qWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
961 qWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
964 qWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
967 // so we were unable to merge, but let's create a user \o/
968 _storage = std::move(storage);
973 // TODO: I am not sure if this function is implemented correctly.
974 // There is currently no concept of migraiton between auth backends.
975 bool Core::selectAuthenticator(const QString& backend)
977 // Register all authentication backends.
978 registerAuthenticators();
979 auto auther = authenticator(backend);
981 QStringList authenticators;
982 std::transform(_registeredAuthenticators.begin(),
983 _registeredAuthenticators.end(),
984 std::back_inserter(authenticators),
985 [](const DeferredSharedPtr<Authenticator>& authenticator) { return authenticator->displayName(); });
986 qWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
987 qWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
991 QVariantMap settings = promptForSettings(auther.get());
993 Authenticator::State state = auther->init(settings);
995 case Authenticator::IsReady:
996 saveAuthenticatorSettings(backend, settings);
997 qWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
999 case Authenticator::NotAvailable:
1000 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
1002 case Authenticator::NeedsSetup:
1003 if (!auther->setup(settings)) {
1004 qWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1008 if (auther->init(settings) != Authenticator::IsReady) {
1009 qWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1013 saveAuthenticatorSettings(backend, settings);
1014 qWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1017 _authenticator = std::move(auther);
1021 bool Core::createUser()
1023 QTextStream out(stdout);
1024 QTextStream in(stdin);
1025 out << "Add a new user:" << endl;
1026 out << "Username: ";
1028 QString username = in.readLine().trimmed();
1031 out << "Password: ";
1033 QString password = in.readLine().trimmed();
1035 out << "Repeat Password: ";
1037 QString password2 = in.readLine().trimmed();
1041 if (password != password2) {
1042 qWarning() << "Passwords don't match!";
1045 if (password.isEmpty()) {
1046 qWarning() << "Password is empty!";
1050 if (_configured && _storage->addUser(username, password).isValid()) {
1051 out << "Added user " << username << " successfully!" << endl;
1055 qWarning() << "Unable to add user:" << qPrintable(username);
1060 bool Core::changeUserPass(const QString& username)
1062 QTextStream out(stdout);
1063 QTextStream in(stdin);
1064 UserId userId = _storage->getUserId(username);
1065 if (!userId.isValid()) {
1066 out << "User " << username << " does not exist." << endl;
1070 if (!canChangeUserPassword(userId)) {
1071 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1075 out << "Change password for user: " << username << endl;
1078 out << "New Password: ";
1080 QString password = in.readLine().trimmed();
1082 out << "Repeat Password: ";
1084 QString password2 = in.readLine().trimmed();
1088 if (password != password2) {
1089 qWarning() << "Passwords don't match!";
1092 if (password.isEmpty()) {
1093 qWarning() << "Password is empty!";
1097 if (_configured && _storage->updateUser(userId, password)) {
1098 out << "Password changed successfully!" << endl;
1102 qWarning() << "Failed to change password!";
1107 bool Core::changeUserPassword(UserId userId, const QString& password)
1109 if (!isConfigured() || !userId.isValid())
1112 if (!canChangeUserPassword(userId))
1115 return instance()->_storage->updateUser(userId, password);
1118 // TODO: this code isn't currently 100% optimal because the core
1119 // doesn't know it can have multiple auth providers configured (there aren't
1120 // multiple auth providers at the moment anyway) and we have hardcoded the
1121 // Database provider to be always allowed.
1122 bool Core::canChangeUserPassword(UserId userId)
1124 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1125 if (authProvider != "Database") {
1126 if (authProvider != instance()->_authenticator->backendId()) {
1129 else if (instance()->_authenticator->canChangePassword()) {
1136 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage* storage)
1141 auto* sqlStorage = qobject_cast<AbstractSqlStorage*>(storage);
1143 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1147 return sqlStorage->createMigrationReader();
1150 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage* storage)
1155 auto* sqlStorage = qobject_cast<AbstractSqlStorage*>(storage);
1157 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1161 return sqlStorage->createMigrationWriter();
1164 bool Core::saveBackendSettings(const QString& backend, const QVariantMap& settings)
1166 QVariantMap dbsettings;
1167 dbsettings["Backend"] = backend;
1168 dbsettings["ConnectionProperties"] = settings;
1169 CoreSettings s = CoreSettings();
1170 s.setStorageSettings(dbsettings);
1174 void Core::saveAuthenticatorSettings(const QString& backend, const QVariantMap& settings)
1176 QVariantMap dbsettings;
1177 dbsettings["Authenticator"] = backend;
1178 dbsettings["AuthProperties"] = settings;
1179 CoreSettings().setAuthSettings(dbsettings);
1182 // Generic version of promptForSettings that doesn't care what *type* of
1183 // backend it runs over.
1184 template<typename Backend>
1185 QVariantMap Core::promptForSettings(const Backend* backend)
1187 QVariantMap settings;
1188 const QVariantList& setupData = backend->setupData();
1190 if (setupData.isEmpty())
1193 QTextStream out(stdout);
1194 QTextStream in(stdin);
1195 out << "Default values are in brackets" << endl;
1197 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1198 QString key = setupData[i].toString();
1199 out << setupData[i + 1].toString() << " [" << setupData[i + 2].toString() << "]: " << flush;
1201 bool noEcho = key.toLower().contains("password");
1205 QString input = in.readLine().trimmed();
1211 QVariant value{setupData[i + 2]};
1212 if (!input.isEmpty()) {
1213 switch (value.type()) {
1215 value = input.toInt();
1221 settings[key] = value;
1227 void Core::stdInEcho(bool on)
1229 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1231 GetConsoleMode(hStdin, &mode);
1233 mode |= ENABLE_ECHO_INPUT;
1235 mode &= ~ENABLE_ECHO_INPUT;
1236 SetConsoleMode(hStdin, mode);
1240 void Core::stdInEcho(bool on)
1243 tcgetattr(STDIN_FILENO, &t);
1248 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1251 #endif /* Q_OS_WIN */