Browse Source

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.
pipelines/786320
Kévin Commaille 1 year ago
parent
commit
ff12986452
No known key found for this signature in database
GPG Key ID: C971D9DBC9D678D
  1. 1
      .typos.toml
  2. 4
      po/POTFILES.in
  3. 2
      src/login/homeserver_page.rs
  4. 145
      src/login/in_browser_page.rs
  5. 46
      src/login/in_browser_page.ui
  6. 8
      src/login/method_page.rs
  7. 104
      src/login/mod.rs
  8. 4
      src/login/mod.ui
  9. 88
      src/login/sso_idp_button.rs
  10. 2
      src/login/sso_idp_button.ui
  11. 48
      src/login/sso_page.rs
  12. 4
      src/ui-resources.gresource.xml

1
.typos.toml

@ -2,6 +2,7 @@
gir = "gir" gir = "gir"
inout = "inout" inout = "inout"
numer = "numer" # Short for numerator in GStreamer numer = "numer" # Short for numerator in GStreamer
ue = "ue" # End of word after mnemonic
[type.po] [type.po]
extend-glob = ["*.po"] extend-glob = ["*.po"]

4
po/POTFILES.in

@ -60,13 +60,13 @@ src/login/advanced_dialog.ui
src/login/greeter.ui src/login/greeter.ui
src/login/homeserver_page.rs src/login/homeserver_page.rs
src/login/homeserver_page.ui 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.rs
src/login/method_page.ui src/login/method_page.ui
src/login/mod.rs src/login/mod.rs
src/login/mod.ui src/login/mod.ui
src/login/session_setup_view.ui src/login/session_setup_view.ui
src/login/sso_page.ui src/login/sso_idp_button.rs
src/secret/linux.rs src/secret/linux.rs
src/session/model/session.rs src/session/model/session.rs
src/session/model/notifications/mod.rs src/session/model/notifications/mod.rs

2
src/login/homeserver_page.rs

@ -242,7 +242,7 @@ mod imp {
match handle.await.expect("task was not aborted") { match handle.await.expect("task was not aborted") {
Ok(res) => { Ok(res) => {
login.set_login_types(res.flows); login.set_login_types(res.flows);
login.show_login_screen(); login.show_login_page();
} }
Err(error) => { Err(error) => {
warn!("Could not get available login types: {error}"); warn!("Could not get available login types: {error}");

145
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<gtk::Button>,
/// The ancestor `Login` object.
#[property(get, set, nullable)]
login: glib::WeakRef<Login>,
/// Whether we are logging in with OIDC compatibility.
#[property(get, set)]
oidc_compatibility: Cell<bool>,
/// The identity provider to use when logging in with SSO.
#[property(get, set, nullable)]
sso_idp_id: RefCell<Option<String>>,
}
#[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<Self>) {
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<imp::LoginInBrowserPage>)
@extends gtk::Widget, adw::NavigationPage, @implements gtk::Accessible;
}
impl LoginInBrowserPage {
pub fn new() -> Self {
glib::Object::new()
}
}

46
src/login/sso_page.ui → src/login/in_browser_page.ui

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<template class="LoginSsoPage" parent="AdwNavigationPage"> <template class="LoginInBrowserPage" parent="AdwNavigationPage">
<property name="tag">sso</property> <property name="tag">in-browser</property>
<property name="title" translatable="yes">Single Sign-On</property> <property name="title" translatable="yes">Authentication</property>
<property name="child"> <property name="child">
<object class="AdwToolbarView"> <object class="AdwToolbarView">
<child type="top"> <child type="top">
@ -23,7 +23,6 @@
<property name="vexpand">True</property> <property name="vexpand">True</property>
<property name="child"> <property name="child">
<object class="AdwClamp"> <object class="AdwClamp">
<property name="maximum-size">360</property>
<property name="margin-top">24</property> <property name="margin-top">24</property>
<property name="margin-bottom">24</property> <property name="margin-bottom">24</property>
<property name="margin-start">12</property> <property name="margin-start">12</property>
@ -38,7 +37,7 @@
<property name="wrap">True</property> <property name="wrap">True</property>
<property name="wrap-mode">word-char</property> <property name="wrap-mode">word-char</property>
<property name="justify">center</property> <property name="justify">center</property>
<property name="label" translatable="yes">Single Sign-On</property> <property name="label" translatable="yes">Authentication</property>
<property name="accessible-role">heading</property> <property name="accessible-role">heading</property>
<accessibility> <accessibility>
<property name="level">1</property> <property name="level">1</property>
@ -53,12 +52,44 @@
<property name="wrap">True</property> <property name="wrap">True</property>
<property name="wrap-mode">word-char</property> <property name="wrap-mode">word-char</property>
<property name="justify">center</property> <property name="justify">center</property>
<property name="label" translatable="yes">Please follow the steps in the browser</property> <property name="label" translatable="yes">Click on the button below and follow the steps in the browser</property>
<style> <style>
<class name="title-4"/> <class name="body"/>
</style> </style>
</object> </object>
</child> </child>
<child>
<object class="GtkButton" id="continue_btn">
<style>
<class name="suggested-action"/>
<class name="standalone-button"/>
<class name="image-text-button"/>
<class name="pill"/>
</style>
<property name="child">
<object class="GtkBox">
<property name="halign">center</property>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Contin_ue</property>
<property name="use-underline">True</property>
<property name="ellipsize">end</property>
<property name="mnemonic-widget">continue_btn</property>
</object>
</child>
<child>
<object class="GtkImage">
<property name="icon-name">external-link-symbolic</property>
<property name="accessible-role">presentation</property>
<property name="valign">center</property>
</object>
</child>
</object>
</property>
<property name="halign">center</property>
<signal name="clicked" handler="login_with_sso" swapped="yes" />
</object>
</child>
</object> </object>
</property> </property>
</object> </object>
@ -71,4 +102,3 @@
</property> </property>
</template> </template>
</interface> </interface>

8
src/login/method_page.rs

@ -4,7 +4,7 @@ use gtk::{self, glib, glib::clone, CompositeTemplate};
use ruma::api::client::session::get_login_types::v3::LoginType; use ruma::api::client::session::get_login_types::v3::LoginType;
use tracing::warn; use tracing::warn;
use super::{idp_button::IdpButton, Login}; use super::{sso_idp_button::SsoIdpButton, Login};
use crate::{ use crate::{
components::LoadingButton, gettext_f, prelude::*, spawn_tokio, toast, utils::BoundObjectWeakRef, components::LoadingButton, gettext_f, prelude::*, spawn_tokio, toast, utils::BoundObjectWeakRef,
}; };
@ -28,7 +28,7 @@ mod imp {
password_entry: TemplateChild<adw::PasswordEntryRow>, password_entry: TemplateChild<adw::PasswordEntryRow>,
#[template_child] #[template_child]
sso_idp_box: TemplateChild<gtk::Box>, sso_idp_box: TemplateChild<gtk::Box>,
sso_idp_box_children: RefCell<Vec<IdpButton>>, sso_idp_box_children: RefCell<Vec<SsoIdpButton>>,
#[template_child] #[template_child]
more_sso_btn: TemplateChild<gtk::Button>, more_sso_btn: TemplateChild<gtk::Button>,
#[template_child] #[template_child]
@ -157,8 +157,8 @@ mod imp {
let mut sso_idp_box_children = self.sso_idp_box_children.borrow_mut(); let mut sso_idp_box_children = self.sso_idp_box_children.borrow_mut();
sso_idp_box_children.reserve(sso_login.identity_providers.len()); sso_idp_box_children.reserve(sso_login.identity_providers.len());
for provider in &sso_login.identity_providers { for identity_provider in sso_login.identity_providers {
if let Some(btn) = IdpButton::new(provider) { if let Some(btn) = SsoIdpButton::new(identity_provider) {
self.sso_idp_box.append(&btn); self.sso_idp_box.append(&btn);
sso_idp_box_children.push(btn); sso_idp_box_children.push(btn);

104
src/login/mod.rs

@ -7,23 +7,24 @@ use gtk::{
}; };
use matrix_sdk::Client; use matrix_sdk::Client;
use ruma::{ use ruma::{
api::client::session::{get_login_types::v3::LoginType, login, SsoRedirectOidcAction}, api::client::session::{get_login_types::v3::LoginType, login},
OwnedServerName, OwnedServerName,
}; };
use tracing::{error, warn}; use tracing::warn;
use url::Url; use url::Url;
mod advanced_dialog; mod advanced_dialog;
mod greeter; mod greeter;
mod homeserver_page; mod homeserver_page;
mod idp_button; mod in_browser_page;
mod method_page; mod method_page;
mod session_setup_view; mod session_setup_view;
mod sso_page; mod sso_idp_button;
use self::{ use self::{
advanced_dialog::LoginAdvancedDialog, greeter::Greeter, homeserver_page::LoginHomeserverPage, advanced_dialog::LoginAdvancedDialog, greeter::Greeter, homeserver_page::LoginHomeserverPage,
method_page::LoginMethodPage, session_setup_view::SessionSetupView, sso_page::LoginSsoPage, in_browser_page::LoginInBrowserPage, method_page::LoginMethodPage,
session_setup_view::SessionSetupView,
}; };
use crate::{ use crate::{
components::OfflineBanner, prelude::*, secret::store_session, session::model::Session, spawn, components::OfflineBanner, prelude::*, secret::store_session, session::model::Session, spawn,
@ -40,8 +41,8 @@ enum LoginPage {
Homeserver, Homeserver,
/// The page to select a login method. /// The page to select a login method.
Method, Method,
/// The page to wait for SSO to be finished. /// The page to log in with the browser.
Sso, InBrowser,
/// The loading page. /// The loading page.
Loading, Loading,
/// The session setup stack. /// The session setup stack.
@ -74,7 +75,7 @@ mod imp {
#[template_child] #[template_child]
method_page: TemplateChild<LoginMethodPage>, method_page: TemplateChild<LoginMethodPage>,
#[template_child] #[template_child]
sso_page: TemplateChild<LoginSsoPage>, in_browser_page: TemplateChild<LoginInBrowserPage>,
#[template_child] #[template_child]
done_button: TemplateChild<gtk::Button>, done_button: TemplateChild<gtk::Button>,
/// Whether auto-discovery is enabled. /// Whether auto-discovery is enabled.
@ -117,8 +118,8 @@ mod imp {
"login.sso", "login.sso",
Some(&Option::<String>::static_variant_type()), Some(&Option::<String>::static_variant_type()),
|obj, _, variant| async move { |obj, _, variant| async move {
let idp_id = variant.and_then(|v| v.get::<Option<String>>()).flatten(); let sso_idp_id = variant.and_then(|v| v.get::<Option<String>>()).flatten();
obj.imp().login_with_sso(idp_id, false).await; obj.imp().show_in_browser_page(sso_idp_id, false);
}, },
); );
@ -181,7 +182,8 @@ mod imp {
LoginPage::Greeter => self.greeter.grab_focus(), LoginPage::Greeter => self.greeter.grab_focus(),
LoginPage::Homeserver => self.homeserver_page.grab_focus(), LoginPage::Homeserver => self.homeserver_page.grab_focus(),
LoginPage::Method => self.method_page.grab_focus(), LoginPage::Method => self.method_page.grab_focus(),
LoginPage::Sso | LoginPage::Loading => false, LoginPage::InBrowser => self.in_browser_page.grab_focus(),
LoginPage::Loading => false,
LoginPage::SessionSetup => { LoginPage::SessionSetup => {
if let Some(session_setup) = self.session_setup() { if let Some(session_setup) = self.session_setup() {
session_setup.grab_focus() session_setup.grab_focus()
@ -355,8 +357,8 @@ mod imp {
dialog.run_future(&*obj).await; dialog.run_future(&*obj).await;
} }
/// Show the appropriate login screen given the current login types. /// Show the appropriate login page given the current login types.
pub(super) fn show_login_screen(&self) { pub(super) fn show_login_page(&self) {
let mut oidc_compatibility = false; let mut oidc_compatibility = false;
let mut supports_password = false; let mut supports_password = false;
@ -375,77 +377,19 @@ mod imp {
} }
if oidc_compatibility || !supports_password { if oidc_compatibility || !supports_password {
spawn!(clone!( self.show_in_browser_page(None, oidc_compatibility);
#[weak(rename_to = imp)]
self,
async move {
imp.login_with_sso(None, oidc_compatibility).await;
}
));
} else { } else {
self.navigation.push_by_tag(LoginPage::Method.as_ref()); self.navigation.push_by_tag(LoginPage::Method.as_ref());
} }
} }
/// Log in with the SSO login type. /// Show the page to log in with the browser with the given parameters.
async fn login_with_sso(&self, idp_id: Option<String>, oidc_compatibility: bool) { fn show_in_browser_page(&self, sso_idp_id: Option<String>, oidc_compatibility: bool) {
self.navigation.push_by_tag(LoginPage::Sso.as_ref()); self.in_browser_page.set_sso_idp_id(sso_idp_id);
let client = self.client().await.expect("client was constructed"); self.in_browser_page
.set_oidc_compatibility(oidc_compatibility);
let handle = spawn_tokio!(async move {
let mut 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(idp_id) = idp_id.as_deref() {
login = login.identity_provider_id(idp_id);
}
login.send().await self.navigation.push_by_tag(LoginPage::InBrowser.as_ref());
});
match handle.await.expect("task was not aborted") {
Ok(response) => {
self.handle_login_response(response).await;
}
Err(error) => {
warn!("Could not log in: {error}");
let obj = self.obj();
toast!(obj, error.to_user_facing());
self.navigation.pop();
}
}
} }
/// Handle the given response after successfully logging in. /// Handle the given response after successfully logging in.
@ -595,8 +539,8 @@ impl Login {
} }
/// Show the appropriate login screen given the current login types. /// Show the appropriate login screen given the current login types.
fn show_login_screen(&self) { fn show_login_page(&self) {
self.imp().show_login_screen(); self.imp().show_login_page();
} }
/// Freeze the login screen. /// Freeze the login screen.

4
src/login/mod.ui

@ -17,7 +17,9 @@
</object> </object>
</child> </child>
<child> <child>
<object class="LoginSsoPage" id="sso_page"/> <object class="LoginInBrowserPage" id="in_browser_page">
<property name="login">Login</property>
</object>
</child> </child>
<child> <child>
<object class="AdwNavigationPage"> <object class="AdwNavigationPage">

88
src/login/idp_button.rs → src/login/sso_idp_button.rs

@ -13,20 +13,26 @@ mod imp {
use super::*; use super::*;
#[derive(Debug, Default, CompositeTemplate, glib::Properties)] #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/login/idp_button.ui")] #[template(resource = "/org/gnome/Fractal/ui/login/sso_idp_button.ui")]
#[properties(wrapper_type = super::IdpButton)] #[properties(wrapper_type = super::SsoIdpButton)]
pub struct IdpButton { pub struct SsoIdpButton {
/// The identity provider brand of this button. /// The identity provider of this button.
brand: OnceCell<IdentityProviderBrand>, identity_provider: OnceCell<IdentityProvider>,
/// The identity provider brand of this button, as a string. /// The ID of the identity provider.
#[property(get = Self::id)]
id: PhantomData<String>,
/// The name of the identity provider.
#[property(get = Self::name)]
name: PhantomData<String>,
/// The brand of the identity provider, as a string.
#[property(get = Self::brand_string)] #[property(get = Self::brand_string)]
brand_string: PhantomData<String>, brand_string: PhantomData<String>,
} }
#[glib::object_subclass] #[glib::object_subclass]
impl ObjectSubclass for IdpButton { impl ObjectSubclass for SsoIdpButton {
const NAME: &'static str = "IdpButton"; const NAME: &'static str = "SsoIdpButton";
type Type = super::IdpButton; type Type = super::SsoIdpButton;
type ParentType = gtk::Button; type ParentType = gtk::Button;
fn class_init(klass: &mut Self::Class) { fn class_init(klass: &mut Self::Class) {
@ -41,15 +47,15 @@ mod imp {
} }
#[glib::derived_properties] #[glib::derived_properties]
impl ObjectImpl for IdpButton {} impl ObjectImpl for SsoIdpButton {}
impl WidgetImpl for IdpButton {} impl WidgetImpl for SsoIdpButton {}
impl ButtonImpl for IdpButton {} impl ButtonImpl for SsoIdpButton {}
impl IdpButton { impl SsoIdpButton {
/// Set the identity provider brand of this button. /// Set the identity provider of this button.
pub(super) fn set_brand(&self, brand: IdentityProviderBrand) { pub(super) fn set_identity_provider(&self, identity_provider: IdentityProvider) {
let brand = self.brand.get_or_init(|| brand); let identity_provider = self.identity_provider.get_or_init(|| identity_provider);
adw::StyleManager::default().connect_dark_notify(clone!( adw::StyleManager::default().connect_dark_notify(clone!(
#[weak(rename_to = imp)] #[weak(rename_to = imp)]
@ -58,29 +64,49 @@ mod imp {
)); ));
self.update_icon(); self.update_icon();
let obj = self.obj(); self.obj()
obj.set_action_target_value(Some(&Some(&brand.as_str()).to_variant())); .set_action_target_value(Some(&Some(&identity_provider.id).to_variant()));
obj.set_tooltip_text(Some(&gettext_f( self.obj().set_tooltip_text(Some(&gettext_f(
// Translators: Do NOT translate the content between '{' and '}', this is a // Translators: Do NOT translate the content between '{' and '}', this is a
// variable name. // variable name.
// This is the tooltip text on buttons to log in via Single Sign-On. // This is the tooltip text on buttons to log in via Single Sign-On.
// The brand is something like Facebook, Apple, GitHub… // The brand is something like Facebook, Apple, GitHub…
"Log in with {brand}", "Log in with {brand}",
&[("brand", brand.as_str())], &[("brand", &identity_provider.name)],
))); )));
} }
/// The identity provider brand of this button. /// 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 { fn brand(&self) -> &IdentityProviderBrand {
self.brand.get().expect("brand is initialized") self.identity_provider()
.brand
.as_ref()
.expect("identity provider has a brand")
} }
/// The identity provider brand of this button, as a string. /// The brand of the identity provider, as a string.
fn brand_string(&self) -> String { fn brand_string(&self) -> String {
self.brand().to_string() self.brand().to_string()
} }
/// The icon name of the current brand, according to the current theme. /// The icon name of the brand, according to the current theme.
fn brand_icon(&self) -> &str { fn brand_icon(&self) -> &str {
let is_dark = adw::StyleManager::default().is_dark(); let is_dark = adw::StyleManager::default().is_dark();
@ -123,13 +149,13 @@ mod imp {
glib::wrapper! { glib::wrapper! {
/// A button to represent an SSO identity provider. /// A button to represent an SSO identity provider.
pub struct IdpButton(ObjectSubclass<imp::IdpButton>) pub struct SsoIdpButton(ObjectSubclass<imp::SsoIdpButton>)
@extends gtk::Widget, gtk::Button, @extends gtk::Widget, gtk::Button,
@implements gtk::Accessible, gtk::Actionable; @implements gtk::Accessible, gtk::Actionable;
} }
impl IdpButton { impl SsoIdpButton {
/// The supported SSO identity provider brands of `IdpButton`. /// The supported SSO identity provider brands of `SsoIdpButton`.
const SUPPORTED_IDP_BRANDS: &[IdentityProviderBrand] = &[ const SUPPORTED_IDP_BRANDS: &[IdentityProviderBrand] = &[
IdentityProviderBrand::Apple, IdentityProviderBrand::Apple,
IdentityProviderBrand::Facebook, IdentityProviderBrand::Facebook,
@ -139,18 +165,18 @@ impl IdpButton {
IdentityProviderBrand::Twitter, IdentityProviderBrand::Twitter,
]; ];
/// Create a new `IdpButton` with the given identity provider. /// Create a new `SsoIdpButton` with the given identity provider.
/// ///
/// Returns `None` if the identity provider's brand is not supported. /// Returns `None` if the identity provider's brand is not supported.
pub fn new(idp: &IdentityProvider) -> Option<Self> { pub fn new(identity_provider: IdentityProvider) -> Option<Self> {
// If this is not a supported brand, return `None`. // If this is not a supported brand, return `None`.
let brand = idp.brand.as_ref()?; let brand = identity_provider.brand.as_ref()?;
if !Self::SUPPORTED_IDP_BRANDS.contains(brand) { if !Self::SUPPORTED_IDP_BRANDS.contains(brand) {
return None; return None;
} }
let obj = glib::Object::new::<Self>(); let obj = glib::Object::new::<Self>();
obj.imp().set_brand(brand.clone()); obj.imp().set_identity_provider(identity_provider);
Some(obj) Some(obj)
} }

2
src/login/idp_button.ui → src/login/sso_idp_button.ui

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<template class="IdpButton" parent="GtkButton"> <template class="SsoIdpButton" parent="GtkButton">
<style> <style>
<class name="card"/> <class name="card"/>
<class name="sso-button"/> <class name="sso-button"/>

48
src/login/sso_page.rs

@ -1,48 +0,0 @@
use adw::subclass::prelude::*;
use gtk::{self, glib, CompositeTemplate};
mod imp {
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/Fractal/ui/login/sso_page.ui")]
pub struct LoginSsoPage {}
#[glib::object_subclass]
impl ObjectSubclass for LoginSsoPage {
const NAME: &'static str = "LoginSsoPage";
type Type = super::LoginSsoPage;
type ParentType = adw::NavigationPage;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for LoginSsoPage {}
impl WidgetImpl for LoginSsoPage {}
impl NavigationPageImpl for LoginSsoPage {
fn shown(&self) {
self.grab_focus();
}
}
}
glib::wrapper! {
/// A page shown while the user is logging in via SSO.
pub struct LoginSsoPage(ObjectSubclass<imp::LoginSsoPage>)
@extends gtk::Widget, adw::NavigationPage, @implements gtk::Accessible;
}
impl LoginSsoPage {
pub fn new() -> Self {
glib::Object::new()
}
}

4
src/ui-resources.gresource.xml

@ -57,11 +57,11 @@
<file compressed="true" preprocess="xml-stripblanks">login/advanced_dialog.ui</file> <file compressed="true" preprocess="xml-stripblanks">login/advanced_dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/greeter.ui</file> <file compressed="true" preprocess="xml-stripblanks">login/greeter.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/homeserver_page.ui</file> <file compressed="true" preprocess="xml-stripblanks">login/homeserver_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/idp_button.ui</file> <file compressed="true" preprocess="xml-stripblanks">login/in_browser_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/method_page.ui</file> <file compressed="true" preprocess="xml-stripblanks">login/method_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/mod.ui</file> <file compressed="true" preprocess="xml-stripblanks">login/mod.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/session_setup_view.ui</file> <file compressed="true" preprocess="xml-stripblanks">login/session_setup_view.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login/sso_page.ui</file> <file compressed="true" preprocess="xml-stripblanks">login/sso_idp_button.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/account_settings/general_page/change_password_subpage.ui</file> <file compressed="true" preprocess="xml-stripblanks">session/view/account_settings/general_page/change_password_subpage.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/account_settings/general_page/deactivate_account_subpage.ui</file> <file compressed="true" preprocess="xml-stripblanks">session/view/account_settings/general_page/deactivate_account_subpage.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/account_settings/general_page/log_out_subpage.ui</file> <file compressed="true" preprocess="xml-stripblanks">session/view/account_settings/general_page/log_out_subpage.ui</file>

Loading…
Cancel
Save