1 /***************************************************************************
2 * Copyright (C) 2005-2019 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);
219 _server.setMetricsServer(_metricsServer);
220 _v6server.setMetricsServer(_metricsServer);
224 Quassel::registerReloadHandler([]() {
225 // Currently, only reloading SSL certificates and the sysident cache is supported
226 if (Core::instance()) {
227 Core::instance()->cacheSysIdent();
228 Core::instance()->reloadCerts();
234 connect(&_storageSyncTimer, &QTimer::timeout, this, &Core::syncStorage);
235 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
238 connect(&_server, &QTcpServer::newConnection, this, &Core::incomingConnection);
239 connect(&_v6server, &QTcpServer::newConnection, this, &Core::incomingConnection);
241 if (!startListening()) {
242 throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
245 if (_configured && !Quassel::isOptionSet("norestore")) {
246 Core::restoreState();
251 if (_pendingInternalConnection) {
252 connectInternalPeer(_pendingInternalConnection);
253 _pendingInternalConnection = {};
257 void Core::initAsync()
262 catch (ExitException e) {
263 emit exitRequested(e.exitCode, e.errorString);
267 void Core::shutdown()
269 qInfo() << "Core shutting down...";
273 for (auto&& client : _connectingClients) {
274 client->deleteLater();
276 _connectingClients.clear();
278 if (_sessions.isEmpty()) {
279 emit shutdownComplete();
283 for (auto&& session : _sessions) {
284 connect(session, &SessionThread::shutdownComplete, this, &Core::onSessionShutdown);
289 void Core::onSessionShutdown(SessionThread* session)
291 _sessions.take(_sessions.key(session))->deleteLater();
292 if (_sessions.isEmpty()) {
293 qInfo() << "Core shutdown complete!";
294 emit shutdownComplete();
298 /*** Session Restore ***/
300 void Core::saveState()
303 QVariantList activeSessions;
304 for (auto&& user : instance()->_sessions.keys())
305 activeSessions << QVariant::fromValue(user);
306 _storage->setCoreState(activeSessions);
310 void Core::restoreState()
313 qWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
316 if (_sessions.count()) {
317 qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
322 /* We don't check, since we are at the first version since switching to Git
323 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
325 qWarning() << qPrintable(tr("Core state too old, ignoring..."));
330 const QList<QVariant>& activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
331 QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
333 if (activeSessions.count() > 0) {
334 qInfo() << "Restoring previous core state...";
335 for (auto&& v : activeSessions) {
336 UserId user = v.value<UserId>();
337 sessionForUser(user, true);
344 QString Core::setup(const QString& adminUser,
345 const QString& adminPassword,
346 const QString& backend,
347 const QVariantMap& setupData,
348 const QString& authenticator,
349 const QVariantMap& authSetupData)
351 return instance()->setupCore(adminUser, adminPassword, backend, setupData, authenticator, authSetupData);
354 QString Core::setupCore(const QString& adminUser,
355 const QString& adminPassword,
356 const QString& backend,
357 const QVariantMap& setupData,
358 const QString& authenticator,
359 const QVariantMap& authSetupData)
362 return tr("Core is already configured! Not configuring again...");
364 if (adminUser.isEmpty() || adminPassword.isEmpty()) {
365 return tr("Admin user or password not set.");
368 if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
369 return tr("Could not setup storage!");
372 qInfo() << "Selected authenticator:" << authenticator;
373 if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true))) {
374 return tr("Could not setup authenticator!");
377 catch (ExitException e) {
378 // Event loop is running, so trigger an exit rather than throwing an exception
379 QCoreApplication::exit(e.exitCode);
380 return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
383 if (!saveBackendSettings(backend, setupData)) {
384 return tr("Could not save backend settings, probably a permission problem.");
386 saveAuthenticatorSettings(authenticator, authSetupData);
388 qInfo() << qPrintable(tr("Creating admin user..."));
389 _storage->addUser(adminUser, adminPassword);
391 startListening(); // TODO check when we need this
395 QString Core::setupCoreForInternalUsage()
397 Q_ASSERT(!_registeredStorageBackends.empty());
399 qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
401 for (int i = 0; i < 10; i++) {
403 pass += qrand() % 10;
406 // mono client currently needs sqlite
407 return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "Database", QVariantMap());
410 /*** Storage Handling ***/
412 template<typename Storage>
413 void Core::registerStorageBackend()
415 auto backend = makeDeferredShared<Storage>(this);
416 if (backend->isAvailable())
417 _registeredStorageBackends.emplace_back(std::move(backend));
419 backend->deleteLater();
422 void Core::registerStorageBackends()
424 if (_registeredStorageBackends.empty()) {
425 registerStorageBackend<SqliteStorage>();
426 registerStorageBackend<PostgreSqlStorage>();
430 DeferredSharedPtr<Storage> Core::storageBackend(const QString& backendId) const
432 auto it = std::find_if(_registeredStorageBackends.begin(),
433 _registeredStorageBackends.end(),
434 [backendId](const DeferredSharedPtr<Storage>& backend) { return backend->displayName() == backendId; });
435 return it != _registeredStorageBackends.end() ? *it : nullptr;
438 bool Core::initStorage(
439 const QString& backend, const QVariantMap& settings, const QProcessEnvironment& environment, bool loadFromEnvironment, bool setup)
441 if (backend.isEmpty()) {
442 qWarning() << "No storage backend selected!";
446 auto storage = storageBackend(backend);
448 qCritical() << "Selected storage backend is not available:" << backend;
452 connect(storage.get(), &Storage::dbUpgradeInProgress, this, &Core::dbUpgradeInProgress);
454 Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
455 switch (storageState) {
456 case Storage::NeedsSetup:
458 return false; // trigger setup process
459 if (storage->setup(settings, environment, loadFromEnvironment))
460 return initStorage(backend, settings, environment, loadFromEnvironment, false);
463 case Storage::NotAvailable:
465 // If initialization wasn't successful, we quit to keep from coming up unconfigured
466 throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
468 qCritical() << "Selected storage backend is not available:" << backend;
471 case Storage::IsReady:
472 // delete all other backends
473 _registeredStorageBackends.clear();
474 connect(storage.get(), &Storage::bufferInfoUpdated, this, &Core::bufferInfoUpdated);
477 _storage = std::move(storage);
481 void Core::syncStorage()
487 /*** Storage Access ***/
488 bool Core::createNetwork(UserId user, NetworkInfo& info)
490 NetworkId networkId = instance()->_storage->createNetwork(user, info);
491 if (!networkId.isValid())
494 info.networkId = networkId;
498 /*** Authenticators ***/
500 // Authentication handling, now independent from storage.
501 template<typename Authenticator>
502 void Core::registerAuthenticator()
504 auto authenticator = makeDeferredShared<Authenticator>(this);
505 if (authenticator->isAvailable())
506 _registeredAuthenticators.emplace_back(std::move(authenticator));
508 authenticator->deleteLater();
511 void Core::registerAuthenticators()
513 if (_registeredAuthenticators.empty()) {
514 registerAuthenticator<SqlAuthenticator>();
516 registerAuthenticator<LdapAuthenticator>();
521 DeferredSharedPtr<Authenticator> Core::authenticator(const QString& backendId) const
523 auto it = std::find_if(_registeredAuthenticators.begin(),
524 _registeredAuthenticators.end(),
525 [backendId](const DeferredSharedPtr<Authenticator>& authenticator) {
526 return authenticator->backendId() == backendId;
528 return it != _registeredAuthenticators.end() ? *it : nullptr;
531 // FIXME: Apparently, this is the legacy way of initting storage backends?
532 // If there's a not-legacy way, it should be used here
533 bool Core::initAuthenticator(
534 const QString& backend, const QVariantMap& settings, const QProcessEnvironment& environment, bool loadFromEnvironment, bool setup)
536 if (backend.isEmpty()) {
537 qWarning() << "No authenticator selected!";
541 auto auth = authenticator(backend);
543 qCritical() << "Selected auth backend is not available:" << backend;
547 Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
549 case Authenticator::NeedsSetup:
551 return false; // trigger setup process
552 if (auth->setup(settings, environment, loadFromEnvironment))
553 return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
556 case Authenticator::NotAvailable:
558 // If initialization wasn't successful, we quit to keep from coming up unconfigured
559 throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
561 qCritical() << "Selected auth backend is not available:" << backend;
564 case Authenticator::IsReady:
565 // delete all other backends
566 _registeredAuthenticators.clear();
569 _authenticator = std::move(auth);
573 /*** Network Management ***/
575 bool Core::sslSupported()
578 auto* sslServer = qobject_cast<SslServer*>(&instance()->_server);
579 return sslServer && sslServer->isCertValid();
585 bool Core::reloadCerts()
588 auto* sslServerv4 = qobject_cast<SslServer*>(&_server);
589 bool retv4 = sslServerv4->reloadCerts();
591 auto* sslServerv6 = qobject_cast<SslServer*>(&_v6server);
592 bool retv6 = sslServerv6->reloadCerts();
594 return retv4 && retv6;
596 // SSL not supported, don't mark configuration reload as failed
601 void Core::cacheSysIdent()
603 if (isConfigured()) {
604 _authUserNames = _storage->getAllAuthUserNames();
608 QString Core::strictSysIdent(UserId user) const
610 if (_authUserNames.contains(user)) {
611 return _authUserNames[user];
614 // A new user got added since we last pulled our cache from the database.
615 // There's no way to avoid a database hit - we don't even know the authname!
616 instance()->cacheSysIdent();
618 if (_authUserNames.contains(user)) {
619 return _authUserNames[user];
622 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
623 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
624 return "unknown"; // Should we just terminate the program instead?
627 bool Core::startListening()
629 // in mono mode we only start a local port if a port is specified in the cli call
630 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
633 bool success = false;
634 uint port = Quassel::optionValue("port").toUInt();
636 const QString listen = Quassel::optionValue("listen");
637 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
638 if (listen_list.size() > 0) {
639 foreach (const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
641 if (!addr.setAddress(listen_term)) {
642 qCritical() << qPrintable(tr("Invalid listen address %1").arg(listen_term));
645 switch (addr.protocol()) {
646 case QAbstractSocket::IPv6Protocol:
647 if (_v6server.listen(addr, port)) {
648 qInfo() << qPrintable(tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
649 .arg(addr.toString())
650 .arg(_v6server.serverPort())
651 .arg(Quassel::buildInfo().protocolVersion));
655 qWarning() << qPrintable(tr("Could not open IPv6 interface %1:%2: %3").arg(addr.toString()).arg(port).arg(_v6server.errorString()));
657 case QAbstractSocket::IPv4Protocol:
658 if (_server.listen(addr, port)) {
659 qInfo() << qPrintable(tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
660 .arg(addr.toString())
661 .arg(_server.serverPort())
662 .arg(Quassel::buildInfo().protocolVersion));
666 // if v6 succeeded on Any, the port will be already in use - don't display the error then
667 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
668 qWarning() << qPrintable(tr("Could not open IPv4 interface %1:%2: %3").arg(addr.toString()).arg(port).arg(_server.errorString()));
672 qCritical() << qPrintable(tr("Invalid listen address %1, unknown network protocol").arg(listen_term));
679 qCritical() << qPrintable(tr("Could not open any network interfaces to listen on!"));
682 _identServer->startListening();
685 if (_metricsServer) {
686 _metricsServer->startListening();
692 void Core::stopListening(const QString& reason)
695 _identServer->stopListening(reason);
698 if (_metricsServer) {
699 _metricsServer->stopListening(reason);
702 bool wasListening = false;
703 if (_server.isListening()) {
707 if (_v6server.isListening()) {
712 if (reason.isEmpty())
713 qInfo() << "No longer listening for GUI clients.";
715 qInfo() << qPrintable(reason);
719 void Core::incomingConnection()
721 auto* server = qobject_cast<QTcpServer*>(sender());
723 while (server->hasPendingConnections()) {
724 QTcpSocket* socket = server->nextPendingConnection();
726 auto* handler = new CoreAuthHandler(socket, this);
727 _connectingClients.insert(handler);
729 connect(handler, &AuthHandler::disconnected, this, &Core::clientDisconnected);
730 connect(handler, &AuthHandler::socketError, this, &Core::socketError);
731 connect(handler, &CoreAuthHandler::handshakeComplete, this, &Core::setupClientSession);
733 qInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
736 stopListening(tr("Closing server for basic setup."));
741 // Potentially called during the initialization phase (before handing the connection off to the session)
742 void Core::clientDisconnected()
744 auto* handler = qobject_cast<CoreAuthHandler*>(sender());
747 qInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
748 _connectingClients.remove(handler);
749 handler->deleteLater();
751 // make server listen again if still not configured
756 // TODO remove unneeded sessions - if necessary/possible...
757 // Suggestion: kill sessions if they are not connected to any network and client.
760 void Core::setupClientSession(RemotePeer* peer, UserId uid)
762 auto* handler = qobject_cast<CoreAuthHandler*>(sender());
765 // From now on everything is handled by the client session
766 disconnect(handler, nullptr, this, nullptr);
767 _connectingClients.remove(handler);
768 handler->deleteLater();
770 // Find or create session for validated user
773 // as we are currently handling an event triggered by incoming data on this socket
774 // it is unsafe to directly move the socket to the client thread.
775 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
778 void Core::customEvent(QEvent* event)
780 if (event->type() == AddClientEventId) {
781 auto* addClientEvent = static_cast<AddClientEvent*>(event);
782 addClientHelper(addClientEvent->peer, addClientEvent->userId);
787 void Core::addClientHelper(RemotePeer* peer, UserId uid)
789 // Find or create session for validated user
790 SessionThread* session = sessionForUser(uid);
791 session->addClient(peer);
794 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
796 if (_initialized && peer) {
797 setupInternalClientSession(peer);
800 _pendingInternalConnection = peer;
804 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
808 auto errorString = setupCoreForInternalUsage();
809 if (!errorString.isEmpty()) {
810 emit exitRequested(EXIT_FAILURE, errorString);
817 uid = _storage->internalUser();
820 qWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
821 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
826 qWarning() << "Client peer went away, not starting a session";
830 auto* corePeer = new InternalPeer(this);
831 corePeer->setPeer(clientPeer);
832 clientPeer->setPeer(corePeer);
834 // Find or create session for validated user
835 SessionThread* sessionThread = sessionForUser(uid);
836 sessionThread->addClient(corePeer);
839 SessionThread* Core::sessionForUser(UserId uid, bool restore)
841 if (_sessions.contains(uid))
842 return _sessions[uid];
844 return (_sessions[uid] = new SessionThread(uid, restore, strictIdentEnabled(), this));
847 void Core::socketError(QAbstractSocket::SocketError err, const QString& errorString)
849 qWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
852 QVariantList Core::backendInfo()
854 instance()->registerStorageBackends();
856 QVariantList backendInfos;
857 for (auto&& backend : instance()->_registeredStorageBackends) {
859 v["BackendId"] = backend->backendId();
860 v["DisplayName"] = backend->displayName();
861 v["Description"] = backend->description();
862 v["SetupData"] = backend->setupData(); // ignored by legacy clients
864 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
865 const auto& setupData = backend->setupData();
866 QStringList setupKeys;
867 QVariantMap setupDefaults;
868 for (int i = 0; i + 2 < setupData.size(); i += 3) {
869 setupKeys << setupData[i].toString();
870 setupDefaults[setupData[i].toString()] = setupData[i + 2];
872 v["SetupKeys"] = setupKeys;
873 v["SetupDefaults"] = setupDefaults;
874 // TODO Protocol Break: Remove
875 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
882 QVariantList Core::authenticatorInfo()
884 instance()->registerAuthenticators();
886 QVariantList authInfos;
887 for (auto&& backend : instance()->_registeredAuthenticators) {
889 v["BackendId"] = backend->backendId();
890 v["DisplayName"] = backend->displayName();
891 v["Description"] = backend->description();
892 v["SetupData"] = backend->setupData();
898 // migration / backend selection
899 bool Core::selectBackend(const QString& backend)
901 // reregister all storage backends
902 registerStorageBackends();
903 auto storage = storageBackend(backend);
905 QStringList backends;
906 std::transform(_registeredStorageBackends.begin(),
907 _registeredStorageBackends.end(),
908 std::back_inserter(backends),
909 [](const DeferredSharedPtr<Storage>& backend) { return backend->displayName(); });
910 qWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
911 qWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
915 QVariantMap settings = promptForSettings(storage.get());
917 Storage::State storageState = storage->init(settings);
918 switch (storageState) {
919 case Storage::IsReady:
920 if (!saveBackendSettings(backend, settings)) {
921 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
923 qWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
924 qWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
926 case Storage::NotAvailable:
927 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
929 case Storage::NeedsSetup:
930 if (!storage->setup(settings)) {
931 qWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
935 if (storage->init(settings) != Storage::IsReady) {
936 qWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
940 if (!saveBackendSettings(backend, settings)) {
941 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
943 qWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
947 // let's see if we have a current storage object we can migrate from
948 auto reader = getMigrationReader(_storage.get());
949 auto writer = getMigrationWriter(storage.get());
950 if (reader && writer) {
951 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
954 if (reader->migrateTo(writer.get())) {
955 qDebug() << "Migration finished!";
956 qDebug() << qPrintable(tr("Migration finished!"));
957 if (!saveBackendSettings(backend, settings)) {
958 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
963 qWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
967 // inform the user why we cannot merge
969 qWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
972 qWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
975 qWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
978 // so we were unable to merge, but let's create a user \o/
979 _storage = std::move(storage);
984 // TODO: I am not sure if this function is implemented correctly.
985 // There is currently no concept of migraiton between auth backends.
986 bool Core::selectAuthenticator(const QString& backend)
988 // Register all authentication backends.
989 registerAuthenticators();
990 auto auther = authenticator(backend);
992 QStringList authenticators;
993 std::transform(_registeredAuthenticators.begin(),
994 _registeredAuthenticators.end(),
995 std::back_inserter(authenticators),
996 [](const DeferredSharedPtr<Authenticator>& authenticator) { return authenticator->displayName(); });
997 qWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
998 qWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
1002 QVariantMap settings = promptForSettings(auther.get());
1004 Authenticator::State state = auther->init(settings);
1006 case Authenticator::IsReady:
1007 saveAuthenticatorSettings(backend, settings);
1008 qWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1010 case Authenticator::NotAvailable:
1011 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
1013 case Authenticator::NeedsSetup:
1014 if (!auther->setup(settings)) {
1015 qWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1019 if (auther->init(settings) != Authenticator::IsReady) {
1020 qWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1024 saveAuthenticatorSettings(backend, settings);
1025 qWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1028 _authenticator = std::move(auther);
1032 bool Core::createUser()
1034 QTextStream out(stdout);
1035 QTextStream in(stdin);
1036 out << "Add a new user:" << endl;
1037 out << "Username: ";
1039 QString username = in.readLine().trimmed();
1042 out << "Password: ";
1044 QString password = in.readLine().trimmed();
1046 out << "Repeat Password: ";
1048 QString password2 = in.readLine().trimmed();
1052 if (password != password2) {
1053 qWarning() << "Passwords don't match!";
1056 if (password.isEmpty()) {
1057 qWarning() << "Password is empty!";
1061 if (_configured && _storage->addUser(username, password).isValid()) {
1062 out << "Added user " << username << " successfully!" << endl;
1066 qWarning() << "Unable to add user:" << qPrintable(username);
1071 bool Core::changeUserPass(const QString& username)
1073 QTextStream out(stdout);
1074 QTextStream in(stdin);
1075 UserId userId = _storage->getUserId(username);
1076 if (!userId.isValid()) {
1077 out << "User " << username << " does not exist." << endl;
1081 if (!canChangeUserPassword(userId)) {
1082 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1086 out << "Change password for user: " << username << endl;
1089 out << "New Password: ";
1091 QString password = in.readLine().trimmed();
1093 out << "Repeat Password: ";
1095 QString password2 = in.readLine().trimmed();
1099 if (password != password2) {
1100 qWarning() << "Passwords don't match!";
1103 if (password.isEmpty()) {
1104 qWarning() << "Password is empty!";
1108 if (_configured && _storage->updateUser(userId, password)) {
1109 out << "Password changed successfully!" << endl;
1113 qWarning() << "Failed to change password!";
1118 bool Core::changeUserPassword(UserId userId, const QString& password)
1120 if (!isConfigured() || !userId.isValid())
1123 if (!canChangeUserPassword(userId))
1126 return instance()->_storage->updateUser(userId, password);
1129 // TODO: this code isn't currently 100% optimal because the core
1130 // doesn't know it can have multiple auth providers configured (there aren't
1131 // multiple auth providers at the moment anyway) and we have hardcoded the
1132 // Database provider to be always allowed.
1133 bool Core::canChangeUserPassword(UserId userId)
1135 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1136 if (authProvider != "Database") {
1137 if (authProvider != instance()->_authenticator->backendId()) {
1140 else if (instance()->_authenticator->canChangePassword()) {
1147 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage* storage)
1152 auto* sqlStorage = qobject_cast<AbstractSqlStorage*>(storage);
1154 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1158 return sqlStorage->createMigrationReader();
1161 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage* storage)
1166 auto* sqlStorage = qobject_cast<AbstractSqlStorage*>(storage);
1168 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1172 return sqlStorage->createMigrationWriter();
1175 bool Core::saveBackendSettings(const QString& backend, const QVariantMap& settings)
1177 QVariantMap dbsettings;
1178 dbsettings["Backend"] = backend;
1179 dbsettings["ConnectionProperties"] = settings;
1180 CoreSettings s = CoreSettings();
1181 s.setStorageSettings(dbsettings);
1185 void Core::saveAuthenticatorSettings(const QString& backend, const QVariantMap& settings)
1187 QVariantMap dbsettings;
1188 dbsettings["Authenticator"] = backend;
1189 dbsettings["AuthProperties"] = settings;
1190 CoreSettings().setAuthSettings(dbsettings);
1193 // Generic version of promptForSettings that doesn't care what *type* of
1194 // backend it runs over.
1195 template<typename Backend>
1196 QVariantMap Core::promptForSettings(const Backend* backend)
1198 QVariantMap settings;
1199 const QVariantList& setupData = backend->setupData();
1201 if (setupData.isEmpty())
1204 QTextStream out(stdout);
1205 QTextStream in(stdin);
1206 out << "Default values are in brackets" << endl;
1208 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1209 QString key = setupData[i].toString();
1210 out << setupData[i + 1].toString() << " [" << setupData[i + 2].toString() << "]: " << flush;
1212 bool noEcho = key.toLower().contains("password");
1216 QString input = in.readLine().trimmed();
1222 QVariant value{setupData[i + 2]};
1223 if (!input.isEmpty()) {
1224 switch (value.type()) {
1226 value = input.toInt();
1232 settings[key] = value;
1238 void Core::stdInEcho(bool on)
1240 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1242 GetConsoleMode(hStdin, &mode);
1244 mode |= ENABLE_ECHO_INPUT;
1246 mode &= ~ENABLE_ECHO_INPUT;
1247 SetConsoleMode(hStdin, mode);
1251 void Core::stdInEcho(bool on)
1254 tcgetattr(STDIN_FILENO, &t);
1259 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1262 #endif /* Q_OS_WIN */