From e1b75074b501d2d3e0100d1170b3edef8a00799c Mon Sep 17 00:00:00 2001
From: Nicolas Werner <nicolas.werner@hotmail.de>
Date: Sun, 6 Mar 2022 19:49:12 +0100
Subject: [PATCH] Support explicit IDPs

---
 include/mtx/responses/login.hpp   | 35 +++++++++++++++++++++++++++++++
 include/mtxclient/http/client.hpp |  4 ++--
 lib/http/client.cpp               |  5 +++--
 lib/structs/responses/login.cpp   | 12 ++++++++++-
 4 files changed, 51 insertions(+), 5 deletions(-)

diff --git a/include/mtx/responses/login.hpp b/include/mtx/responses/login.hpp
index 33a4b5e2d..fb93ee253 100644
--- a/include/mtx/responses/login.hpp
+++ b/include/mtx/responses/login.hpp
@@ -40,11 +40,46 @@ struct Login
 void
 from_json(const nlohmann::json &obj, Login &response);
 
+//! Identity provider for SSO
+struct IdentityProvider
+{
+    //! Optional UI hint for what kind of common SSO provider is being described in this IdP. Matrix
+    //! maintains a registry of identifiers in the matrix-doc repo to ensure clients and servers are
+    //! aligned on major/common brands.
+    //!
+    //! Clients should prefer the brand over the icon, when both are provided. Clients are not
+    //! required to support any particular brand, including those in the registry, though are
+    //! expected to be able to present any IdP based off the name/icon to the user regardless.
+    //!
+    //! Unregistered brands are permitted using the Common Namespaced Identifier Grammar, though
+    //! excluding the namespace requirements. For example, examplesso is a valid brand which is not
+    //! in the registry but still permitted. Servers should be mindful that clients might not
+    //! support their unregistered brand usage as intended by the server.
+    std::string brand;
+    //! Optional MXC URI to provide an image/icon representing the IdP. Intended to be shown
+    //! alongside the name if provided.
+    std::string icon;
+    //! Required: Opaque string chosen by the homeserver, uniquely identifying the IdP from other
+    //! IdPs the homeserver might support. Should be between 1 and 255 characters in length,
+    //! containing unreserved characters under RFC 3986 (ALPHA DIGIT "-" / "." / "_" / "~"). Clients
+    //! are not intended to parse or infer meaning from opaque strings.
+    std::string id;
+    //! Required: Human readable description for the IdP, intended to be shown to the user.
+    std::string name;
+};
+void
+from_json(const nlohmann::json &obj, IdentityProvider &response);
+
 //! One supported login flow.
 struct LoginFlow
 {
     //! The authentication used for this flow.
     mtx::user_interactive::AuthType type;
+
+    //! Optional identity providers (IdPs) to present to the user. These would appear (typically) as
+    //! distinct buttons for the user to interact with, and would map to the appropriate
+    //! IdP-dependent redirect endpoint for that IdP.
+    std::vector<IdentityProvider> identity_providers;
 };
 void
 from_json(const nlohmann::json &obj, LoginFlow &response);
diff --git a/include/mtxclient/http/client.hpp b/include/mtxclient/http/client.hpp
index 019e96380..500d0cff6 100644
--- a/include/mtxclient/http/client.hpp
+++ b/include/mtxclient/http/client.hpp
@@ -255,9 +255,9 @@ public:
 
     //! Get the supported login flows
     void get_login(Callback<mtx::responses::LoginFlows> cb);
-    //! Get url to navigate to for sso login flow
+    //! Get url to navigate to for sso login flow, optionally preselecting an identity provider
     //! Open this in a browser
-    std::string login_sso_redirect(std::string redirectUrl);
+    std::string login_sso_redirect(std::string redirectUrl, const std::string &idp = "");
     //! Lookup real server to connect to.
     //! Call set_server with the returned homeserver url after this
     void well_known(Callback<mtx::responses::WellKnown> cb);
diff --git a/lib/http/client.cpp b/lib/http/client.cpp
index d303a79d5..b73b47d6b 100644
--- a/lib/http/client.cpp
+++ b/lib/http/client.cpp
@@ -258,10 +258,11 @@ Client::get_login(Callback<mtx::responses::LoginFlows> cb)
 }
 
 std::string
-Client::login_sso_redirect(std::string redirectUrl)
+Client::login_sso_redirect(std::string redirectUrl, const std::string &idp)
 {
+    const std::string idp_suffix = idp.empty() ? idp : ("/" + mtx::client::utils::url_encode(idp));
     return protocol_ + "://" + server() + ":" + std::to_string(port()) +
-           "/_matrix/client/r0/login/sso/redirect?" +
+           "/_matrix/client/r0/login/sso/redirect" + idp_suffix + "?" +
            mtx::client::utils::query_params({{"redirectUrl", redirectUrl}});
 }
 
diff --git a/lib/structs/responses/login.cpp b/lib/structs/responses/login.cpp
index 5ceaf4c38..32e16b984 100644
--- a/lib/structs/responses/login.cpp
+++ b/lib/structs/responses/login.cpp
@@ -20,10 +20,20 @@ from_json(const nlohmann::json &obj, Login &response)
         response.well_known = obj.at("well_known").get<WellKnown>();
 }
 
+void
+from_json(const nlohmann::json &obj, IdentityProvider &response)
+{
+    response.brand = obj.value("brand", "");
+    response.icon  = obj.value("icon", "");
+    response.id    = obj.at("id").get<std::string>();
+    response.name  = obj.at("name").get<std::string>();
+}
+
 void
 from_json(const nlohmann::json &obj, LoginFlow &response)
 {
-    response.type = obj.at("type").get<std::string>();
+    response.type               = obj.at("type").get<std::string>();
+    response.identity_providers = obj.value("identity_providers", std::vector<IdentityProvider>{});
 }
 void
 from_json(const nlohmann::json &obj, LoginFlows &response)
-- 
GitLab