aboutsummaryrefslogtreecommitdiff
path: root/lib/ssosession.cpp
diff options
context:
space:
mode:
authorn-peugnet <n.peugnet@free.fr>2022-10-06 19:27:24 +0200
committern-peugnet <n.peugnet@free.fr>2022-10-06 19:27:24 +0200
commitd911b207f49e936b3e992200796110f0749ed150 (patch)
tree96d20ebb4d074a4c1755e21cb316a52d584daee3 /lib/ssosession.cpp
parent8ad8a74152c5701b6ca1f9a00487ba9257a439b4 (diff)
parent56c2f2e2b809b9077393eb617828f33d144f5634 (diff)
downloadlibquotient-d911b207f49e936b3e992200796110f0749ed150.tar.gz
libquotient-d911b207f49e936b3e992200796110f0749ed150.zip
New upstream version 0.7.0
Diffstat (limited to 'lib/ssosession.cpp')
-rw-r--r--lib/ssosession.cpp131
1 files changed, 131 insertions, 0 deletions
diff --git a/lib/ssosession.cpp b/lib/ssosession.cpp
new file mode 100644
index 00000000..93e252cc
--- /dev/null
+++ b/lib/ssosession.cpp
@@ -0,0 +1,131 @@
+// SPDX-FileCopyrightText: 2020 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "ssosession.h"
+
+#include "connection.h"
+#include "csapi/sso_login_redirect.h"
+
+#include <QtNetwork/QTcpServer>
+#include <QtNetwork/QTcpSocket>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QStringBuilder>
+
+using namespace Quotient;
+
+class SsoSession::Private {
+public:
+ Private(SsoSession* q, QString initialDeviceName = {},
+ QString deviceId = {}, Connection* connection = nullptr)
+ : initialDeviceName(std::move(initialDeviceName))
+ , deviceId(std::move(deviceId))
+ , connection(connection)
+ {
+ auto* server = new QTcpServer(q);
+ server->listen();
+ // The "/returnToApplication" part is just a hint for the end-user,
+ // the callback will work without it equally well.
+ callbackUrl = QStringLiteral("http://localhost:%1/returnToApplication")
+ .arg(server->serverPort());
+ ssoUrl = connection->getUrlForApi<RedirectToSSOJob>(callbackUrl);
+
+ QObject::connect(server, &QTcpServer::newConnection, q, [this, q, server] {
+ qCDebug(MAIN) << "SSO callback initiated";
+ socket = server->nextPendingConnection();
+ server->close();
+ QObject::connect(socket, &QTcpSocket::readyRead, socket, [this] {
+ requestData.append(socket->readAll());
+ if (!socket->atEnd() && !requestData.endsWith("\r\n\r\n")) {
+ qDebug(MAIN) << "Incomplete request, waiting for more data";
+ return;
+ }
+ processCallback();
+ });
+ QObject::connect(socket, &QTcpSocket::disconnected, socket,
+ &QTcpSocket::deleteLater);
+ QObject::connect(socket, &QObject::destroyed, q,
+ &QObject::deleteLater);
+ });
+ qCDebug(MAIN) << "SSO session constructed";
+ }
+ ~Private() { qCDebug(MAIN) << "SSO session deconstructed"; }
+ Q_DISABLE_COPY_MOVE(Private)
+
+ void processCallback();
+ void sendHttpResponse(const QByteArray& code, const QByteArray& msg);
+ void onError(const QByteArray& code, const QString& errorMsg);
+
+ QString initialDeviceName;
+ QString deviceId;
+ Connection* connection;
+ QString callbackUrl {};
+ QUrl ssoUrl {};
+ QTcpSocket* socket = nullptr;
+ QByteArray requestData {};
+};
+
+SsoSession::SsoSession(Connection* connection, const QString& initialDeviceName,
+ const QString& deviceId)
+ : QObject(connection)
+ , d(makeImpl<Private>(this, initialDeviceName, deviceId, connection))
+{}
+
+QUrl SsoSession::ssoUrl() const { return d->ssoUrl; }
+
+QUrl SsoSession::callbackUrl() const { return QUrl(d->callbackUrl); }
+
+void SsoSession::Private::processCallback()
+{
+ // https://matrix.org/docs/guides/sso-for-client-developers
+ // Inspired by Clementine's src/internet/core/localredirectserver.cpp
+ // (see at https://github.com/clementine-player/Clementine/)
+ const auto& requestParts = requestData.split(' ');
+ if (requestParts.size() < 2 || requestParts[1].isEmpty()) {
+ onError("400 Bad Request", tr("Malformed single sign-on callback"));
+ return;
+ }
+ const auto& QueryItemName = QStringLiteral("loginToken");
+ QUrlQuery query { QUrl(requestParts[1]).query() };
+ if (!query.hasQueryItem(QueryItemName)) {
+ onError("400 Bad Request", tr("No login token in SSO callback"));
+ return;
+ }
+ qCDebug(MAIN) << "Found the token in SSO callback, logging in";
+ connection->loginWithToken(query.queryItemValue(QueryItemName).toLatin1(),
+ initialDeviceName, deviceId);
+ connect(connection, &Connection::connected, socket, [this] {
+ const auto msg =
+ tr("The application '%1' has successfully logged in as a user %2 "
+ "with device id %3. This window can be closed. Thank you.\r\n")
+ .arg(QCoreApplication::applicationName(), connection->userId(),
+ connection->deviceId());
+ sendHttpResponse("200 OK", msg.toHtmlEscaped().toUtf8());
+ socket->disconnectFromHost();
+ });
+ connect(connection, &Connection::loginError, socket, [this] {
+ onError("401 Unauthorised", tr("Login failed"));
+ });
+}
+
+void SsoSession::Private::sendHttpResponse(const QByteArray& code,
+ const QByteArray& msg)
+{
+ socket->write("HTTP/1.0 ");
+ socket->write(code);
+ socket->write("\r\n"
+ "Content-type: text/html;charset=UTF-8\r\n"
+ "\r\n\r\n");
+ socket->write(msg);
+ socket->write("\r\n");
+}
+
+void SsoSession::Private::onError(const QByteArray& code,
+ const QString& errorMsg)
+{
+ qCWarning(MAIN).nospace() << errorMsg;
+ sendHttpResponse(code, "<h3>" + errorMsg.toUtf8() + "</h3>");
+ // [kitsune] Yeah, I know, dirty. Maybe the "right" way would be to have
+ // an intermediate signal but that seems just a fight for purity.
+ emit connection->loginError(errorMsg, requestData);
+ socket->disconnectFromHost();
+}