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 Quassel::registerReloadHandler([]() {
217 // Currently, only reloading SSL certificates and the sysident cache is supported
218 if (Core::instance()) {
219 Core::instance()->cacheSysIdent();
220 Core::instance()->reloadCerts();
226 connect(&_storageSyncTimer, &QTimer::timeout, this, &Core::syncStorage);
227 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
230 connect(&_server, &QTcpServer::newConnection, this, &Core::incomingConnection);
231 connect(&_v6server, &QTcpServer::newConnection, this, &Core::incomingConnection);
233 if (!startListening()) {
234 throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
237 if (_configured && !Quassel::isOptionSet("norestore")) {
238 Core::restoreState();
243 if (_pendingInternalConnection) {
244 connectInternalPeer(_pendingInternalConnection);
245 _pendingInternalConnection = {};
249 void Core::initAsync()
254 catch (ExitException e) {
255 emit exitRequested(e.exitCode, e.errorString);
259 void Core::shutdown()
261 qInfo() << "Core shutting down...";
265 for (auto&& client : _connectingClients) {
266 client->deleteLater();
268 _connectingClients.clear();
270 if (_sessions.isEmpty()) {
271 emit shutdownComplete();
275 for (auto&& session : _sessions) {
276 connect(session, &SessionThread::shutdownComplete, this, &Core::onSessionShutdown);
281 void Core::onSessionShutdown(SessionThread* session)
283 _sessions.take(_sessions.key(session))->deleteLater();
284 if (_sessions.isEmpty()) {
285 qInfo() << "Core shutdown complete!";
286 emit shutdownComplete();
290 /*** Session Restore ***/
292 void Core::saveState()
295 QVariantList activeSessions;
296 for (auto&& user : instance()->_sessions.keys())
297 activeSessions << QVariant::fromValue<UserId>(user);
298 _storage->setCoreState(activeSessions);
302 void Core::restoreState()
305 qWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
308 if (_sessions.count()) {
309 qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
314 /* We don't check, since we are at the first version since switching to Git
315 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
317 qWarning() << qPrintable(tr("Core state too old, ignoring..."));
322 const QList<QVariant>& activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
323 QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
325 if (activeSessions.count() > 0) {
326 qInfo() << "Restoring previous core state...";
327 for (auto&& v : activeSessions) {
328 UserId user = v.value<UserId>();
329 sessionForUser(user, true);
336 QString Core::setup(const QString& adminUser,
337 const QString& adminPassword,
338 const QString& backend,
339 const QVariantMap& setupData,
340 const QString& authenticator,
341 const QVariantMap& authSetupData)
343 return instance()->setupCore(adminUser, adminPassword, backend, setupData, authenticator, authSetupData);
346 QString Core::setupCore(const QString& adminUser,
347 const QString& adminPassword,
348 const QString& backend,
349 const QVariantMap& setupData,
350 const QString& authenticator,
351 const QVariantMap& authSetupData)
354 return tr("Core is already configured! Not configuring again...");
356 if (adminUser.isEmpty() || adminPassword.isEmpty()) {
357 return tr("Admin user or password not set.");
360 if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
361 return tr("Could not setup storage!");
364 qInfo() << "Selected authenticator:" << authenticator;
365 if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true))) {
366 return tr("Could not setup authenticator!");
369 catch (ExitException e) {
370 // Event loop is running, so trigger an exit rather than throwing an exception
371 QCoreApplication::exit(e.exitCode);
372 return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
375 if (!saveBackendSettings(backend, setupData)) {
376 return tr("Could not save backend settings, probably a permission problem.");
378 saveAuthenticatorSettings(authenticator, authSetupData);
380 qInfo() << qPrintable(tr("Creating admin user..."));
381 _storage->addUser(adminUser, adminPassword);
383 startListening(); // TODO check when we need this
387 QString Core::setupCoreForInternalUsage()
389 Q_ASSERT(!_registeredStorageBackends.empty());
391 qsrand(QDateTime::currentDateTime().toMSecsSinceEpoch());
393 for (int i = 0; i < 10; i++) {
395 pass += qrand() % 10;
398 // mono client currently needs sqlite
399 return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap(), "Database", QVariantMap());
402 /*** Storage Handling ***/
404 template<typename Storage>
405 void Core::registerStorageBackend()
407 auto backend = makeDeferredShared<Storage>(this);
408 if (backend->isAvailable())
409 _registeredStorageBackends.emplace_back(std::move(backend));
411 backend->deleteLater();
414 void Core::registerStorageBackends()
416 if (_registeredStorageBackends.empty()) {
417 registerStorageBackend<SqliteStorage>();
418 registerStorageBackend<PostgreSqlStorage>();
422 DeferredSharedPtr<Storage> Core::storageBackend(const QString& backendId) const
424 auto it = std::find_if(_registeredStorageBackends.begin(),
425 _registeredStorageBackends.end(),
426 [backendId](const DeferredSharedPtr<Storage>& backend) { return backend->displayName() == backendId; });
427 return it != _registeredStorageBackends.end() ? *it : nullptr;
430 bool Core::initStorage(
431 const QString& backend, const QVariantMap& settings, const QProcessEnvironment& environment, bool loadFromEnvironment, bool setup)
433 if (backend.isEmpty()) {
434 qWarning() << "No storage backend selected!";
438 auto storage = storageBackend(backend);
440 qCritical() << "Selected storage backend is not available:" << backend;
444 connect(storage.get(), &Storage::dbUpgradeInProgress, this, &Core::dbUpgradeInProgress);
446 Storage::State storageState = storage->init(settings, environment, loadFromEnvironment);
447 switch (storageState) {
448 case Storage::NeedsSetup:
450 return false; // trigger setup process
451 if (storage->setup(settings, environment, loadFromEnvironment))
452 return initStorage(backend, settings, environment, loadFromEnvironment, false);
455 case Storage::NotAvailable:
457 // If initialization wasn't successful, we quit to keep from coming up unconfigured
458 throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
460 qCritical() << "Selected storage backend is not available:" << backend;
463 case Storage::IsReady:
464 // delete all other backends
465 _registeredStorageBackends.clear();
466 connect(storage.get(), &Storage::bufferInfoUpdated, this, &Core::bufferInfoUpdated);
469 _storage = std::move(storage);
473 void Core::syncStorage()
479 /*** Storage Access ***/
480 bool Core::createNetwork(UserId user, NetworkInfo& info)
482 NetworkId networkId = instance()->_storage->createNetwork(user, info);
483 if (!networkId.isValid())
486 info.networkId = networkId;
490 /*** Authenticators ***/
492 // Authentication handling, now independent from storage.
493 template<typename Authenticator>
494 void Core::registerAuthenticator()
496 auto authenticator = makeDeferredShared<Authenticator>(this);
497 if (authenticator->isAvailable())
498 _registeredAuthenticators.emplace_back(std::move(authenticator));
500 authenticator->deleteLater();
503 void Core::registerAuthenticators()
505 if (_registeredAuthenticators.empty()) {
506 registerAuthenticator<SqlAuthenticator>();
508 registerAuthenticator<LdapAuthenticator>();
513 DeferredSharedPtr<Authenticator> Core::authenticator(const QString& backendId) const
515 auto it = std::find_if(_registeredAuthenticators.begin(),
516 _registeredAuthenticators.end(),
517 [backendId](const DeferredSharedPtr<Authenticator>& authenticator) {
518 return authenticator->backendId() == backendId;
520 return it != _registeredAuthenticators.end() ? *it : nullptr;
523 // FIXME: Apparently, this is the legacy way of initting storage backends?
524 // If there's a not-legacy way, it should be used here
525 bool Core::initAuthenticator(
526 const QString& backend, const QVariantMap& settings, const QProcessEnvironment& environment, bool loadFromEnvironment, bool setup)
528 if (backend.isEmpty()) {
529 qWarning() << "No authenticator selected!";
533 auto auth = authenticator(backend);
535 qCritical() << "Selected auth backend is not available:" << backend;
539 Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment);
541 case Authenticator::NeedsSetup:
543 return false; // trigger setup process
544 if (auth->setup(settings, environment, loadFromEnvironment))
545 return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
548 case Authenticator::NotAvailable:
550 // If initialization wasn't successful, we quit to keep from coming up unconfigured
551 throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
553 qCritical() << "Selected auth backend is not available:" << backend;
556 case Authenticator::IsReady:
557 // delete all other backends
558 _registeredAuthenticators.clear();
561 _authenticator = std::move(auth);
565 /*** Network Management ***/
567 bool Core::sslSupported()
570 auto* sslServer = qobject_cast<SslServer*>(&instance()->_server);
571 return sslServer && sslServer->isCertValid();
577 bool Core::reloadCerts()
580 auto* sslServerv4 = qobject_cast<SslServer*>(&_server);
581 bool retv4 = sslServerv4->reloadCerts();
583 auto* sslServerv6 = qobject_cast<SslServer*>(&_v6server);
584 bool retv6 = sslServerv6->reloadCerts();
586 return retv4 && retv6;
588 // SSL not supported, don't mark configuration reload as failed
593 void Core::cacheSysIdent()
595 if (isConfigured()) {
596 _authUserNames = _storage->getAllAuthUserNames();
600 QString Core::strictSysIdent(UserId user) const
602 if (_authUserNames.contains(user)) {
603 return _authUserNames[user];
606 // A new user got added since we last pulled our cache from the database.
607 // There's no way to avoid a database hit - we don't even know the authname!
608 instance()->cacheSysIdent();
610 if (_authUserNames.contains(user)) {
611 return _authUserNames[user];
614 // ...something very weird is going on if we ended up here (an active CoreSession without a corresponding database entry?)
615 qWarning().nospace() << "Unable to find authusername for UserId " << user << ", this should never happen!";
616 return "unknown"; // Should we just terminate the program instead?
619 bool Core::startListening()
621 // in mono mode we only start a local port if a port is specified in the cli call
622 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
625 bool success = false;
626 uint port = Quassel::optionValue("port").toUInt();
628 const QString listen = Quassel::optionValue("listen");
629 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
630 if (listen_list.size() > 0) {
631 foreach (const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
633 if (!addr.setAddress(listen_term)) {
634 qCritical() << qPrintable(tr("Invalid listen address %1").arg(listen_term));
637 switch (addr.protocol()) {
638 case QAbstractSocket::IPv6Protocol:
639 if (_v6server.listen(addr, port)) {
640 qInfo() << qPrintable(tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
641 .arg(addr.toString())
642 .arg(_v6server.serverPort())
643 .arg(Quassel::buildInfo().protocolVersion));
647 qWarning() << qPrintable(tr("Could not open IPv6 interface %1:%2: %3").arg(addr.toString()).arg(port).arg(_v6server.errorString()));
649 case QAbstractSocket::IPv4Protocol:
650 if (_server.listen(addr, port)) {
651 qInfo() << qPrintable(tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
652 .arg(addr.toString())
653 .arg(_server.serverPort())
654 .arg(Quassel::buildInfo().protocolVersion));
658 // if v6 succeeded on Any, the port will be already in use - don't display the error then
659 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
660 qWarning() << qPrintable(tr("Could not open IPv4 interface %1:%2: %3").arg(addr.toString()).arg(port).arg(_server.errorString()));
664 qCritical() << qPrintable(tr("Invalid listen address %1, unknown network protocol").arg(listen_term));
671 qCritical() << qPrintable(tr("Could not open any network interfaces to listen on!"));
674 _identServer->startListening();
680 void Core::stopListening(const QString& reason)
683 _identServer->stopListening(reason);
686 bool wasListening = false;
687 if (_server.isListening()) {
691 if (_v6server.isListening()) {
696 if (reason.isEmpty())
697 qInfo() << "No longer listening for GUI clients.";
699 qInfo() << qPrintable(reason);
703 void Core::incomingConnection()
705 auto* server = qobject_cast<QTcpServer*>(sender());
707 while (server->hasPendingConnections()) {
708 QTcpSocket* socket = server->nextPendingConnection();
710 auto* handler = new CoreAuthHandler(socket, this);
711 _connectingClients.insert(handler);
713 connect(handler, &AuthHandler::disconnected, this, &Core::clientDisconnected);
714 connect(handler, &AuthHandler::socketError, this, &Core::socketError);
715 connect(handler, &CoreAuthHandler::handshakeComplete, this, &Core::setupClientSession);
717 qInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
720 stopListening(tr("Closing server for basic setup."));
725 // Potentially called during the initialization phase (before handing the connection off to the session)
726 void Core::clientDisconnected()
728 auto* handler = qobject_cast<CoreAuthHandler*>(sender());
731 qInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
732 _connectingClients.remove(handler);
733 handler->deleteLater();
735 // make server listen again if still not configured
740 // TODO remove unneeded sessions - if necessary/possible...
741 // Suggestion: kill sessions if they are not connected to any network and client.
744 void Core::setupClientSession(RemotePeer* peer, UserId uid)
746 auto* handler = qobject_cast<CoreAuthHandler*>(sender());
749 // From now on everything is handled by the client session
750 disconnect(handler, nullptr, this, nullptr);
751 _connectingClients.remove(handler);
752 handler->deleteLater();
754 // Find or create session for validated user
757 // as we are currently handling an event triggered by incoming data on this socket
758 // it is unsafe to directly move the socket to the client thread.
759 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
762 void Core::customEvent(QEvent* event)
764 if (event->type() == AddClientEventId) {
765 auto* addClientEvent = static_cast<AddClientEvent*>(event);
766 addClientHelper(addClientEvent->peer, addClientEvent->userId);
771 void Core::addClientHelper(RemotePeer* peer, UserId uid)
773 // Find or create session for validated user
774 SessionThread* session = sessionForUser(uid);
775 session->addClient(peer);
778 void Core::connectInternalPeer(QPointer<InternalPeer> peer)
780 if (_initialized && peer) {
781 setupInternalClientSession(peer);
784 _pendingInternalConnection = peer;
788 void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
792 auto errorString = setupCoreForInternalUsage();
793 if (!errorString.isEmpty()) {
794 emit exitRequested(EXIT_FAILURE, errorString);
801 uid = _storage->internalUser();
804 qWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
805 emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
810 qWarning() << "Client peer went away, not starting a session";
814 auto* corePeer = new InternalPeer(this);
815 corePeer->setPeer(clientPeer);
816 clientPeer->setPeer(corePeer);
818 // Find or create session for validated user
819 SessionThread* sessionThread = sessionForUser(uid);
820 sessionThread->addClient(corePeer);
823 SessionThread* Core::sessionForUser(UserId uid, bool restore)
825 if (_sessions.contains(uid))
826 return _sessions[uid];
828 return (_sessions[uid] = new SessionThread(uid, restore, strictIdentEnabled(), this));
831 void Core::socketError(QAbstractSocket::SocketError err, const QString& errorString)
833 qWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
836 QVariantList Core::backendInfo()
838 instance()->registerStorageBackends();
840 QVariantList backendInfos;
841 for (auto&& backend : instance()->_registeredStorageBackends) {
843 v["BackendId"] = backend->backendId();
844 v["DisplayName"] = backend->displayName();
845 v["Description"] = backend->description();
846 v["SetupData"] = backend->setupData(); // ignored by legacy clients
848 // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
849 const auto& setupData = backend->setupData();
850 QStringList setupKeys;
851 QVariantMap setupDefaults;
852 for (int i = 0; i + 2 < setupData.size(); i += 3) {
853 setupKeys << setupData[i].toString();
854 setupDefaults[setupData[i].toString()] = setupData[i + 2];
856 v["SetupKeys"] = setupKeys;
857 v["SetupDefaults"] = setupDefaults;
858 // TODO Protocol Break: Remove
859 v["IsDefault"] = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
866 QVariantList Core::authenticatorInfo()
868 instance()->registerAuthenticators();
870 QVariantList authInfos;
871 for (auto&& backend : instance()->_registeredAuthenticators) {
873 v["BackendId"] = backend->backendId();
874 v["DisplayName"] = backend->displayName();
875 v["Description"] = backend->description();
876 v["SetupData"] = backend->setupData();
882 // migration / backend selection
883 bool Core::selectBackend(const QString& backend)
885 // reregister all storage backends
886 registerStorageBackends();
887 auto storage = storageBackend(backend);
889 QStringList backends;
890 std::transform(_registeredStorageBackends.begin(),
891 _registeredStorageBackends.end(),
892 std::back_inserter(backends),
893 [](const DeferredSharedPtr<Storage>& backend) { return backend->displayName(); });
894 qWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
895 qWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
899 QVariantMap settings = promptForSettings(storage.get());
901 Storage::State storageState = storage->init(settings);
902 switch (storageState) {
903 case Storage::IsReady:
904 if (!saveBackendSettings(backend, settings)) {
905 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
907 qWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
908 qWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
910 case Storage::NotAvailable:
911 qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
913 case Storage::NeedsSetup:
914 if (!storage->setup(settings)) {
915 qWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
919 if (storage->init(settings) != Storage::IsReady) {
920 qWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
924 if (!saveBackendSettings(backend, settings)) {
925 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
927 qWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
931 // let's see if we have a current storage object we can migrate from
932 auto reader = getMigrationReader(_storage.get());
933 auto writer = getMigrationWriter(storage.get());
934 if (reader && writer) {
935 qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
938 if (reader->migrateTo(writer.get())) {
939 qDebug() << "Migration finished!";
940 qDebug() << qPrintable(tr("Migration finished!"));
941 if (!saveBackendSettings(backend, settings)) {
942 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
947 qWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
951 // inform the user why we cannot merge
953 qWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
956 qWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
959 qWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
962 // so we were unable to merge, but let's create a user \o/
963 _storage = std::move(storage);
968 // TODO: I am not sure if this function is implemented correctly.
969 // There is currently no concept of migraiton between auth backends.
970 bool Core::selectAuthenticator(const QString& backend)
972 // Register all authentication backends.
973 registerAuthenticators();
974 auto auther = authenticator(backend);
976 QStringList authenticators;
977 std::transform(_registeredAuthenticators.begin(),
978 _registeredAuthenticators.end(),
979 std::back_inserter(authenticators),
980 [](const DeferredSharedPtr<Authenticator>& authenticator) { return authenticator->displayName(); });
981 qWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
982 qWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
986 QVariantMap settings = promptForSettings(auther.get());
988 Authenticator::State state = auther->init(settings);
990 case Authenticator::IsReady:
991 saveAuthenticatorSettings(backend, settings);
992 qWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
994 case Authenticator::NotAvailable:
995 qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
997 case Authenticator::NeedsSetup:
998 if (!auther->setup(settings)) {
999 qWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
1003 if (auther->init(settings) != Authenticator::IsReady) {
1004 qWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
1008 saveAuthenticatorSettings(backend, settings);
1009 qWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
1012 _authenticator = std::move(auther);
1016 bool Core::createUser()
1018 QTextStream out(stdout);
1019 QTextStream in(stdin);
1020 out << "Add a new user:" << endl;
1021 out << "Username: ";
1023 QString username = in.readLine().trimmed();
1026 out << "Password: ";
1028 QString password = in.readLine().trimmed();
1030 out << "Repeat Password: ";
1032 QString password2 = in.readLine().trimmed();
1036 if (password != password2) {
1037 qWarning() << "Passwords don't match!";
1040 if (password.isEmpty()) {
1041 qWarning() << "Password is empty!";
1045 if (_configured && _storage->addUser(username, password).isValid()) {
1046 out << "Added user " << username << " successfully!" << endl;
1050 qWarning() << "Unable to add user:" << qPrintable(username);
1055 bool Core::changeUserPass(const QString& username)
1057 QTextStream out(stdout);
1058 QTextStream in(stdin);
1059 UserId userId = _storage->getUserId(username);
1060 if (!userId.isValid()) {
1061 out << "User " << username << " does not exist." << endl;
1065 if (!canChangeUserPassword(userId)) {
1066 out << "User " << username << " is configured through an auth provider that has forbidden manual password changing." << endl;
1070 out << "Change password for user: " << username << endl;
1073 out << "New Password: ";
1075 QString password = in.readLine().trimmed();
1077 out << "Repeat Password: ";
1079 QString password2 = in.readLine().trimmed();
1083 if (password != password2) {
1084 qWarning() << "Passwords don't match!";
1087 if (password.isEmpty()) {
1088 qWarning() << "Password is empty!";
1092 if (_configured && _storage->updateUser(userId, password)) {
1093 out << "Password changed successfully!" << endl;
1097 qWarning() << "Failed to change password!";
1102 bool Core::changeUserPassword(UserId userId, const QString& password)
1104 if (!isConfigured() || !userId.isValid())
1107 if (!canChangeUserPassword(userId))
1110 return instance()->_storage->updateUser(userId, password);
1113 // TODO: this code isn't currently 100% optimal because the core
1114 // doesn't know it can have multiple auth providers configured (there aren't
1115 // multiple auth providers at the moment anyway) and we have hardcoded the
1116 // Database provider to be always allowed.
1117 bool Core::canChangeUserPassword(UserId userId)
1119 QString authProvider = instance()->_storage->getUserAuthenticator(userId);
1120 if (authProvider != "Database") {
1121 if (authProvider != instance()->_authenticator->backendId()) {
1124 else if (instance()->_authenticator->canChangePassword()) {
1131 std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage* storage)
1136 auto* sqlStorage = qobject_cast<AbstractSqlStorage*>(storage);
1138 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1142 return sqlStorage->createMigrationReader();
1145 std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage* storage)
1150 auto* sqlStorage = qobject_cast<AbstractSqlStorage*>(storage);
1152 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1156 return sqlStorage->createMigrationWriter();
1159 bool Core::saveBackendSettings(const QString& backend, const QVariantMap& settings)
1161 QVariantMap dbsettings;
1162 dbsettings["Backend"] = backend;
1163 dbsettings["ConnectionProperties"] = settings;
1164 CoreSettings s = CoreSettings();
1165 s.setStorageSettings(dbsettings);
1169 void Core::saveAuthenticatorSettings(const QString& backend, const QVariantMap& settings)
1171 QVariantMap dbsettings;
1172 dbsettings["Authenticator"] = backend;
1173 dbsettings["AuthProperties"] = settings;
1174 CoreSettings().setAuthSettings(dbsettings);
1177 // Generic version of promptForSettings that doesn't care what *type* of
1178 // backend it runs over.
1179 template<typename Backend>
1180 QVariantMap Core::promptForSettings(const Backend* backend)
1182 QVariantMap settings;
1183 const QVariantList& setupData = backend->setupData();
1185 if (setupData.isEmpty())
1188 QTextStream out(stdout);
1189 QTextStream in(stdin);
1190 out << "Default values are in brackets" << endl;
1192 for (int i = 0; i + 2 < setupData.size(); i += 3) {
1193 QString key = setupData[i].toString();
1194 out << setupData[i + 1].toString() << " [" << setupData[i + 2].toString() << "]: " << flush;
1196 bool noEcho = key.toLower().contains("password");
1200 QString input = in.readLine().trimmed();
1206 QVariant value{setupData[i + 2]};
1207 if (!input.isEmpty()) {
1208 switch (value.type()) {
1210 value = input.toInt();
1216 settings[key] = value;
1222 void Core::stdInEcho(bool on)
1224 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1226 GetConsoleMode(hStdin, &mode);
1228 mode |= ENABLE_ECHO_INPUT;
1230 mode &= ~ENABLE_ECHO_INPUT;
1231 SetConsoleMode(hStdin, mode);
1235 void Core::stdInEcho(bool on)
1238 tcgetattr(STDIN_FILENO, &t);
1243 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1246 #endif /* Q_OS_WIN */