Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 58 additions & 10 deletions src/gui/newwizard/jobs/discoverwebfingerservicejobfactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <QJsonObject>
#include <QJsonParseError>
#include <QNetworkReply>
#include <QUrl>

namespace OCC::Wizard::Jobs {

Expand Down Expand Up @@ -73,20 +74,67 @@ CoreJob *DiscoverWebFingerServiceJobFactory::startJob(const QUrl &url, QObject *
return;
}

// check for an OIDC issuer in the list of links provided (we use the first that matches our conditions)
// Check for an OIDC issuer in the list of links provided.
// We prioritize a desktop-specific issuer over the generic one to support
// identity providers that require separate OIDC client configurations per application type.
// See: https://github.com/opencloud-eu/desktop/issues/246
const auto links = doc.object().value(QStringLiteral("links")).toArray();
for (const auto &link : links) {
const auto linkObject = link.toObject();

if (linkObject.value(QStringLiteral("rel")).toString() == QStringLiteral("http://openid.net/specs/connect/1.0/issuer")) {
// we have good faith in the server to provide a meaningful value and do not have to validate this any further
const auto href = linkObject.value(QStringLiteral("href")).toString();
setJobResult(job, href);
return;

// Helper struct to hold issuer info including optional client_id from properties
struct IssuerInfo {
QString href;
QString clientId;
};

// Helper lambda to find an OIDC issuer with a specific relation type
// Also extracts the client_id from properties if present
auto findIssuerByRelation = [&links](const QString &relation) -> IssuerInfo {
for (const auto &link : links) {
const auto linkObject = link.toObject();
if (linkObject.value(QStringLiteral("rel")).toString() == relation) {
IssuerInfo info;
info.href = linkObject.value(QStringLiteral("href")).toString();
// Check for client_id in properties
const auto properties = linkObject.value(QStringLiteral("properties")).toObject();
const QString clientIdProperty = QStringLiteral("http://openid.net/specs/connect/1.0/client_id");
if (properties.contains(clientIdProperty)) {
info.clientId = properties.value(clientIdProperty).toString();
}
return info;
}
}
return {};
};

// First, try desktop-specific OIDC issuer
const QString desktopIssuerRel = QStringLiteral("http://openid.net/specs/connect/1.0/issuer/desktop");
IssuerInfo issuerInfo = findIssuerByRelation(desktopIssuerRel);
if (!issuerInfo.href.isEmpty()) {
qCInfo(lcDiscoverWebFingerService) << u"using desktop-specific OIDC issuer:" << issuerInfo.href;
if (!issuerInfo.clientId.isEmpty()) {
qCInfo(lcDiscoverWebFingerService) << u"using desktop-specific client_id:" << issuerInfo.clientId;
}
// Return both issuer URL and client_id as a map
QVariantMap result;
result[QStringLiteral("issuer")] = QUrl::fromUserInput(issuerInfo.href);
result[QStringLiteral("clientId")] = issuerInfo.clientId;
setJobResult(job, result);
return;
}

// Fall back to generic OIDC issuer
const QString genericIssuerRel = QStringLiteral("http://openid.net/specs/connect/1.0/issuer");
issuerInfo = findIssuerByRelation(genericIssuerRel);
if (!issuerInfo.href.isEmpty()) {
qCDebug(lcDiscoverWebFingerService) << u"using generic OIDC issuer:" << issuerInfo.href;
QVariantMap result;
result[QStringLiteral("issuer")] = QUrl::fromUserInput(issuerInfo.href);
result[QStringLiteral("clientId")] = QString(); // No client_id for generic issuer
setJobResult(job, result);
return;
}

qCWarning(lcDiscoverWebFingerService) << u"could not find suitable relation in WebFinger response";
qCWarning(lcDiscoverWebFingerService) << u"could not find OIDC issuer relation in WebFinger response";
setInvalidReplyError();
});

Expand Down
12 changes: 11 additions & 1 deletion src/gui/newwizard/setupwizardaccountbuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ AccountPtr SetupWizardAccountBuilder::build() const
// TODO: perhaps _authenticationStrategy->setUpAccountPtr(...) would be more elegant? no need for getters then
newAccountPtr->setCredentials(_authenticationStrategy->makeCreds());
newAccountPtr->credentials()->persist();
OAuth::persist(newAccountPtr, _authenticationStrategy->dynamicRegistrationData(), _authenticationStrategy->idToken());
OAuth::persist(newAccountPtr, _authenticationStrategy->dynamicRegistrationData(), _authenticationStrategy->idToken(), _webFingerDesktopClientId);

newAccountPtr->setDavDisplayName(_displayName);

Expand Down Expand Up @@ -166,6 +166,16 @@ QUrl SetupWizardAccountBuilder::webFingerAuthenticationServerUrl() const
return _webFingerAuthenticationServerUrl;
}

void SetupWizardAccountBuilder::setWebFingerDesktopClientId(const QString &clientId)
{
_webFingerDesktopClientId = clientId;
}

QString SetupWizardAccountBuilder::webFingerDesktopClientId() const
{
return _webFingerDesktopClientId;
}

void SetupWizardAccountBuilder::setWebFingerInstances(const QVector<QUrl> &instancesList)
{
_webFingerInstances = instancesList;
Expand Down
4 changes: 4 additions & 0 deletions src/gui/newwizard/setupwizardaccountbuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ class SetupWizardAccountBuilder
void setWebFingerAuthenticationServerUrl(const QUrl &url);
QUrl webFingerAuthenticationServerUrl() const;

void setWebFingerDesktopClientId(const QString &clientId);
QString webFingerDesktopClientId() const;

void setWebFingerInstances(const QVector<QUrl> &instancesList);
QVector<QUrl> webFingerInstances() const;

Expand All @@ -108,6 +111,7 @@ class SetupWizardAccountBuilder
QUrl _serverUrl;

QUrl _webFingerAuthenticationServerUrl;
QString _webFingerDesktopClientId;
QVector<QUrl> _webFingerInstances;
QUrl _webFingerSelectedInstance;

Expand Down
8 changes: 8 additions & 0 deletions src/gui/newwizard/states/oauthcredentialssetupwizardstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ OAuthCredentialsSetupWizardState::OAuthCredentialsSetupWizardState(SetupWizardCo
}();

auto oAuth = new OAuth(authServerUrl, _context->accessManager(), {}, this);

// Use the desktop-specific client_id if provided by webfinger
// See: https://github.com/opencloud-eu/desktop/issues/246
const QString desktopClientId = _context->accountBuilder().webFingerDesktopClientId();
if (!desktopClientId.isEmpty()) {
oAuth->setClientId(desktopClientId);
}

_page = new OAuthCredentialsSetupWizardPage(oAuth, authServerUrl);


Expand Down
8 changes: 7 additions & 1 deletion src/gui/newwizard/states/serverurlsetupwizardstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,13 @@ void ServerUrlSetupWizardState::evaluatePage()
if (!checkWebFingerAuthJob->success()) {
Q_EMIT evaluationSuccessful();
} else {
_context->accountBuilder().setWebFingerAuthenticationServerUrl(checkWebFingerAuthJob->result().toUrl());
// Result is now a QVariantMap with "issuer" and "clientId" keys
const auto resultMap = checkWebFingerAuthJob->result().toMap();
_context->accountBuilder().setWebFingerAuthenticationServerUrl(resultMap.value(QStringLiteral("issuer")).toUrl());
const QString clientId = resultMap.value(QStringLiteral("clientId")).toString();
if (!clientId.isEmpty()) {
_context->accountBuilder().setWebFingerDesktopClientId(clientId);
}
Q_EMIT evaluationSuccessful();
}
});
Expand Down
34 changes: 32 additions & 2 deletions src/libsync/creds/oauth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ QString idTokenC()
return QStringLiteral("oauth/id_token");
}

QString desktopClientIdC()
{
return QStringLiteral("oauth/desktopClientId");
}

QVariant getRequiredField(const QVariantMap &json, const QString &s, QString *error)
{
const auto out = json.constFind(s);
Expand Down Expand Up @@ -481,7 +486,12 @@ QString OAuth::clientSecret() const
return _clientSecret;
}

void OAuth::persist(const OCC::AccountPtr &accountPtr, const QVariantMap &dynamicRegistrationData, const IdToken &idToken)
void OAuth::setClientId(const QString &clientId)
{
_clientId = clientId;
}

void OAuth::persist(const OCC::AccountPtr &accountPtr, const QVariantMap &dynamicRegistrationData, const IdToken &idToken, const QString &desktopClientId)
{
if (!dynamicRegistrationData.isEmpty()) {
accountPtr->credentialManager()->set(dynamicRegistrationDataC(), dynamicRegistrationData);
Expand All @@ -493,6 +503,13 @@ void OAuth::persist(const OCC::AccountPtr &accountPtr, const QVariantMap &dynami
} else {
accountPtr->credentialManager()->clear(idTokenC());
}
// Store desktop-specific client_id from webfinger if provided
// See: https://github.com/opencloud-eu/desktop/issues/246
if (!desktopClientId.isEmpty()) {
accountPtr->credentialManager()->set(desktopClientIdC(), desktopClientId);
} else {
accountPtr->credentialManager()->clear(desktopClientIdC());
}
}

void OAuth::updateDynamicRegistration()
Expand Down Expand Up @@ -708,7 +725,20 @@ void AccountBasedOAuth::restore()
logCredentialsJobResult(credentialsJob);

_dynamicRegistrationData = credentialsJob->data().value<QVariantMap>();
Q_EMIT restored(QPrivateSignal());

// Also restore the desktop-specific client_id if stored
// See: https://github.com/opencloud-eu/desktop/issues/246
auto clientIdJob = _account->credentialManager()->get(desktopClientIdC());
connect(clientIdJob, &CredentialJob::finished, this, [this, clientIdJob] {
if (clientIdJob->error() == QKeychain::NoError) {
const QString storedClientId = clientIdJob->data().toString();
if (!storedClientId.isEmpty()) {
qCInfo(lcOauth) << u"restored desktop-specific client_id";
setClientId(storedClientId);
}
}
Q_EMIT restored(QPrivateSignal());
});
});
});
}
Expand Down
9 changes: 8 additions & 1 deletion src/libsync/creds/oauth.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,14 @@ class OPENCLOUD_SYNC_EXPORT OAuth : public QObject
QString clientId() const;
QString clientSecret() const;

static void persist(const AccountPtr &accountPtr, const QVariantMap &dynamicRegistrationData, const IdToken &idToken);
/**
* Set a custom client_id to use instead of the theme default.
* This is used when the server provides a desktop-specific client_id via webfinger.
* Must be called before startAuthentication().
*/
void setClientId(const QString &clientId);

static void persist(const AccountPtr &accountPtr, const QVariantMap &dynamicRegistrationData, const IdToken &idToken, const QString &desktopClientId = {});

Q_SIGNALS:
/**
Expand Down