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: */

Reply via email to