From ff12986452a51f9ed5d042f2b8d049c9848e51fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Sun, 12 Jan 2025 19:22:01 +0100 Subject: [PATCH] login: Move away from SSO term and add "Continue" button Moving away from the SSO term allows us to transition seemlessly towards OIDC. Adding a "Continue" button avoids to surprise the user by opening a URL without warning. Fixes SSO identity provider login in the process. --- .typos.toml | 1 + po/POTFILES.in | 4 +- src/login/homeserver_page.rs | 2 +- src/login/in_browser_page.rs | 145 ++++++++++++++++++ src/login/{sso_page.ui => in_browser_page.ui} | 46 +++++- src/login/method_page.rs | 8 +- src/login/mod.rs | 104 +++---------- src/login/mod.ui | 4 +- .../{idp_button.rs => sso_idp_button.rs} | 88 +++++++---- .../{idp_button.ui => sso_idp_button.ui} | 2 +- src/login/sso_page.rs | 48 ------ src/ui-resources.gresource.xml | 4 +- 12 files changed, 278 insertions(+), 178 deletions(-) create mode 100644 src/login/in_browser_page.rs rename src/login/{sso_page.ui => in_browser_page.ui} (57%) rename src/login/{idp_button.rs => sso_idp_button.rs} (59%) rename src/login/{idp_button.ui => sso_idp_button.ui} (80%) delete mode 100644 src/login/sso_page.rs diff --git a/.typos.toml b/.typos.toml index 9d361b20..e6839ffc 100644 --- a/.typos.toml +++ b/.typos.toml @@ -2,6 +2,7 @@ gir = "gir" inout = "inout" numer = "numer" # Short for numerator in GStreamer +ue = "ue" # End of word after mnemonic [type.po] extend-glob = ["*.po"] diff --git a/po/POTFILES.in b/po/POTFILES.in index 12ae55d6..74fa6f39 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -60,13 +60,13 @@ src/login/advanced_dialog.ui src/login/greeter.ui src/login/homeserver_page.rs src/login/homeserver_page.ui -src/login/idp_button.rs +src/login/in_browser_page.ui src/login/method_page.rs src/login/method_page.ui src/login/mod.rs src/login/mod.ui src/login/session_setup_view.ui -src/login/sso_page.ui +src/login/sso_idp_button.rs src/secret/linux.rs src/session/model/session.rs src/session/model/notifications/mod.rs diff --git a/src/login/homeserver_page.rs b/src/login/homeserver_page.rs index b60cb017..f3ce4586 100644 --- a/src/login/homeserver_page.rs +++ b/src/login/homeserver_page.rs @@ -242,7 +242,7 @@ mod imp { match handle.await.expect("task was not aborted") { Ok(res) => { login.set_login_types(res.flows); - login.show_login_screen(); + login.show_login_page(); } Err(error) => { warn!("Could not get available login types: {error}"); diff --git a/src/login/in_browser_page.rs b/src/login/in_browser_page.rs new file mode 100644 index 00000000..da858e9c --- /dev/null +++ b/src/login/in_browser_page.rs @@ -0,0 +1,145 @@ +use adw::{prelude::*, subclass::prelude::*}; +use gtk::{glib, CompositeTemplate}; +use ruma::api::client::session::SsoRedirectOidcAction; +use tracing::{error, warn}; +use url::Url; + +use super::Login; +use crate::{prelude::*, spawn, spawn_tokio, toast}; + +mod imp { + use std::cell::{Cell, RefCell}; + + use glib::subclass::InitializingObject; + + use super::*; + + #[derive(Debug, Default, CompositeTemplate, glib::Properties)] + #[template(resource = "/org/gnome/Fractal/ui/login/in_browser_page.ui")] + #[properties(wrapper_type = super::LoginInBrowserPage)] + pub struct LoginInBrowserPage { + #[template_child] + continue_btn: TemplateChild, + /// The ancestor `Login` object. + #[property(get, set, nullable)] + login: glib::WeakRef, + /// Whether we are logging in with OIDC compatibility. + #[property(get, set)] + oidc_compatibility: Cell, + /// The identity provider to use when logging in with SSO. + #[property(get, set, nullable)] + sso_idp_id: RefCell>, + } + + #[glib::object_subclass] + impl ObjectSubclass for LoginInBrowserPage { + const NAME: &'static str = "LoginInBrowserPage"; + type Type = super::LoginInBrowserPage; + type ParentType = adw::NavigationPage; + + fn class_init(klass: &mut Self::Class) { + Self::bind_template(klass); + Self::bind_template_callbacks(klass); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } + } + + #[glib::derived_properties] + impl ObjectImpl for LoginInBrowserPage {} + + impl WidgetImpl for LoginInBrowserPage { + fn grab_focus(&self) -> bool { + self.continue_btn.grab_focus() + } + } + + impl NavigationPageImpl for LoginInBrowserPage { + fn shown(&self) { + self.grab_focus(); + } + } + + #[gtk::template_callbacks] + impl LoginInBrowserPage { + /// Open the URL of the SSO login page. + #[template_callback] + async fn login_with_sso(&self) { + let Some(login) = self.login.upgrade() else { + return; + }; + + let client = login.client().await.expect("client was constructed"); + let oidc_compatibility = self.oidc_compatibility.get(); + let sso_idp_id = self.sso_idp_id.borrow().clone(); + + let handle = spawn_tokio!(async move { + let mut sso_login = client + .matrix_auth() + .login_sso(|sso_url| async move { + let ctx = glib::MainContext::default(); + ctx.spawn(async move { + spawn!(async move { + let mut sso_url = sso_url; + + if oidc_compatibility { + if let Ok(mut parsed_url) = Url::parse(&sso_url) { + // Add an action query parameter manually. + parsed_url.query_pairs_mut().append_pair( + "action", + SsoRedirectOidcAction::Login.as_str(), + ); + sso_url = parsed_url.into(); + } else { + // If parsing fails, just use the provided URL. + error!("Failed to parse SSO URL: {sso_url}"); + } + } + + if let Err(error) = gtk::UriLauncher::new(&sso_url) + .launch_future(gtk::Window::NONE) + .await + { + // FIXME: We should forward the error. + error!("Could not launch URI: {error}"); + } + }); + }); + Ok(()) + }) + .initial_device_display_name("Fractal"); + + if let Some(sso_idp_id) = &sso_idp_id { + sso_login = sso_login.identity_provider_id(sso_idp_id); + } + + sso_login.send().await + }); + + match handle.await.expect("task was not aborted") { + Ok(response) => { + login.handle_login_response(response).await; + } + Err(error) => { + warn!("Could not log in via SSO: {error}"); + let obj = self.obj(); + toast!(obj, error.to_user_facing()); + } + } + } + } +} + +glib::wrapper! { + /// A page shown while the user is logging in via SSO. + pub struct LoginInBrowserPage(ObjectSubclass) + @extends gtk::Widget, adw::NavigationPage, @implements gtk::Accessible; +} + +impl LoginInBrowserPage { + pub fn new() -> Self { + glib::Object::new() + } +} diff --git a/src/login/sso_page.ui b/src/login/in_browser_page.ui similarity index 57% rename from src/login/sso_page.ui rename to src/login/in_browser_page.ui index 8625b76d..03da736b 100644 --- a/src/login/sso_page.ui +++ b/src/login/in_browser_page.ui @@ -1,8 +1,8 @@ -