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 return instance()->_server.isCertValid() && instance()->_v6server.isCertValid();
578 bool Core::reloadCerts()
580 bool retv4 = _server.reloadCerts();
581 bool retv6 = _v6server.reloadCerts();
583 return retv4 && retv6;
586 void Core::cacheSysIdent()
588 if (isConfigured()) {
589 _authUserNames = _storage->getAllAuthUserNames();
593 QString Core::strictSysIdent(UserId user) const
595 if (_authUserNames.contains(user)) {
596 return _authUserNames[user];
599 // A new user got added since we last pulled our cache from the database.
600 // There's no way to avoid a database hit - we don't even know the authname!
601 instance()->cacheSysIdent();
603 if (_authUserNames.contains(user)) {
604 return _authUserNames[user];
607 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
608 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
609 return "unknown"; // Should we just terminate the program instead?
612 bool Core::startListening()
614 // in mono mode we only start a local port if a port is specified in the cli call
615 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
618 bool success = false;
619 uint port = Quassel::optionValue("port").toUInt();
621 const QString listen = Quassel::optionValue("listen");
622 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
623 if (listen_list.size() > 0) {
624 foreach (const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
626 if (!addr.setAddress(listen_term)) {
627 qCritical() << qPrintable(tr("Invalid listen address %1").arg(listen_term));
630 switch (addr.protocol()) {
631 case QAbstractSocket::IPv6Protocol:
632 if (_v6server.listen(addr, port)) {
633 qInfo() << qPrintable(tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
634 .arg(addr.toString())
635 .arg(_v6server.serverPort())
636 .arg(Quassel::buildInfo().protocolVersion));
640 qWarning() << qPrintable(tr("Could not open IPv6 interface %1:%2: %3").arg(addr.toString()).arg(port).arg(_v6server.errorString()));
642 case QAbstractSocket::IPv4Protocol:
643 if (_server.listen(addr, port)) {
644 qInfo() << qPrintable(tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
645 .arg(addr.toString())
646 .arg(_server.serverPort())
647 .arg(Quassel::buildInfo().protocolVersion));
651 // if v6 succeeded on Any, the port will be already in use - don't display the error then
652 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
653 qWarning() << qPrintable(tr("Could not open IPv4 interface %1:%2: %3").arg(addr.toString()).arg(port).arg(_server.errorString()));
657 qCritical() << qPrintable(tr("Invalid listen address %1, unknown network protocol").arg(listen_term));
664 qCritical() << qPrintable(tr("Could not open any network interfaces to listen on!"));
667 _identServer->startListening();
670 if (_metricsServer) {
671 _metricsServer->startListening();
677 void Core::stopListening(const QString& reason)
680 _identServer->stopListening(reason);
683 if (_metricsServer) {
684 _metricsServer->stopListening(reason);
687 bool wasListening = false;
688 if (_server.isListening()) {
692 if (_v6server.isListening()) {
697 if (reason.isEmpty())
698 qInfo() << "No longer listening for GUI clients.";
700 qInfo() << qPrintable(reason);
704 void Core::incomingConnection()
706 auto* server = qobject_cast<SslServer*>(sender());
708 while (server->hasPendingConnections()) {
709 auto socket = qobject_cast<QSslSocket*>(server->nextPendingConnection());
712 auto* handler = new CoreAuthHandler(socket, this);
713 _connectingClients.insert(handler);
715 connect(handler, &AuthHandler::disconnected, this, &Core::clientDisconnected);
716 connect(handler, &AuthHandler::socketError, this, &Core::socketError);
717 connect(handler, &CoreAuthHandler::handshakeComplete, this, &Core::setupClientSession);
719 qInfo() << qPrintable(tr("Client connected from")) << qPrintable(handler->hostAddress().toString());
722 stopListening(tr("Closing server for basic setup."));
727 // Potentially called during the initialization phase (before handing the connection off to the session)
728 void Core::clientDisconnected()
730 auto* handler = qobject_cast<CoreAuthHandler*>(sender());
733 qInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->hostAddress().toString());
734 _connectingClients.remove(handler);
735 handler->deleteLater();
737 // make server listen again if still not configured
742 // TODO remove unneeded sessions - if necessary/possible...
743 // Suggestion: kill sessions if they are not connected to any network and client.
746 void Core::setupClientSession(RemotePeer* peer, UserId uid)
748 auto* handler = qobject_cast<CoreAuthHandler*>(sender());
751 // From now on everything is handled by the client session
752 disconnect(handler, nullptr, this, nullptr);
753 _connectingClients.remove(handler);
754 handler->deleteLater();
756 // Find or create session for validated user
759 // as we are currently handling an event triggered by incoming data on this socket
760 // it is unsafe to directly move the socket to the client thread.
761 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
764 void Core::customEvent(QEvent* event)
766 if (event->type() == AddClientEventId) {
767 auto* addClientEvent = static_cast<AddClientEvent*>(event);
768 addClientHelper(addClientEvent->peer, addClientEvent->userId);
773 void Core::addClientHelper(RemotePeer* peer, UserId uid)
775 // Find or create session for validated user
776 SessionThread* session = sessionForUser(uid);
777 session->addClient(peer);
780 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
782 if (_initialized && peer) {
783 setupInternalClientSession(peer);
786 _pendingInternalConnection = peer;
790 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
794 auto errorString = setupCoreForInternalUsage();
795 if (!errorString.isEmpty()) {
796 emit exitRequested(EXIT_FAILURE, errorString);
803 uid = _storage->internalUser();
806 qWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
807 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
812 qWarning() << "Client peer went away, not starting a session";
816 auto* corePeer = new InternalPeer(this);
817 corePeer->setPeer(clientPeer);
818 clientPeer->setPeer(corePeer);
820 // Find or create session for validated user
821 SessionThread* sessionThread = sessionForUser(uid);
822 sessionThread->addClient(corePeer);
825 SessionThread* Core::sessionForUser(UserId uid, bool restore)
827 if (_sessions.contains(uid))
828 return _sessions[uid];
830 return (_sessions[uid] = new SessionThread(uid, restore, strictIdentEnabled(), this));
833 void Core::socketError(QAbstractSocket::SocketError err, const QString& errorString)
835 qWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
838 QVariantList Core::backendInfo()
840 instance()->registerStorageBackends();
842 QVariantList backendInfos;
843 for (auto&& backend : instance()->_registeredStorageBackends) {
845 v["BackendId"] = backend->backendId();
846 v["DisplayName"] = backend->displayName();
847 v["Description"] = backend->description();
848 v["SetupData"] = backend->setupData(); // ignored by legacy clients
850 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
851 const auto& setupData = backend->setupData();
852 QStringList setupKeys;
853 QVariantMap setupDefaults;
854 for (int i = 0; i + 2 < setupData.size(); i += 3) {
855 setupKeys << setupData[i].toString();
856 setupDefaults[setupData[i].toString()] = setupData[i + 2];
858 v["SetupKeys"] = setupKeys;
859 v["SetupDefaults"] = setupDefaults;
860 // TODO Protocol Break: Remove
861 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
868 QVariantList Core::authenticatorInfo()
870 instance()->registerAuthenticators();
872 QVariantList authInfos;
873 for (auto&& backend : instance()->_registeredAuthenticators) {
875 v["BackendId"] = backend->backendId();
876 v["DisplayName"] = backend->displayName();
877 v["Description"] = backend->description();
878 v["SetupData"] = backend->setupData();
884 // migration / backend selection
885 bool Core::selectBackend(const QString& backend)
887 // reregister all storage backends
888 registerStorageBackends();
889 auto storage = storageBackend(backend);
891 QStringList backends;
892 std::transform(_registeredStorageBackends.begin(),
893 _registeredStorageBackends.end(),
894 std::back_inserter(backends),
895 [](const DeferredSharedPtr<Storage>& backend) { return backend->displayName(); });
896 qWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
897 qWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
901 QVariantMap settings = promptForSettings(storage.get());
903 Storage::State storageState = storage->init(settings);
904 switch (storageState) {
905 case Storage::IsReady:
906 if (!saveBackendSettings(backend, settings)) {
907 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
909 qWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
910 qWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
912 case Storage::NotAvailable:
913 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
915 case Storage::NeedsSetup:
916 if (!storage->setup(settings)) {
917 qWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
921 if (storage->init(settings) != Storage::IsReady) {
922 qWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
926 if (!saveBackendSettings(backend, settings)) {
927 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
929 qWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
933 // let's see if we have a current storage object we can migrate from
934 auto reader = getMigrationReader(_storage.get());
935 auto writer = getMigrationWriter(storage.get());
936 if (reader && writer) {
937 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
940 if (reader->migrateTo(writer.get())) {
941 qDebug() << "Migration finished!";
942 qDebug() << qPrintable(tr("Migration finished!"));
943 if (!saveBackendSettings(backend, settings)) {
944 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
949 qWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
953 // inform the user why we cannot merge
955 qWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
958 qWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
961 qWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
964 // so we were unable to merge, but let's create a user \o/
965 _storage = std::move(storage);
970 // TODO: I am not sure if this function is implemented correctly.
971 // There is currently no concept of migraiton between auth backends.
972 bool Core::selectAuthenticator(const QString& backend)
974 // Register all authentication backends.
975 registerAuthenticators();
976 auto auther = authenticator(backend);
978 QStringList authenticators;
979 std::transform(_registeredAuthenticators.begin(),
980 _registeredAuthenticators.end(),
981 std::back_inserter(authenticators),
982 [](const DeferredSharedPtr<Authenticator>& authenticator) { return authenticator->displayName(); });
983 qWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
984 qWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
988 QVariantMap settings = promptForSettings(auther.get());
990 Authenticator::State state = auther->init(settings);
992 case Authenticator::IsReady:
993 saveAuthenticatorSettings(backend, settings);
994 qWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
996 case Authenticator::NotAvailable:
997 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
999 case Authenticator::NeedsSetup:
1000 if (!auther->setup(settings)) {
1001 qWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1005 if (auther->init(settings) != Authenticator::IsReady) {
1006 qWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1010 saveAuthenticatorSettings(backend, settings);
1011 qWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1014 _authenticator = std::move(auther);
1018 bool Core::createUser()
1020 QTextStream out(stdout);
1021 QTextStream in(stdin);
1022 out << "Add a new user:" << endl;
1023 out << "Username: ";
1025 QString username = in.readLine().trimmed();
1028 out << "Password: ";
1030 QString password = in.readLine().trimmed();
1032 out << "Repeat Password: ";
1034 QString password2 = in.readLine().trimmed();
1038 if (password != password2) {
1039 qWarning() << "Passwords don't match!";
1042 if (password.isEmpty()) {
1043 qWarning() << "Password is empty!";
1047 if (_configured && _storage->addUser(username, password).isValid()) {
1048 out << "Added user " << username << " successfully!" << endl;
1052 qWarning() << "Unable to add user:" << qPrintable(username);
1057 bool Core::changeUserPass(const QString& username)
1059 QTextStream out(stdout);
1060 QTextStream in(stdin);
1061 UserId userId = _storage->getUserId(username);
1062 if (!userId.isValid()) {
1063 out << "User " << username << " does not exist." << endl;
1067 if (!canChangeUserPassword(userId)) {
1068 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1072 out << "Change password for user: " << username << endl;
1075 out << "New Password: ";
1077 QString password = in.readLine().trimmed();
1079 out << "Repeat Password: ";
1081 QString password2 = in.readLine().trimmed();
1085 if (password != password2) {
1086 qWarning() << "Passwords don't match!";
1089 if (password.isEmpty()) {
1090 qWarning() << "Password is empty!";
1094 if (_configured && _storage->updateUser(userId, password)) {
1095 out << "Password changed successfully!" << endl;
1099 qWarning() << "Failed to change password!";
1104 bool Core::changeUserPassword(UserId userId, const QString& password)
1106 if (!isConfigured() || !userId.isValid())
1109 if (!canChangeUserPassword(userId))
1112 return instance()->_storage->updateUser(userId, password);
1115 // TODO: this code isn't currently 100% optimal because the core
1116 // doesn't know it can have multiple auth providers configured (there aren't
1117 // multiple auth providers at the moment anyway) and we have hardcoded the
1118 // Database provider to be always allowed.
1119 bool Core::canChangeUserPassword(UserId userId)
1121 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1122 if (authProvider != "Database") {
1123 if (authProvider != instance()->_authenticator->backendId()) {
1126 else if (instance()->_authenticator->canChangePassword()) {
1133 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage* storage)
1138 auto* sqlStorage = qobject_cast<AbstractSqlStorage*>(storage);
1140 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1144 return sqlStorage->createMigrationReader();
1147 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage* storage)
1152 auto* sqlStorage = qobject_cast<AbstractSqlStorage*>(storage);
1154 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1158 return sqlStorage->createMigrationWriter();
1161 bool Core::saveBackendSettings(const QString& backend, const QVariantMap& settings)
1163 QVariantMap dbsettings;
1164 dbsettings["Backend"] = backend;
1165 dbsettings["ConnectionProperties"] = settings;
1166 CoreSettings s = CoreSettings();
1167 s.setStorageSettings(dbsettings);
1171 void Core::saveAuthenticatorSettings(const QString& backend, const QVariantMap& settings)
1173 QVariantMap dbsettings;
1174 dbsettings["Authenticator"] = backend;
1175 dbsettings["AuthProperties"] = settings;
1176 CoreSettings().setAuthSettings(dbsettings);
1179 // Generic version of promptForSettings that doesn't care what *type* of
1180 // backend it runs over.
1181 template<typename Backend>
1182 QVariantMap Core::promptForSettings(const Backend* backend)
1184 QVariantMap settings;
1185 const QVariantList& setupData = backend->setupData();
1187 if (setupData.isEmpty())
1190 QTextStream out(stdout);
1191 QTextStream in(stdin);
1192 out << "Default values are in brackets" << endl;
1194 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1195 QString key = setupData[i].toString();
1196 out << setupData[i + 1].toString() << " [" << setupData[i + 2].toString() << "]: " << flush;
1198 bool noEcho = key.toLower().contains("password");
1202 QString input = in.readLine().trimmed();
1208 QVariant value{setupData[i + 2]};
1209 if (!input.isEmpty()) {
1210 switch (value.type()) {
1212 value = input.toInt();
1218 settings[key] = value;
1224 void Core::stdInEcho(bool on)
1226 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1228 GetConsoleMode(hStdin, &mode);
1230 mode |= ENABLE_ECHO_INPUT;
1232 mode &= ~ENABLE_ECHO_INPUT;
1233 SetConsoleMode(hStdin, mode);
1237 void Core::stdInEcho(bool on)
1240 tcgetattr(STDIN_FILENO, &t);
1245 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1248 #endif /* Q_OS_WIN */