config_host/config_oauth2.h.in | 8 svtools/source/dialogs/PlaceEditDialog.cxx | 6 svtools/source/dialogs/ServerDetailsControls.cxx | 16 + svtools/source/dialogs/ServerDetailsControls.hxx | 2 uui/Library_uui.mk | 2 uui/source/iahndl-authentication.cxx | 28 + uui/source/iahndl-oauth2-win.cxx | 328 +++++++++++++++++++++++ uui/source/iahndl-oauth2.hxx | 56 +++ 8 files changed, 437 insertions(+), 9 deletions(-)
New commits: commit 964bcfa28751a4ad861323185547f194452f3080 Author: Mike Kaganski <[email protected]> AuthorDate: Sun Oct 12 20:08:53 2025 +0500 Commit: Mike Kaganski <[email protected]> CommitDate: Sun Oct 26 16:54:25 2025 +0100 tdf#101630: Use redirect URL for browser-based OAuth2 To interact with an OAuth2 provider, we must open a browser with the URL of the provider, and in that URL encode our target URL to use to send information about the result of authentication (either failure, or the access tokens). The target URL can either be an HTTP URL, or a custom URI scheme [1]. The custom scheme looks attractive, because it doesn't need to listen to HTTP, and would receive the data through command line arguments. However, this technique requires to register a custom URI handler on the system; and that can't be done temporarily, for the current app, on some platforms; so if we decided to use the custom URI scheme, it would only work for installed app, not for portable one. Using HTTP URL, it can be either an external one (which would require to run an online service for authentication, which would receive the data from OAuth2 providers, and then communicate it to the app that requested it - with all kinds of security implications), or it can be a loopback URL to a local HTTP server. The latter option has no HTTPS requirement (the sensitive data is sent through HTTP locally, never leaving the host; the problem with HTTPS is that we would need a cert for the local system, trusted by browsers); that is implemented here: a local server is established by the app itself, before sending the auth request. This change only provides implementation for Windows. To have a cross- platform solution, another HTTP server implementation is needed, e.g. using boost/beast. This also removes password prompt from the File Services dialog for Google Drive and OneDrive, when the new method is implemented (i.e., currently only on Windows). [1] https://www.oauth.com/oauth2-servers/oauth-native-apps/redirect-urls-for-native-apps/ Change-Id: I4684477c73a54370721e700fa95fc5f14344cf60 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192253 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> diff --git a/config_host/config_oauth2.h.in b/config_host/config_oauth2.h.in index 9945dda3dda5..8b5bd3db0045 100644 --- a/config_host/config_oauth2.h.in +++ b/config_host/config_oauth2.h.in @@ -13,6 +13,8 @@ #ifndef CONFIG_OAUTH2_H #define CONFIG_OAUTH2_H +/* 49152..65535 is ephemeral port number range recommended by IANA */ +#define OAUTH2_REDIRECT_URI_PREFIX "http://localhost:49153/LibreOffice/oauth2/" /* Google Drive settings */ #define GDRIVE_BASE_URL "https://www.googleapis.com/drive/v3" @@ -20,7 +22,7 @@ #define GDRIVE_CLIENT_SECRET "" #define GDRIVE_AUTH_URL "https://accounts.google.com/o/oauth2/v2/auth" #define GDRIVE_TOKEN_URL "https://oauth2.googleapis.com/token" -#define GDRIVE_REDIRECT_URI "urn:ietf:wg:oauth:2.0:oob" +#define GDRIVE_REDIRECT_URI OAUTH2_REDIRECT_URI_PREFIX "GDRIVE/" #define GDRIVE_SCOPE "https://www.googleapis.com/auth/drive.file" @@ -43,6 +45,10 @@ #define ONEDRIVE_REDIRECT_URI "http://localhost/LibreOffice" #define ONEDRIVE_SCOPE "Files.ReadWrite offline_access" +#if defined _WIN32 +#define OAUTH2REQUEST_SUPPORTED +#endif + #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/dialogs/PlaceEditDialog.cxx b/svtools/source/dialogs/PlaceEditDialog.cxx index c7d1ef3dc854..b3ea52c05eed 100644 --- a/svtools/source/dialogs/PlaceEditDialog.cxx +++ b/svtools/source/dialogs/PlaceEditDialog.cxx @@ -356,9 +356,9 @@ void PlaceEditDialog::SelectType(bool bSkipSeparator) m_xCurrentDetails->set_visible(true); - m_xCBPassword->set_visible( m_bShowPassword && m_xCurrentDetails->enableUserCredentials() ); - m_xEDPassword->set_visible( m_bShowPassword && m_xCurrentDetails->enableUserCredentials() ); - m_xFTPasswordLabel->set_visible( m_bShowPassword && m_xCurrentDetails->enableUserCredentials() ); + m_xCBPassword->set_visible(m_bShowPassword && m_xCurrentDetails->enablePassword()); + m_xEDPassword->set_visible(m_bShowPassword && m_xCurrentDetails->enablePassword()); + m_xFTPasswordLabel->set_visible(m_bShowPassword && m_xCurrentDetails->enablePassword()); m_xEDUsername->set_visible( m_xCurrentDetails->enableUserCredentials() ); m_xFTUsernameLabel->set_visible( m_xCurrentDetails->enableUserCredentials() ); diff --git a/svtools/source/dialogs/ServerDetailsControls.cxx b/svtools/source/dialogs/ServerDetailsControls.cxx index 65f15d2e10f5..befc1bc6e1cb 100644 --- a/svtools/source/dialogs/ServerDetailsControls.cxx +++ b/svtools/source/dialogs/ServerDetailsControls.cxx @@ -306,6 +306,13 @@ void CmisDetailsContainer::set_visible( bool bShow ) m_pDialog->m_xRepositoryBox->hide(); m_pDialog->m_xEDRoot->hide(); m_pDialog->m_xFTRoot->hide(); +#if defined OAUTH2REQUEST_SUPPORTED + if (m_sBinding == GDRIVE_BASE_URL || m_sBinding == ONEDRIVE_BASE_URL) + { + m_pDialog->m_xFTShare->hide(); + m_pDialog->m_xEDShare->hide(); + } +#endif } else { @@ -322,6 +329,15 @@ void CmisDetailsContainer::set_visible( bool bShow ) m_pDialog->m_xFTPort->set_sensitive( !bShow ); } +bool CmisDetailsContainer::enablePassword() +{ +#if defined OAUTH2REQUEST_SUPPORTED + if (m_sBinding == GDRIVE_BASE_URL || m_sBinding == ONEDRIVE_BASE_URL) + return false; +#endif + return DetailsContainer::enablePassword(); +} + INetURLObject CmisDetailsContainer::getUrl( ) { OUString sBindingUrl = m_pDialog->m_xEDHost->get_text().trim(); diff --git a/svtools/source/dialogs/ServerDetailsControls.hxx b/svtools/source/dialogs/ServerDetailsControls.hxx index 6b40e9efe0d9..b2f5dc774cd4 100644 --- a/svtools/source/dialogs/ServerDetailsControls.hxx +++ b/svtools/source/dialogs/ServerDetailsControls.hxx @@ -54,6 +54,7 @@ class DetailsContainer virtual void setPassword( const OUString& ) { }; virtual bool enableUserCredentials( ) { return true; }; + virtual bool enablePassword() { return enableUserCredentials(); } protected: void notifyChange( ); @@ -126,6 +127,7 @@ class CmisDetailsContainer final : public DetailsContainer CmisDetailsContainer(PlaceEditDialog* pDialog, OUString sBinding); virtual void set_visible( bool bShow ) override; + virtual bool enablePassword() override; virtual INetURLObject getUrl( ) override; virtual bool setUrl( const INetURLObject& rUrl ) override; virtual void setUsername( const OUString& rUsername ) override; diff --git a/uui/Library_uui.mk b/uui/Library_uui.mk index a04f3de5b051..0e82e131ba21 100644 --- a/uui/Library_uui.mk +++ b/uui/Library_uui.mk @@ -17,6 +17,7 @@ $(eval $(call gb_Library_set_include,uui,\ $(eval $(call gb_Library_set_componentfile,uui,uui/util/uui,services)) $(eval $(call gb_Library_use_external,uui,boost_headers)) +$(eval $(call gb_Library_use_external,uui,curl)) $(eval $(call gb_Library_use_custom_headers,uui,\ officecfg/registry \ @@ -51,6 +52,7 @@ $(eval $(call gb_Library_add_exception_objects,uui,\ uui/source/iahndl-filter \ uui/source/iahndl-ioexceptions \ uui/source/iahndl-locking \ + $(if $(filter MSC,$(COM)),uui/source/iahndl-oauth2-win) \ uui/source/iahndl-ssl \ uui/source/interactionhandler \ uui/source/lockfailed \ diff --git a/uui/source/iahndl-authentication.cxx b/uui/source/iahndl-authentication.cxx index b09159fb099c..4d7eeadacb42 100644 --- a/uui/source/iahndl-authentication.cxx +++ b/uui/source/iahndl-authentication.cxx @@ -17,6 +17,10 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ +#include <sal/config.h> + +#include <config_oauth2.h> + #include <com/sun/star/task/DocumentPasswordRequest.hpp> #include <com/sun/star/task/DocumentPasswordRequest2.hpp> #include <com/sun/star/task/DocumentMSPasswordRequest.hpp> @@ -51,6 +55,7 @@ #include "passworddlg.hxx" #include "iahndl.hxx" +#include "iahndl-oauth2.hxx" #include <memory> @@ -762,16 +767,29 @@ UUIInteractionHelper::handleAuthFallbackRequest( const OUString & instructions, const OUString & url, uno::Sequence< uno::Reference< task::XInteractionContinuation > > const & rContinuations ) { - uno::Reference<awt::XWindow> xParent = getParentXWindow(); - AuthFallbackDlg dlg(Application::GetFrameWeld(xParent), instructions, url); - int retCode = dlg.run(); + OUString code; +#if defined OAUTH2REQUEST_SUPPORTED + if (url.indexOf(OAUTH2_REDIRECT_URI_PREFIX) >= 0) + { + OAuth2Request request(url); + request.run(); + code = request.getRetCode(); + } + else +#endif + { + uno::Reference<awt::XWindow> xParent = getParentXWindow(); + AuthFallbackDlg dlg(Application::GetFrameWeld(xParent), instructions, url); + if (dlg.run() == RET_OK) + code = dlg.GetCode(); + } uno::Reference< task::XInteractionAbort > xAbort; uno::Reference< ucb::XInteractionAuthFallback > xAuthFallback; getContinuations(rContinuations, &xAbort, &xAuthFallback); - if( retCode == RET_OK && xAuthFallback.is( ) ) + if (!code.isEmpty() && xAuthFallback.is()) { - xAuthFallback->setCode(dlg.GetCode()); + xAuthFallback->setCode(code); xAuthFallback->select( ); } } diff --git a/uui/source/iahndl-oauth2-win.cxx b/uui/source/iahndl-oauth2-win.cxx new file mode 100644 index 000000000000..f7744de996b4 --- /dev/null +++ b/uui/source/iahndl-oauth2-win.cxx @@ -0,0 +1,328 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <config_oauth2.h> + +#include "iahndl-oauth2.hxx" + +// Has to be before vcl/svapp.hxx +#include <prewin.h> +#include <http.h> +#include <postwin.h> + +#pragma comment(lib, "httpapi.lib") + +#include <comphelper/anytostring.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <o3tl/temporary.hxx> +#include <osl/thread.h> +#include <rtl/uri.hxx> +#include <sal/log.hxx> +#include <sal/types.h> +#include <systools/curlinit.hxx> +#include <tools/link.hxx> +#include <unotools/configmgr.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include <curl/curl.h> + +#include <com/sun/star/system/SystemShellExecute.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> + +static OUString getQueryComponent(std::u16string_view query, std::u16string_view name) +{ + auto i = query.find(name); + for (;; i = query.find(name, i + 1)) + { + if (i == std::u16string_view::npos || query.size() <= i + name.size()) + return {}; + if ((i == 0 || query[i - 1] == '&' || query[i - 1] == '?') && query[i + name.size()] == '=') + break; // found + } + + auto rest = query.substr(i + name.size() + 1); + // up to next component or fragment or end of URL + return rtl::Uri::decode(OUString(rest.substr(0, rest.find_first_of(u"&#"))), + rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8); +} + +struct OAuth2Request::Impl +{ + std::unique_ptr<weld::MessageDialog> cancelBox; + OUString authUrl; + OUString redirectUrl; + OUString retCode; + int initStage = 0; + + HTTP_URL_GROUP_ID groupId{}; + HTTP_BINDING_INFO bindingInfo{ .Flags = { .Present = 1 }, .RequestQueueHandle = nullptr }; + + Impl(const OUString& auth_url) + : cancelBox(Application::CreateMessageDialog( + nullptr, VclMessageType::Info, VclButtonsType::Cancel, + u"Login to Google in the opened browser window."_ustr)) + , authUrl(auth_url) + { + cancelBox->set_title(u"Waiting for authentication"_ustr); + } + + DECL_LINK(CloseDialog, void*, void); + bool initHTTP(); + void deinitHTTP(); + static void SAL_CALL listenHTTP(void* pThis); + bool openBrowser() const; + void sendResponse(HTTP_REQUEST_ID id, std::string_view body = {}) const; + void sendCloseResponse(HTTP_REQUEST_ID id, std::u16string_view error) const; +}; + +OAuth2Request::OAuth2Request(const OUString& auth_url) + : impl(std::make_unique<Impl>(auth_url)) +{ +} + +OAuth2Request::~OAuth2Request() = default; // for Impl destruction + +IMPL_LINK_NOARG(OAuth2Request::Impl, CloseDialog, void*, void) +{ + cancelBox->response(retCode.isEmpty() ? VclResponseType::RET_CANCEL : VclResponseType::RET_OK); +} + +// Create a minimal HTTP server on localhost, which will handle the redirect from OAuth2 server. +void OAuth2Request::run() +{ + if (impl->initHTTP()) + { + if (auto thread = osl_createThread(Impl::listenHTTP, impl.get())) + { + if (impl->openBrowser()) + impl->cancelBox->run(); + + // Send DELETE request + CURL* curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, + OUStringToOString(impl->redirectUrl, RTL_TEXTENCODING_UTF8).getStr()); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_easy_perform(curl); + curl_easy_cleanup(curl); + + osl_joinWithThread(thread); + } + } + impl->deinitHTTP(); // Cleans up everything possibly initialized, even on initHTTP failure +} + +const OUString& OAuth2Request::getRetCode() const { return impl->retCode; } + +bool OAuth2Request::Impl::initHTTP() +{ + redirectUrl = getQueryComponent(authUrl, u"redirect_uri"); + if (!redirectUrl.startsWith(OAUTH2_REDIRECT_URI_PREFIX)) + return false; + + ULONG result = HttpInitialize(HTTPAPI_VERSION_2, HTTP_INITIALIZE_SERVER, 0); + if (result != NO_ERROR) + { + SAL_WARN("ucb.ucp.cmis", "HttpInitialize failed with code " << result); + return false; + } + initStage = 1; + + HTTP_SERVER_SESSION_ID serverSessionId; + result = HttpCreateServerSession(HTTPAPI_VERSION_2, &serverSessionId, 0); + if (result != NO_ERROR) + { + SAL_WARN("ucb.ucp.cmis", "HttpCreateServerSession failed with code " << result); + return false; + } + + result = HttpCreateUrlGroup(serverSessionId, &groupId, 0); + if (result != NO_ERROR) + { + SAL_WARN("ucb.ucp.cmis", "HttpCreateUrlGroup failed with code " << result); + return false; + } + + result = HttpCreateRequestQueue(HTTPAPI_VERSION_2, nullptr, nullptr, 0, + &bindingInfo.RequestQueueHandle); + if (result != NO_ERROR) + { + SAL_WARN("ucb.ucp.cmis", "HttpCreateRequestQueue failed with code " << result); + return false; + } + initStage = 2; + + result = HttpSetUrlGroupProperty(groupId, HttpServerBindingProperty, &bindingInfo, + sizeof(bindingInfo)); + if (result != NO_ERROR) + { + SAL_WARN("ucb.ucp.cmis", "HttpSetUrlGroupProperty failed with code " << result); + return false; + } + initStage = 3; + + result = HttpAddUrlToUrlGroup(groupId, o3tl::toW(redirectUrl.getStr()), 0, 0); + if (result != NO_ERROR) + { + SAL_WARN("ucb.ucp.cmis", "HttpAddUrlToUrlGroup failed with code " << result); + return false; + } + initStage = 4; + return true; +} + +void OAuth2Request::Impl::deinitHTTP() +{ + switch (initStage) + { + case 4: + HttpRemoveUrlFromUrlGroup(groupId, o3tl::toW(redirectUrl.getStr()), 0); + [[fallthrough]]; + case 3: + HttpSetUrlGroupProperty(groupId, HttpServerBindingProperty, + &o3tl::temporary(HTTP_BINDING_INFO{ + .Flags = { .Present = 0 }, .RequestQueueHandle = nullptr }), + sizeof(HTTP_BINDING_INFO)); + [[fallthrough]]; + case 2: + HttpShutdownRequestQueue(bindingInfo.RequestQueueHandle); + [[fallthrough]]; + case 1: + HttpTerminate(HTTP_INITIALIZE_SERVER, nullptr); + } +} + +// The server must handle the redirect data -> send HTML with a prompt to close the current page; +// and DELETE requests, which mean stop the operation. + +// static +void SAL_CALL OAuth2Request::Impl::listenHTTP(void* pThis) +{ + Impl* impl = static_cast<Impl*>(pThis); + assert(impl); + + for (;;) + { + char buf[sizeof(HTTP_REQUEST) + 0x10000]{}; // 64K for the data + PHTTP_REQUEST request = reinterpret_cast<PHTTP_REQUEST>(buf); + ULONG result = HttpReceiveHttpRequest(impl->bindingInfo.RequestQueueHandle, HTTP_NULL_ID, + HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, request, + std::size(buf), &o3tl::temporary(ULONG()), nullptr); + if (result != NO_ERROR) + { + SAL_WARN("ucb.ucp.cmis", "HttpReceiveHttpRequest failed with code " << result); + return; + } + + if (request->Verb == HttpVerbDELETE) + { + SAL_INFO("ucb.ucp.cmis", "DELETE request received"); + impl->sendResponse(request->RequestId); // send 200 "OK" + // No need to send anything to the dialog + return; + } + + if (request->Verb = HttpVerbGET) + { + std::u16string_view query(o3tl::toU(request->CookedUrl.pQueryString)); + if (auto code = getQueryComponent(query, u"code"); !code.isEmpty()) + { + impl->retCode = code; + impl->sendCloseResponse(request->RequestId, {}); + continue; + } + else if (auto error = getQueryComponent(query, u"error"); !error.isEmpty()) + { + impl->sendCloseResponse(request->RequestId, error); + continue; + } + } + + impl->sendResponse(request->RequestId); // just send 200 OK + } +} + +bool OAuth2Request::Impl::openBrowser() const +{ + assert(!authUrl.isEmpty()); + try + { + auto exec( + css::system::SystemShellExecute::create(comphelper::getProcessComponentContext())); + exec->execute(authUrl, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY); + return true; + } + catch (const css::uno::Exception&) + { + css::uno::Any exc(::cppu::getCaughtException()); + OUString msg(::comphelper::anyToString(exc)); + const SolarMutexGuard guard; + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog( + nullptr, VclMessageType::Warning, VclButtonsType::Ok, msg)); + xErrorBox->set_title("title"); + xErrorBox->run(); + xErrorBox.reset(); + return false; + } +} + +void OAuth2Request::Impl::sendResponse(HTTP_REQUEST_ID id, std::string_view body) const +{ + HTTP_RESPONSE response{}; + response.StatusCode = 200; + + static constexpr std::string_view ok("OK"); + response.pReason = ok.data(); + response.ReasonLength = ok.size(); + + static constexpr std::string_view text_html("text/html"); + response.Headers.KnownHeaders[HttpHeaderContentType].pRawValue = text_html.data(); + response.Headers.KnownHeaders[HttpHeaderContentType].RawValueLength = text_html.size(); + + if (!body.empty()) + { + HTTP_DATA_CHUNK chunk{ .DataChunkType = HttpDataChunkFromMemory, + .FromMemory = { .pBuffer = const_cast<char*>(body.data()), + .BufferLength = static_cast<ULONG>(body.size()) } }; + response.EntityChunkCount = 1; + response.pEntityChunks = &chunk; + } + + HttpSendHttpResponse(bindingInfo.RequestQueueHandle, id, 0, &response, nullptr, nullptr, + nullptr, 0, nullptr, nullptr); +} + +void OAuth2Request::Impl::sendCloseResponse(HTTP_REQUEST_ID id, std::u16string_view error) const +{ + static constexpr OUString html_tmpl(u"<!DOCTYPE html>" + "<html>" + "<head>" + "<meta charset=\"UTF-8\">" + "<title>Close Window</title>" + "</head>" + "<body>" + "<p>$RESULT</p>" + "<p>Close the page to return to $PRODUCTNAME</p>" + "</body>" + "</html>"_ustr); + OUString result = retCode.isEmpty() || !error.empty() ? u"Authorization error: "_ustr + error + : u"Authorization successful."_ustr; + OUString u16 = html_tmpl // replace the placeholders + .replaceFirst("$RESULT", result) + .replaceFirst("$PRODUCTNAME", utl::ConfigManager::getProductName()); + sendResponse(id, OUStringToOString(u16, RTL_TEXTENCODING_UTF8)); + + // Executed in the VCL thread, inside the dialog's run() + Application::PostUserEvent(LINK(const_cast<Impl*>(this), Impl, CloseDialog)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/uui/source/iahndl-oauth2.hxx b/uui/source/iahndl-oauth2.hxx new file mode 100644 index 000000000000..140dce870a65 --- /dev/null +++ b/uui/source/iahndl-oauth2.hxx @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <sal/config.h> + +#include <config_oauth2.h> + +#if defined OAUTH2REQUEST_SUPPORTED + +#include <rtl/ustring.hxx> + +#include <memory> + +/// The class that incapsulates OAuth2 interaction. +/// It is instantiated with an auth URL, usually generated by libcmis. +/// 1. It extracts redirect URL from auth URL using OAUTH2_REDIRECT_URI_PREFIX from config_oauth2.h. +/// 2. It creates a minimal localhost webserver listening to the redirect URL. +/// 3. It opens a web browser with the auth URL (the user will authenticate there, and authorize +/// the application for the access). +/// 4. It creates a message box with a Cancel button, allowing to stop the procedure. +/// 5. The auth provider (opened at #3) will eventually redirect to the localhost address listened +/// by the webserver created at #2. It may be a success, or a failure. +/// 6. The task of webserver is to handle the query of the redirect URL (from #5). If it's success, +/// it must store the auth code, close message box (#4) with success code, and exit. In case of +/// failure, it must close the message box with Cancel (which will eventually terminate the +/// webserver at #7). In both success and failure cases, it must send a web page with a text to +/// close the page, because it's impossible to auto-close it from the page script. +/// Here it may be required to distinguish the OAuth2 providers. The OAUTH2_REDIRECT_URI_PREFIX +/// allows to do that: the rest of the redirect URL is supposed to define the provider, as +/// defined in config_oauth2.h. +/// 7. If the message box (#4) is dismissed using Cancel, the webserver is terminated. +class OAuth2Request +{ +public: + OAuth2Request(const OUString& auth_url); + ~OAuth2Request(); + + void run(); + const OUString& getRetCode() const; + +private: + struct Impl; + std::unique_ptr<Impl> impl; +}; + +#endif // OAUTH2REQUEST_SUPPORTED + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
