From 8171f2ceb2f31dbc648c08c7b1aee9a102f3afc8 Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Mon, 1 Jul 2024 16:48:13 +0200 Subject: [PATCH] SSO --- Linphone/CMakeLists.txt | 2 +- Linphone/core/App.cpp | 5 +- Linphone/core/account/AccountList.cpp | 6 +- Linphone/core/notifier/Notifier.cpp | 3 +- Linphone/model/CMakeLists.txt | 2 + Linphone/model/auth/OIDCModel.cpp | 190 ++++++++++++++++++++++++++ Linphone/model/auth/OIDCModel.hpp | 52 +++++++ Linphone/model/core/CoreModel.cpp | 13 ++ Linphone/model/core/CoreModel.hpp | 3 + external/linphone-sdk | 2 +- 10 files changed, 269 insertions(+), 9 deletions(-) create mode 100644 Linphone/model/auth/OIDCModel.cpp create mode 100644 Linphone/model/auth/OIDCModel.hpp diff --git a/Linphone/CMakeLists.txt b/Linphone/CMakeLists.txt index 659486d5f..8211bf2da 100644 --- a/Linphone/CMakeLists.txt +++ b/Linphone/CMakeLists.txt @@ -22,7 +22,7 @@ set(APP_TARGETS ${LinphoneCxx_TARGET} ${LibLinphone_TARGET})#MediastreamerUtils set(QT_DEFAULT_MAJOR_VERSION 6) -set(QT_PACKAGES Core Quick Qml Widgets Svg Multimedia Test)# Search Core at first for initialize Qt scripts for next find_packages. +set(QT_PACKAGES Core Quick Qml Widgets Svg Multimedia Test NetworkAuth)# Search Core at first for initialize Qt scripts for next find_packages. if (UNIX AND NOT APPLE) list(APPEND QT_PACKAGES DBus) endif() diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index 5dcd8a778..657437139 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -203,9 +203,6 @@ void App::init() { createCommandParser(); // Recreate parser in order to use translations from config. mParser->process(*this); - if (mParser->isSet("verbose")) QtLogger::enableVerbose(true); - if (mParser->isSet("qt-logs-only")) QtLogger::enableQtOnly(true); - if (!mLinphoneThread->isRunning()) { lDebug() << log().arg("Starting Thread"); mLinphoneThread->start(); @@ -218,6 +215,8 @@ void App::init() { void App::initCore() { // Core. Manage the logger so it must be instantiate at first. CoreModel::create("", mLinphoneThread); + if (mParser->isSet("verbose")) QtLogger::enableVerbose(true); + if (mParser->isSet("qt-logs-only")) QtLogger::enableQtOnly(true); QMetaObject::invokeMethod( mLinphoneThread->getThreadId(), [this]() mutable { diff --git a/Linphone/core/account/AccountList.cpp b/Linphone/core/account/AccountList.cpp index 920cec1f5..4b4ffa514 100644 --- a/Linphone/core/account/AccountList.cpp +++ b/Linphone/core/account/AccountList.cpp @@ -77,8 +77,10 @@ void AccountList::setSelf(QSharedPointer me) { mModelConnection->makeConnectToModel( &CoreModel::defaultAccountChanged, [this](const std::shared_ptr &core, const std::shared_ptr &account) { - auto model = AccountCore::create(account); - mModelConnection->invokeToCore([this, model]() { setDefaultAccount(model); }); + if (account) { + auto model = AccountCore::create(account); + mModelConnection->invokeToCore([this, model]() { setDefaultAccount(model); }); + } else mModelConnection->invokeToCore([this]() { setDefaultAccount(nullptr); }); }); lUpdate(); } diff --git a/Linphone/core/notifier/Notifier.cpp b/Linphone/core/notifier/Notifier.cpp index 1396e7be1..a35973141 100644 --- a/Linphone/core/notifier/Notifier.cpp +++ b/Linphone/core/notifier/Notifier.cpp @@ -120,9 +120,8 @@ Notifier::~Notifier() { const int nComponents = Notifications.size(); if (mComponents) { - for (int i = 0; i < nComponents; ++i) - if (mComponents[i]) mComponents[i]->deleteLater(); delete[] mComponents; + mComponents = nullptr; } } diff --git a/Linphone/model/CMakeLists.txt b/Linphone/model/CMakeLists.txt index d8fde253b..bd7d1019f 100644 --- a/Linphone/model/CMakeLists.txt +++ b/Linphone/model/CMakeLists.txt @@ -4,6 +4,8 @@ list(APPEND _LINPHONEAPP_SOURCES model/account/AccountManagerServicesModel.cpp model/account/AccountManagerServicesRequestModel.cpp + model/auth/OIDCModel.cpp + model/call/CallModel.cpp model/call-history/CallHistoryModel.cpp diff --git a/Linphone/model/auth/OIDCModel.cpp b/Linphone/model/auth/OIDCModel.cpp new file mode 100644 index 000000000..c73aeae1b --- /dev/null +++ b/Linphone/model/auth/OIDCModel.cpp @@ -0,0 +1,190 @@ +/* + * 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 . + */ + +#include "OIDCModel.hpp" + +#include +#include + +#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 &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); + + 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: { + emit statusChanged("Authentication granted"); + emit authenticated(); + break; + } + case QAbstractOAuth::Status::NotAuthenticated: { + 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) { + 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"); + 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 *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(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()); + qDebug() << "Authenticated for " << expiration << "s"; + auto refreshBearer = + linphone::Factory::get()->createBearerToken(Utils::appStringToCoreString(mOidc.refreshToken()), expiration); + + auto accessBearer = + linphone::Factory::get()->createBearerToken(Utils::appStringToCoreString(mOidc.token()), expiration); + mAuthInfo->setRefreshToken(refreshBearer); + mAuthInfo->setAccessToken(accessBearer); + CoreModel::getInstance()->getCore()->addAuthInfo(mAuthInfo); + emit finished(); +} diff --git a/Linphone/model/auth/OIDCModel.hpp b/Linphone/model/auth/OIDCModel.hpp new file mode 100644 index 000000000..6a68c5e64 --- /dev/null +++ b/Linphone/model/auth/OIDCModel.hpp @@ -0,0 +1,52 @@ +/* + * 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 . + */ + +#ifndef OIDC_MODEL_H_ +#define OIDC_MODEL_H_ + +#include "tool/AbstractObject.hpp" +#include +#include + +// ============================================================================= + +class OIDCModel : public QObject, public AbstractObject { + Q_OBJECT + +public: + OIDCModel(const std::shared_ptr &authInfo, QObject *parent = Q_NULLPTR); + + void openIdConfigReceived(); + void setBearers(); + +signals: + void authenticated(); + void requestFailed(const QString &error); + void statusChanged(const QString &status); + void finished(); + +private: + QOAuth2AuthorizationCodeFlow mOidc; + std::shared_ptr mAuthInfo; + + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/model/core/CoreModel.cpp b/Linphone/model/core/CoreModel.cpp index eae5bf5c2..2bcfc755b 100644 --- a/Linphone/model/core/CoreModel.cpp +++ b/Linphone/model/core/CoreModel.cpp @@ -235,6 +235,19 @@ void CoreModel::onAccountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &authInfo, linphone::AuthMethod method) { + if (method == linphone::AuthMethod::Bearer) { + auto serverUrl = authInfo->getAuthorizationServer(); + auto username = authInfo->getUsername(); + auto realm = authInfo->getRealm(); + if (!serverUrl.empty()) { + qDebug() << "onAuthenticationRequested for Bearer. Initialize OpenID connection for " << username.c_str() + << " at " << serverUrl.c_str(); + QString key = Utils::coreStringToAppString(username) + '@' + Utils::coreStringToAppString(realm) + ' ' + + Utils::coreStringToAppString(serverUrl); + if (mOpenIdConnections.contains(key)) mOpenIdConnections[key]->deleteLater(); + mOpenIdConnections[key] = new OIDCModel(authInfo, this); + } + } emit authenticationRequested(core, authInfo, method); } void CoreModel::onCallEncryptionChanged(const std::shared_ptr &core, diff --git a/Linphone/model/core/CoreModel.hpp b/Linphone/model/core/CoreModel.hpp index 3fe589aa1..d47af6da8 100644 --- a/Linphone/model/core/CoreModel.hpp +++ b/Linphone/model/core/CoreModel.hpp @@ -21,6 +21,7 @@ #ifndef CORE_MODEL_H_ #define CORE_MODEL_H_ +#include #include #include #include @@ -29,6 +30,7 @@ #include #include "model/account/AccountManager.hpp" +#include "model/auth/OIDCModel.hpp" #include "model/cli/CliModel.hpp" #include "model/listener/Listener.hpp" #include "model/logger/LoggerModel.hpp" @@ -73,6 +75,7 @@ signals: private: QString mConfigPath; QTimer *mIterateTimer = nullptr; + QMap mOpenIdConnections; void setPathBeforeCreation(); void setPathsAfterCreation(); diff --git a/external/linphone-sdk b/external/linphone-sdk index fecd61150..09ec61ae5 160000 --- a/external/linphone-sdk +++ b/external/linphone-sdk @@ -1 +1 @@ -Subproject commit fecd61150bd1d08d85b4888f3e5bd00630cb94b7 +Subproject commit 09ec61ae54e4f972ac00bf5b20dd48e4aad867b1