Skip to content

Commit

Permalink
Support SSO logins via the external web browser (#1504)
Browse files Browse the repository at this point in the history
  • Loading branch information
rumtid authored Dec 28, 2023
1 parent 8bd84f8 commit 189d1a4
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 11 deletions.
10 changes: 5 additions & 5 deletions src/account-mgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -516,8 +516,8 @@ void AccountManager::updateServerInfoForAllAccounts()
void AccountManager::updateAccountServerInfo(const Account& account)
{
ServerInfoRequest *request = new ServerInfoRequest(account);
connect(request, SIGNAL(success(const Account&, const ServerInfo &)),
this, SLOT(serverInfoSuccess(const Account&, const ServerInfo &)));
connect(request, SIGNAL(success(const ServerInfo &)),
this, SLOT(serverInfoSuccess(const ServerInfo &)));
connect(request, SIGNAL(failed(const ApiError&)),
this, SLOT(serverInfoFailed(const ApiError&)));
request->send();
Expand All @@ -543,12 +543,12 @@ void AccountManager::updateAccountInfo(const Account& account,
}


void AccountManager::serverInfoSuccess(const Account &_account, const ServerInfo &info)
void AccountManager::serverInfoSuccess(const ServerInfo &info)
{
ServerInfoRequest *req = (ServerInfoRequest *)(sender());
req->deleteLater();

Account account = _account;
Account account(req->account());
account.serverInfo = info;

setServerInfoKeyValue(db, account, kVersionKeyName, info.getVersionString());
Expand All @@ -557,7 +557,7 @@ void AccountManager::serverInfoSuccess(const Account &_account, const ServerInfo
setServerInfoKeyValue(db, account, kCustomLogoKeyName, info.customLogo);
setServerInfoKeyValue(db, account, kCustomBrandKeyName, info.customBrand);

bool changed = _account.serverInfo != info;
bool changed = req->account().serverInfo != info;
if (!changed)
return;

Expand Down
2 changes: 1 addition & 1 deletion src/account-mgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public slots:
void reloginAccount(const Account &account);

private slots:
void serverInfoSuccess(const Account &account, const ServerInfo &info);
void serverInfoSuccess(const ServerInfo &info);
void serverInfoFailed(const ApiError&);

void onAccountsChanged();
Expand Down
71 changes: 70 additions & 1 deletion src/api/requests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const char* kRemoteWipeReportUrl = "api2/device-wiped/";
const char* kSearchUsersUrl = "api2/search-user/";
const char* kGetThumbnailUrl = "api2/repos/%1/thumbnail/";
const char* kCreateFileUploadLink = "api/v2.1/upload-links/";
const char* kClientSSOLinkUrl = "api2/client-sso-link/";
const char* kClientSSOStatusUrl = "api2/client-sso-link/%1/";

const char* kGetFileActivitiesUrl = "api/v2.1/activities/";
} // namespace
Expand Down Expand Up @@ -715,6 +717,12 @@ ServerInfoRequest::ServerInfoRequest(const Account& account)
{
}

ServerInfoRequest::ServerInfoRequest(const QUrl& server_url)
: SeafileApiRequest(urlJoin(server_url, kServerInfoUrl),
SeafileApiRequest::METHOD_GET)
{
}

void ServerInfoRequest::requestSuccess(QNetworkReply& reply)
{
json_error_t error;
Expand Down Expand Up @@ -750,7 +758,7 @@ void ServerInfoRequest::requestSuccess(QNetworkReply& reply)
ret.customBrand = dict["desktop-custom-brand"].toString();
}

emit success(account_, ret);
emit success(ret);
}

LogoutDeviceRequest::LogoutDeviceRequest(const Account& account)
Expand Down Expand Up @@ -1429,3 +1437,64 @@ void GetUploadFileLinkRequest::requestSuccess(QNetworkReply& reply)
} while (0);
emit failed(ApiError::fromHttpError(500));
}

ClientSSOLinkRequest::ClientSSOLinkRequest(const QUrl& server_url)
: SeafileApiRequest(urlJoin(server_url, kClientSSOLinkUrl),
SeafileApiRequest::METHOD_POST)
{
}

void ClientSSOLinkRequest::requestSuccess(QNetworkReply& reply)
{
json_error_t error;
json_t *root = parseJSON(reply, &error);
if (!root) {
qWarning() << "ClientSSOLinkRequest: failed to parse json:" << error.text;
emit failed(ApiError::fromJsonError());
return;
}

QScopedPointer<json_t, JsonPointerCustomDeleter> json(root);
QMap<QString, QVariant> dict = mapFromJSON(json.data(), &error);
QString link = dict["link"].toString();
if (link.isEmpty()) {
qWarning() << "ClientSSOLinkRequest: failed to parse link";
emit failed(ApiError::fromJsonError());
return;
}

emit success(link);
}

ClientSSOStatusRequest::ClientSSOStatusRequest(const QUrl& server_url, const QString& token)
: SeafileApiRequest(urlJoin(server_url, QString(kClientSSOStatusUrl).arg(token)),
SeafileApiRequest::METHOD_GET)
{
}

void ClientSSOStatusRequest::requestSuccess(QNetworkReply& reply)
{
json_error_t error;
json_t *root = parseJSON(reply, &error);
if (!root) {
qWarning() << "ClientSSOStatusRequest: failed to parse json:" << error.text;
emit failed(ApiError::fromJsonError());
return;
}

QScopedPointer<json_t, JsonPointerCustomDeleter> json(root);
QMap<QString, QVariant> dict = mapFromJSON(json.data(), &error);

ClientSSOStatus status;
status.status = dict["status"].toString();
status.username = dict["username"].toString();
status.api_token = dict["apiToken"].toString();

if (status.status.isEmpty()) {
qWarning() << "ClientSSOStatusRequest: failed to parse status";
emit failed(ApiError::fromJsonError());
return;
}

emit success(status);
}
43 changes: 42 additions & 1 deletion src/api/requests.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "api/server-repo.h"
#include "api/starred-file.h"
#include "api/event.h"
#include "api/sso-status.h"

class QNetworkReply;
class QImage;
Expand Down Expand Up @@ -406,9 +407,15 @@ class ServerInfoRequest : public SeafileApiRequest
Q_OBJECT
public:
ServerInfoRequest(const Account& account);
ServerInfoRequest(const QUrl& server_url);

const Account& account() const
{
return account_;
}

signals:
void success(const Account& account, const ServerInfo& info);
void success(const ServerInfo& info);

protected slots:
void requestSuccess(QNetworkReply& reply);
Expand Down Expand Up @@ -887,4 +894,38 @@ protected slots:
Q_DISABLE_COPY(GetUploadFileLinkRequest);
};

class ClientSSOLinkRequest : public SeafileApiRequest
{
Q_OBJECT

public:
ClientSSOLinkRequest(const QUrl& server_url);

signals:
void success(const QString& link);

protected slots:
void requestSuccess(QNetworkReply& reply);

private:
Q_DISABLE_COPY(ClientSSOLinkRequest)
};

class ClientSSOStatusRequest : public SeafileApiRequest
{
Q_OBJECT

public:
ClientSSOStatusRequest(const QUrl& server_url, const QString& token);

signals:
void success(const ClientSSOStatus& status);

protected slots:
void requestSuccess(QNetworkReply& reply);

private:
Q_DISABLE_COPY(ClientSSOStatusRequest)
};

#endif // SEAFILE_CLIENT_API_REQUESTS_H
3 changes: 3 additions & 0 deletions src/api/server-info.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class ServerInfo {
bool officePreview;
bool fileSearch;
bool disableSyncWithAnyFolder;
bool clientSSOViaLocalBrowser;
QString customBrand;
QString customLogo;

Expand Down Expand Up @@ -66,6 +67,8 @@ class ServerInfo {
fileSearch = value;
} else if (key == "disable-sync-with-any-folder") {
disableSyncWithAnyFolder = value;
} else if (key == "client-sso-via-local-browser") {
clientSSOViaLocalBrowser = value;
} else {
return false;
}
Expand Down
14 changes: 14 additions & 0 deletions src/api/sso-status.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef SEAFILE_CLIENT_API_SSO_STATUS_H
#define SEAFILE_CLIENT_API_SSO_STATUS_H

#include <QString>

class ClientSSOStatus
{
public:
QString status;
QString username;
QString api_token;
};

#endif // SEAFILE_CLIENT_API_SSO_STATUS_H
114 changes: 111 additions & 3 deletions src/ui/login-dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent)
request_ = NULL;
account_info_req_ = NULL;
is_remember_device_ = false;
connect(&client_sso_timer_, SIGNAL(timeout()),
this, SLOT(checkClientSSOStatus()));

mStatusText->setText("");
mLogo->setPixmap(QPixmap(":/images/seafile-32.png"));
Expand Down Expand Up @@ -432,10 +434,116 @@ void LoginDialog::loginWithShib()
}

seafApplet->settingsManager()->setLastShibUrl(url.toString());
sso_server_ = url;

ServerInfoRequest *request = new ServerInfoRequest(sso_server_);
connect(request, SIGNAL(success(const ServerInfo&)),
this, SLOT(serverInfoSuccess(const ServerInfo&)));
connect(request, SIGNAL(failed(const ApiError&)),
this, SLOT(serverInfoFailed(const ApiError&)));
request->send();
}

void LoginDialog::serverInfoSuccess(const ServerInfo& info)
{
if (!info.clientSSOViaLocalBrowser) {
ShibLoginDialog shib_dialog(sso_server_, mComputerName->text(), this);
if (shib_dialog.exec() == QDialog::Accepted) {
accept();
}
return;
}

qInfo() << "begin sso login via local browser";

ClientSSOLinkRequest *request = new ClientSSOLinkRequest(sso_server_);
connect(request, SIGNAL(success(const QString&)),
this, SLOT(clientSSOLinkSuccess(const QString&)));
connect(request, SIGNAL(failed(const ApiError&)),
this, SLOT(clientSSOLinkFailed(const ApiError&)));
request->send();
}


void LoginDialog::serverInfoFailed(const ApiError& error)
{
showWarning(tr("Failed to get server info. Please check the server address."));
}

void LoginDialog::clientSSOLinkSuccess(const QString& link)
{
QString sso_link(link);

qInfo() << "open sso link in browser:" << sso_link;
QDesktopServices::openUrl(QUrl(sso_link));

ShibLoginDialog shib_dialog(url, mComputerName->text(), this);
if (shib_dialog.exec() == QDialog::Accepted) {
accept();
QRegularExpression re("/client-sso/([^/]+)");
QRegularExpressionMatch match = re.match(sso_link);
if (!match.hasMatch()) {
qWarning() << "failed to parse token from client sso link" << sso_link;
return;
}

QString token = match.captured(1);
if (token.isEmpty()) {
qWarning() << "failed to parse token from client sso link" << sso_link;
return;
}
sso_token_ = token;

client_sso_timer_.start(3000);
client_sso_success_ = false;
}

void LoginDialog::clientSSOLinkFailed(const ApiError& error)
{
showWarning(tr("Failed to get client sso link."));
}

void LoginDialog::checkClientSSOStatus()
{
ClientSSOStatusRequest *request = new ClientSSOStatusRequest(sso_server_, sso_token_);
connect(request, SIGNAL(success(const ClientSSOStatus&)),
this, SLOT(clientSSOStatusSuccess(const ClientSSOStatus&)));
connect(request, SIGNAL(failed(const ApiError&)),
this, SLOT(clientSSOStatusFailed(const ApiError&)));
request->send();
}

void LoginDialog::clientSSOStatusSuccess(const ClientSSOStatus& status)
{
if (status.status == "waiting") {
qInfo() << "waiting for client sso login";
return;
} else if (status.status != "success") {
qWarning() << "client sso login failed:" << status.status;
showWarning(tr("SSO login failed."));
client_sso_timer_.stop();
return;
}
client_sso_timer_.stop();

// avoid multiple success signals
if (client_sso_success_) {
return;
}
client_sso_success_ = true;

qInfo() << "client sso login success" << status.username;

Account account(sso_server_, status.username, status.api_token);
account_info_req_ = new FetchAccountInfoRequest(account);
connect(account_info_req_, SIGNAL(success(const AccountInfo&)),
this, SLOT(onFetchAccountInfoSuccess(const AccountInfo&)));
connect(account_info_req_, SIGNAL(failed(const ApiError&)),
this, SLOT(onFetchAccountInfoFailed(const ApiError&)));
account_info_req_->send();
}

void LoginDialog::clientSSOStatusFailed(const ApiError& error)
{
showWarning(tr("Failed to get client sso status."));
client_sso_timer_.stop();
}

#endif // HAVE_SHIBBOLETH_SUPPORT
Loading

0 comments on commit 189d1a4

Please sign in to comment.