linphone-desktop/Linphone/model/auth/OIDCModel.cpp

203 lines
7.4 KiB
C++

/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "OIDCModel.hpp"
#include <QDesktopServices>
#include <QtNetworkAuth>
#include "model/core/CoreModel.hpp"
#include "tool/Utils.hpp"
// =============================================================================
static constexpr char OIDCClientId[] = "linphone";
static constexpr char OIDCScope[] = "offline_access";
static constexpr char OIDCWellKnown[] = "/.well-known/openid-configuration";
DEFINE_ABSTRACT_OBJECT(OIDCModel)
class OAuthHttpServerReplyHandler : public QOAuthHttpServerReplyHandler {
public:
OAuthHttpServerReplyHandler(const int &port, QObject *parent = nullptr)
: QOAuthHttpServerReplyHandler(port, parent) {
}
QString callback() const override;
};
QString OAuthHttpServerReplyHandler::callback() const {
QString uri;
if (uri != "") return QUrl::toPercentEncoding(uri);
else return QOAuthHttpServerReplyHandler::callback(); // Return default
}
OIDCModel::OIDCModel(const std::shared_ptr<linphone::AuthInfo> &authInfo, QObject *parent) {
auto replyHandler = new OAuthHttpServerReplyHandler(0, this);
mAuthInfo = authInfo;
mOidc.setReplyHandler(replyHandler);
mOidc.setAuthorizationUrl(QUrl(Utils::coreStringToAppString(authInfo->getAuthorizationServer())));
mOidc.setNetworkAccessManager(new QNetworkAccessManager(&mOidc));
mOidc.setClientIdentifier(OIDCClientId);
mAuthInfo->setClientId(OIDCClientId);
mOidc.setScope(OIDCScope);
mTimeout.setInterval(1000 * 60 * 2); // 2minutes
connect(&mTimeout, &QTimer::timeout, [this]() {
qWarning() << log().arg("Timeout reached for OpenID connection.");
dynamic_cast<OAuthHttpServerReplyHandler *>(mOidc.replyHandler())->close();
CoreModel::getInstance()->getCore()->abortAuthentication(mAuthInfo);
emit statusChanged("Timeout: Not authenticated");
emit finished();
});
connect(mOidc.networkAccessManager(), &QNetworkAccessManager::authenticationRequired,
[=](QNetworkReply *reply, QAuthenticator *authenticator) {
lWarning() << log().arg("authenticationRequired received but not implemented");
});
connect(&mOidc, &QOAuth2AuthorizationCodeFlow::statusChanged, [=](QAbstractOAuth::Status status) {
switch (status) {
case QAbstractOAuth::Status::Granted: {
mTimeout.stop();
emit statusChanged("Authentication granted");
emit authenticated();
break;
}
case QAbstractOAuth::Status::NotAuthenticated: {
mTimeout.stop();
emit statusChanged("Not authenticated");
emit finished();
break;
}
case QAbstractOAuth::Status::RefreshingToken: {
emit statusChanged("Refreshing token");
break;
}
case QAbstractOAuth::Status::TemporaryCredentialsReceived: {
emit statusChanged("Temporary credentials received");
break;
}
default: {
}
}
});
connect(&mOidc, &QOAuth2AuthorizationCodeFlow::requestFailed, [=](QAbstractOAuth::Error error) {
mTimeout.stop();
qWarning() << "RequestFailed:" << (int)error;
switch (error) {
case QAbstractOAuth::Error::NetworkError:
emit requestFailed("Network error");
break;
case QAbstractOAuth::Error::ServerError:
emit requestFailed("Server error");
break;
case QAbstractOAuth::Error::OAuthTokenNotFoundError:
emit requestFailed("OAuth token not found");
break;
case QAbstractOAuth::Error::OAuthTokenSecretNotFoundError:
emit requestFailed("OAuth token secret not found");
break;
case QAbstractOAuth::Error::OAuthCallbackNotVerified:
emit requestFailed("OAuth callback not verified");
break;
default: {
}
}
emit finished();
});
connect(&mOidc, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, [this](const QUrl &url) {
qDebug() << "Browser authentication url : " << url;
emit statusChanged("Requesting authorization from browser");
mTimeout.start();
QDesktopServices::openUrl(url);
});
connect(&mOidc, &QOAuth2AuthorizationCodeFlow::finished, [this](QNetworkReply *reply) {
connect(reply, &QNetworkReply::errorOccurred,
[this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString(); });
});
connect(this, &OIDCModel::authenticated, this, &OIDCModel::setBearers);
// in case we want to add parameters. Needed to override redirect_url
mOidc.setModifyParametersFunction([&, username = Utils::coreStringToAppString(authInfo->getUsername())](
QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant> *parameters) {
parameters->insert("login_hint", username);
parameters->replace("application_type", "native");
switch (stage) {
case QAbstractOAuth::Stage::RequestingAccessToken: {
emit statusChanged("Requesting access token");
break;
}
case QAbstractOAuth::Stage::RefreshingAccessToken: {
emit statusChanged("Refreshing access token");
break;
}
case QAbstractOAuth::Stage::RequestingAuthorization: {
emit statusChanged("Requesting authorization");
break;
}
case QAbstractOAuth::Stage::RequestingTemporaryCredentials: {
emit statusChanged("Requesting temporary credentials");
break;
}
default: {
}
}
});
connect(this, &OIDCModel::finished, this, &OIDCModel::deleteLater);
QNetworkRequest request(QUrl(Utils::coreStringToAppString(authInfo->getAuthorizationServer()) + OIDCWellKnown));
auto reply = mOidc.networkAccessManager()->get(request);
connect(reply, &QNetworkReply::finished, this, &OIDCModel::openIdConfigReceived);
}
void OIDCModel::openIdConfigReceived() {
auto reply = dynamic_cast<QNetworkReply *>(sender());
auto document = QJsonDocument::fromJson(reply->readAll());
if (document.isNull()) return;
auto rootArray = document.toVariant().toMap();
if (rootArray.contains("authorization_endpoint")) {
mOidc.setAuthorizationUrl(QUrl(rootArray["authorization_endpoint"].toString()));
}
if (rootArray.contains("token_endpoint")) {
mOidc.setAccessTokenUrl(QUrl(rootArray["token_endpoint"].toString()));
mAuthInfo->setTokenEndpointUri(
Utils::appStringToCoreString(QUrl(rootArray["token_endpoint"].toString()).toString()));
}
mOidc.grant();
reply->deleteLater();
}
void OIDCModel::setBearers() {
auto expiration = QDateTime::currentDateTime().secsTo(mOidc.expirationAt());
auto timeT = mOidc.expirationAt().toSecsSinceEpoch();
qDebug() << "Authenticated for " << expiration << "s";
auto refreshBearer =
linphone::Factory::get()->createBearerToken(Utils::appStringToCoreString(mOidc.refreshToken()), timeT);
auto accessBearer = linphone::Factory::get()->createBearerToken(Utils::appStringToCoreString(mOidc.token()), timeT);
mAuthInfo->setRefreshToken(refreshBearer);
mAuthInfo->setAccessToken(accessBearer);
CoreModel::getInstance()->getCore()->addAuthInfo(mAuthInfo);
emit CoreModel::getInstance()->bearerAccountAdded();
emit finished();
}