From b8f3e91e458b6c615dab9de14f61ef9d545f040f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Fri, 6 Feb 2026 16:47:21 +0100 Subject: [PATCH] login: Remove support for SSO identity providers Instead of having support for a handful of SSO providers on our end, the user is always redirected to the default SSO URL where they will be presented with all the providers supported by the homeserver. This matches the UX of OAuth 2.0 where we can only redirect the user to the homeserver's UI, and allows us to remove a bunch of code and files. --- .../icons/scalable/actions/idp-apple-dark.svg | 1 - .../icons/scalable/actions/idp-apple.svg | 1 - .../icons/scalable/actions/idp-facebook.svg | 1 - .../scalable/actions/idp-github-dark.svg | 1 - .../icons/scalable/actions/idp-github.svg | 1 - .../icons/scalable/actions/idp-gitlab.svg | 1 - .../scalable/actions/idp-google-dark.svg | 1 - .../icons/scalable/actions/idp-google.svg | 1 - .../icons/scalable/actions/idp-x-dark.svg | 1 - .../icons/scalable/actions/idp-x-light.svg | 1 - data/resources/resources.gresource.xml | 10 - po/POTFILES.in | 1 - src/login/method_page.blp | 16 +- src/login/method_page.rs | 64 +----- src/login/mod.rs | 27 ++- src/login/sso_idp_button.blp | 10 - src/login/sso_idp_button.rs | 183 ------------------ src/ui-blueprint-resources.in | 1 - 18 files changed, 21 insertions(+), 301 deletions(-) delete mode 100644 data/resources/icons/scalable/actions/idp-apple-dark.svg delete mode 100644 data/resources/icons/scalable/actions/idp-apple.svg delete mode 100644 data/resources/icons/scalable/actions/idp-facebook.svg delete mode 100644 data/resources/icons/scalable/actions/idp-github-dark.svg delete mode 100644 data/resources/icons/scalable/actions/idp-github.svg delete mode 100644 data/resources/icons/scalable/actions/idp-gitlab.svg delete mode 100644 data/resources/icons/scalable/actions/idp-google-dark.svg delete mode 100644 data/resources/icons/scalable/actions/idp-google.svg delete mode 100644 data/resources/icons/scalable/actions/idp-x-dark.svg delete mode 100644 data/resources/icons/scalable/actions/idp-x-light.svg delete mode 100644 src/login/sso_idp_button.blp delete mode 100644 src/login/sso_idp_button.rs diff --git a/data/resources/icons/scalable/actions/idp-apple-dark.svg b/data/resources/icons/scalable/actions/idp-apple-dark.svg deleted file mode 100644 index a1c99081..00000000 --- a/data/resources/icons/scalable/actions/idp-apple-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/data/resources/icons/scalable/actions/idp-apple.svg b/data/resources/icons/scalable/actions/idp-apple.svg deleted file mode 100644 index 39d8356c..00000000 --- a/data/resources/icons/scalable/actions/idp-apple.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/data/resources/icons/scalable/actions/idp-facebook.svg b/data/resources/icons/scalable/actions/idp-facebook.svg deleted file mode 100644 index 9aabcdc5..00000000 --- a/data/resources/icons/scalable/actions/idp-facebook.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/data/resources/icons/scalable/actions/idp-github-dark.svg b/data/resources/icons/scalable/actions/idp-github-dark.svg deleted file mode 100644 index fee0a9fc..00000000 --- a/data/resources/icons/scalable/actions/idp-github-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/data/resources/icons/scalable/actions/idp-github.svg b/data/resources/icons/scalable/actions/idp-github.svg deleted file mode 100644 index cb628a47..00000000 --- a/data/resources/icons/scalable/actions/idp-github.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/data/resources/icons/scalable/actions/idp-gitlab.svg b/data/resources/icons/scalable/actions/idp-gitlab.svg deleted file mode 100644 index 47087deb..00000000 --- a/data/resources/icons/scalable/actions/idp-gitlab.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/data/resources/icons/scalable/actions/idp-google-dark.svg b/data/resources/icons/scalable/actions/idp-google-dark.svg deleted file mode 100644 index 11614169..00000000 --- a/data/resources/icons/scalable/actions/idp-google-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/data/resources/icons/scalable/actions/idp-google.svg b/data/resources/icons/scalable/actions/idp-google.svg deleted file mode 100644 index 11614169..00000000 --- a/data/resources/icons/scalable/actions/idp-google.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/data/resources/icons/scalable/actions/idp-x-dark.svg b/data/resources/icons/scalable/actions/idp-x-dark.svg deleted file mode 100644 index 5ba1a1eb..00000000 --- a/data/resources/icons/scalable/actions/idp-x-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/data/resources/icons/scalable/actions/idp-x-light.svg b/data/resources/icons/scalable/actions/idp-x-light.svg deleted file mode 100644 index 0a74df05..00000000 --- a/data/resources/icons/scalable/actions/idp-x-light.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml index ce2b0019..43acd04d 100644 --- a/data/resources/resources.gresource.xml +++ b/data/resources/resources.gresource.xml @@ -19,16 +19,6 @@ icons/scalable/actions/go-next-symbolic.svg icons/scalable/actions/go-previous-symbolic.svg icons/scalable/actions/hide-symbolic.svg - icons/scalable/actions/idp-apple-dark.svg - icons/scalable/actions/idp-apple.svg - icons/scalable/actions/idp-facebook.svg - icons/scalable/actions/idp-github-dark.svg - icons/scalable/actions/idp-github.svg - icons/scalable/actions/idp-gitlab.svg - icons/scalable/actions/idp-google-dark.svg - icons/scalable/actions/idp-google.svg - icons/scalable/actions/idp-x-dark.svg - icons/scalable/actions/idp-x-light.svg icons/scalable/actions/map-marker-symbolic.svg icons/scalable/actions/media-playback-pause-symbolic.svg icons/scalable/actions/media-playback-start-symbolic.svg diff --git a/po/POTFILES.in b/po/POTFILES.in index 44cde3a7..31ec53a5 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -99,7 +99,6 @@ src/login/method_page.rs src/login/mod.blp src/login/mod.rs src/login/session_setup_view.blp -src/login/sso_idp_button.rs src/secret/linux.rs src/session/mod.rs src/session/notifications/mod.rs diff --git a/src/login/method_page.blp b/src/login/method_page.blp index 35223cd0..e06657db 100644 --- a/src/login/method_page.blp +++ b/src/login/method_page.blp @@ -113,28 +113,16 @@ template $LoginMethodPage: Adw.NavigationPage { } } - Gtk.Box sso_idp_box { - visible: false; - spacing: 12; - homogeneous: true; - hexpand: true; - vexpand: true; - - accessibility { - label: _("Single Sign-On Providers"); - } - } - - Gtk.Button more_sso_btn { + Gtk.Button sso_button { styles [ "standalone-button", "pill", ] + label: _("Login via SSO"); can-shrink: true; halign: center; action-name: "login.sso"; - action-target: "@ms nothing"; } $LoadingButton next_button { diff --git a/src/login/method_page.rs b/src/login/method_page.rs index f69cb2ea..8722a984 100644 --- a/src/login/method_page.rs +++ b/src/login/method_page.rs @@ -1,16 +1,14 @@ use adw::{prelude::*, subclass::prelude::*}; use gettextrs::gettext; use gtk::glib; -use ruma::{OwnedServerName, api::client::session::get_login_types::v3::LoginType}; +use ruma::OwnedServerName; use tracing::warn; use url::Url; -use super::{Login, sso_idp_button::SsoIdpButton}; +use super::Login; use crate::{components::LoadingButton, gettext_f, prelude::*, spawn_tokio, toast}; mod imp { - use std::cell::RefCell; - use glib::subclass::InitializingObject; use super::*; @@ -28,10 +26,7 @@ mod imp { #[template_child] password_entry: TemplateChild, #[template_child] - sso_idp_box: TemplateChild, - sso_idp_box_children: RefCell>, - #[template_child] - more_sso_btn: TemplateChild, + sso_button: TemplateChild, #[template_child] next_button: TemplateChild, /// The parent `Login` object. @@ -108,45 +103,8 @@ mod imp { } /// Update the SSO group. - pub(super) fn update_sso(&self, login_types: Vec) { - let Some(sso_login) = login_types.into_iter().find_map(|t| match t { - LoginType::Sso(sso) => Some(sso), - _ => None, - }) else { - self.sso_idp_box.set_visible(false); - self.more_sso_btn.set_visible(false); - return; - }; - - self.clean_idp_box(); - - let mut has_unknown_methods = false; - let mut has_known_methods = false; - - if !sso_login.identity_providers.is_empty() { - let mut sso_idp_box_children = self.sso_idp_box_children.borrow_mut(); - sso_idp_box_children.reserve(sso_login.identity_providers.len()); - - for identity_provider in sso_login.identity_providers { - if let Some(btn) = SsoIdpButton::new(identity_provider) { - self.sso_idp_box.append(&btn); - sso_idp_box_children.push(btn); - - has_known_methods = true; - } else { - has_unknown_methods = true; - } - } - } - self.sso_idp_box.set_visible(has_known_methods); - - if has_known_methods { - self.more_sso_btn.set_label(&gettext("More SSO Providers")); - self.more_sso_btn.set_visible(has_unknown_methods); - } else { - self.more_sso_btn.set_label(&gettext("Login via SSO")); - self.more_sso_btn.set_visible(true); - } + pub(super) fn update_sso(&self, supports_sso: bool) { + self.sso_button.set_visible(supports_sso); } /// Whether the current state allows to login with a password. @@ -210,14 +168,6 @@ mod imp { self.password_entry.set_text(""); self.next_button.set_is_loading(false); self.update_next_state(); - self.clean_idp_box(); - } - - /// Empty the identity providers box. - fn clean_idp_box(&self) { - for child in self.sso_idp_box_children.borrow_mut().drain(..) { - self.sso_idp_box.remove(&child); - } } } } @@ -239,11 +189,11 @@ impl LoginMethodPage { &self, homeserver_url: &Url, domain_name: Option<&OwnedServerName>, - login_types: Vec, + supports_sso: bool, ) { let imp = self.imp(); imp.update_title(homeserver_url, domain_name); - imp.update_sso(login_types); + imp.update_sso(supports_sso); imp.update_next_state(); } diff --git a/src/login/mod.rs b/src/login/mod.rs index 9c9da4bf..a0869a6d 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -23,7 +23,6 @@ mod in_browser_page; mod local_server; mod method_page; mod session_setup_view; -mod sso_idp_button; use self::{ advanced_dialog::LoginAdvancedDialog, @@ -105,14 +104,9 @@ mod imp { klass.set_css_name("login"); klass.set_accessible_role(gtk::AccessibleRole::Group); - klass.install_action_async( - "login.sso", - Some(&Option::::static_variant_type()), - |obj, _, variant| async move { - let idp = variant.and_then(|v| v.get::>()).flatten(); - obj.imp().init_matrix_sso_login(idp).await; - }, - ); + klass.install_action_async("login.sso", None, |obj, _, _| async move { + obj.imp().init_matrix_sso_login().await; + }); klass.install_action_async("login.open-advanced", None, |obj, _, _| async move { obj.imp().open_advanced_dialog().await; @@ -328,6 +322,9 @@ mod imp { let supports_password = login_types .iter() .any(|login_type| matches!(login_type, LoginType::Password(_))); + let supports_sso = login_types + .iter() + .any(|login_type| matches!(login_type, LoginType::Sso(_))); if supports_password { let server_name = self @@ -336,14 +333,14 @@ mod imp { .then(|| self.homeserver_page.homeserver()) .and_then(|s| sanitize_server_name(&s).ok()); - self.show_method_page(&client.homeserver(), server_name.as_ref(), login_types); + self.show_method_page(&client.homeserver(), server_name.as_ref(), supports_sso); } else { - self.init_matrix_sso_login(None).await; + self.init_matrix_sso_login().await; } } /// Prepare to log in via the Matrix SSO API. - pub(super) async fn init_matrix_sso_login(&self, idp: Option) { + pub(super) async fn init_matrix_sso_login(&self) { let Some(client) = self.client.borrow().clone() else { return; }; @@ -355,7 +352,7 @@ mod imp { let matrix_auth = client.matrix_auth(); let handle = spawn_tokio!(async move { matrix_auth - .get_sso_login_url(redirect_uri.as_str(), idp.as_deref()) + .get_sso_login_url(redirect_uri.as_str(), None) .await }); @@ -376,10 +373,10 @@ mod imp { &self, homeserver: &Url, server_name: Option<&OwnedServerName>, - login_types: Vec, + supports_sso: bool, ) { self.method_page - .update(homeserver, server_name, login_types); + .update(homeserver, server_name, supports_sso); self.navigation.push_by_tag(LoginPage::Method.as_ref()); } diff --git a/src/login/sso_idp_button.blp b/src/login/sso_idp_button.blp deleted file mode 100644 index 3f85812f..00000000 --- a/src/login/sso_idp_button.blp +++ /dev/null @@ -1,10 +0,0 @@ -using Gtk 4.0; - -template $SsoIdpButton: Gtk.Button { - action-name: "login.sso"; - - styles [ - "card", - "sso-button", - ] -} diff --git a/src/login/sso_idp_button.rs b/src/login/sso_idp_button.rs deleted file mode 100644 index d5e2dce8..00000000 --- a/src/login/sso_idp_button.rs +++ /dev/null @@ -1,183 +0,0 @@ -use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*}; -use matrix_sdk::ruma::api::client::session::get_login_types::v3::{ - IdentityProvider, IdentityProviderBrand, -}; - -use crate::gettext_f; - -mod imp { - use std::{cell::OnceCell, marker::PhantomData}; - - use glib::subclass::InitializingObject; - - use super::*; - - #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)] - #[template(resource = "/org/gnome/Fractal/ui/login/sso_idp_button.ui")] - #[properties(wrapper_type = super::SsoIdpButton)] - pub struct SsoIdpButton { - /// The identity provider of this button. - identity_provider: OnceCell, - /// The ID of the identity provider. - #[property(get = Self::id)] - id: PhantomData, - /// The name of the identity provider. - #[property(get = Self::name)] - name: PhantomData, - /// The brand of the identity provider, as a string. - #[property(get = Self::brand_string)] - brand_string: PhantomData, - } - - #[glib::object_subclass] - impl ObjectSubclass for SsoIdpButton { - const NAME: &'static str = "SsoIdpButton"; - type Type = super::SsoIdpButton; - type ParentType = gtk::Button; - - fn class_init(klass: &mut Self::Class) { - Self::bind_template(klass); - - klass.set_accessible_role(gtk::AccessibleRole::Button); - } - - fn instance_init(obj: &InitializingObject) { - obj.init_template(); - } - } - - #[glib::derived_properties] - impl ObjectImpl for SsoIdpButton {} - - impl WidgetImpl for SsoIdpButton {} - impl ButtonImpl for SsoIdpButton {} - - impl SsoIdpButton { - /// Set the identity provider of this button. - pub(super) fn set_identity_provider(&self, identity_provider: IdentityProvider) { - let identity_provider = self.identity_provider.get_or_init(|| identity_provider); - - adw::StyleManager::default().connect_dark_notify(clone!( - #[weak(rename_to = imp)] - self, - move |_| imp.update_icon() - )); - self.update_icon(); - - self.obj() - .set_action_target_value(Some(&Some(&identity_provider.id).to_variant())); - self.obj().set_tooltip_text(Some(&gettext_f( - // Translators: Do NOT translate the content between '{' and '}', this is a - // variable name. - // This is the tooltip text on buttons to log in via Single Sign-On. - // The brand is something like Facebook, Apple, GitHub… - "Log in with {brand}", - &[("brand", &identity_provider.name)], - ))); - } - - /// The identity provider of this button. - fn identity_provider(&self) -> &IdentityProvider { - self.identity_provider - .get() - .expect("identity provider is initialized") - } - - /// The ID of the identity provider. - fn id(&self) -> String { - self.identity_provider().id.clone() - } - - /// The name of the identity provider. - fn name(&self) -> String { - self.identity_provider().name.clone() - } - - /// The brand of the identity provider. - fn brand(&self) -> &IdentityProviderBrand { - self.identity_provider() - .brand - .as_ref() - .expect("identity provider has a brand") - } - - /// The brand of the identity provider, as a string. - fn brand_string(&self) -> String { - self.brand().to_string() - } - - /// The icon name of the brand, according to the current theme. - fn brand_icon(&self) -> &str { - let is_dark = adw::StyleManager::default().is_dark(); - - match self.brand() { - IdentityProviderBrand::Apple => { - if is_dark { - "idp-apple-dark" - } else { - "idp-apple" - } - } - IdentityProviderBrand::Facebook => "idp-facebook", - IdentityProviderBrand::GitHub => { - if is_dark { - "idp-github-dark" - } else { - "idp-github" - } - } - IdentityProviderBrand::GitLab => "idp-gitlab", - IdentityProviderBrand::Google => "idp-google", - IdentityProviderBrand::Twitter => { - if is_dark { - "idp-x-dark" - } else { - "idp-x-light" - } - } - // We do not construct this for other brands. - _ => unreachable!(), - } - } - - /// Update the icon of this button for the current state. - fn update_icon(&self) { - self.obj().set_icon_name(self.brand_icon()); - } - } -} - -glib::wrapper! { - /// A button to represent an SSO identity provider. - pub struct SsoIdpButton(ObjectSubclass) - @extends gtk::Widget, gtk::Button, - @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Actionable; -} - -impl SsoIdpButton { - /// The supported SSO identity provider brands of `SsoIdpButton`. - const SUPPORTED_IDP_BRANDS: &[IdentityProviderBrand] = &[ - IdentityProviderBrand::Apple, - IdentityProviderBrand::Facebook, - IdentityProviderBrand::GitHub, - IdentityProviderBrand::GitLab, - IdentityProviderBrand::Google, - IdentityProviderBrand::Twitter, - ]; - - /// Create a new `SsoIdpButton` with the given identity provider. - /// - /// Returns `None` if the identity provider's brand is not supported. - pub fn new(identity_provider: IdentityProvider) -> Option { - // If this is not a supported brand, return `None`. - let brand = identity_provider.brand.as_ref()?; - if !Self::SUPPORTED_IDP_BRANDS.contains(brand) { - return None; - } - - let obj = glib::Object::new::(); - obj.imp().set_identity_provider(identity_provider); - - Some(obj) - } -} diff --git a/src/ui-blueprint-resources.in b/src/ui-blueprint-resources.in index 0370d141..40e7c00f 100644 --- a/src/ui-blueprint-resources.in +++ b/src/ui-blueprint-resources.in @@ -74,7 +74,6 @@ login/in_browser_page.blp login/method_page.blp login/mod.blp login/session_setup_view.blp -login/sso_idp_button.blp session_view/content.blp session_view/create_direct_chat_dialog/mod.blp session_view/create_room_dialog.blp