[fractal/fractal-next] login: Add SSO support
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] login: Add SSO support
- Date: Tue, 22 Mar 2022 15:06:14 +0000 (UTC)
commit d8e58a76aa402b5ec0f9ee8debd3a14bbe58e6c4
Author: JCWasmx86 <JCWasmx86 t-online de>
Date: Thu Feb 3 17:20:49 2022 +0100
login: Add SSO support
Cargo.lock | 105 ++++++++++
Cargo.toml | 1 +
.../icons/scalable/actions/idp-apple-dark.svg | 1 +
.../resources/icons/scalable/actions/idp-apple.svg | 1 +
.../icons/scalable/actions/idp-facebook.svg | 1 +
.../icons/scalable/actions/idp-github-dark.svg | 1 +
.../icons/scalable/actions/idp-github.svg | 1 +
.../icons/scalable/actions/idp-gitlab.svg | 1 +
.../icons/scalable/actions/idp-google-dark.svg | 1 +
.../icons/scalable/actions/idp-google.svg | 1 +
.../icons/scalable/actions/idp-twitter.svg | 1 +
data/resources/resources.gresource.xml | 10 +
data/resources/style.css | 6 +
data/resources/ui/idp-button.ui | 10 +
data/resources/ui/login.ui | 48 ++++-
src/idp_button.rs | 231 +++++++++++++++++++++
src/login.rs | 110 +++++++++-
src/main.rs | 1 +
src/session/mod.rs | 56 +++++
19 files changed, 584 insertions(+), 3 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index e7ec3ea7b..db3baa238 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1711,6 +1711,31 @@ dependencies = [
"ahash",
]
+[[package]]
+name = "headers"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d"
+dependencies = [
+ "base64",
+ "bitflags",
+ "bytes",
+ "headers-core",
+ "http",
+ "httpdate",
+ "mime",
+ "sha-1",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
+dependencies = [
+ "http",
+]
+
[[package]]
name = "heck"
version = "0.3.3"
@@ -2239,14 +2264,17 @@ dependencies = [
"matrix-sdk-common",
"matrix-sdk-sled",
"mime",
+ "rand 0.8.5",
"reqwest",
"ruma",
"serde",
"serde_json",
"thiserror",
"tokio",
+ "tokio-stream",
"tracing",
"url",
+ "warp",
"zeroize",
]
@@ -2872,6 +2900,26 @@ dependencies = [
"siphasher",
]
+[[package]]
+name = "pin-project"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
+dependencies = [
+ "proc-macro2 1.0.36",
+ "quote 1.0.15",
+ "syn 1.0.86",
+]
+
[[package]]
name = "pin-project-lite"
version = "0.2.8"
@@ -3558,6 +3606,12 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
[[package]]
name = "scoped_threadpool"
version = "0.1.9"
@@ -3671,6 +3725,17 @@ dependencies = [
"serde",
]
+[[package]]
+name = "sha-1"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
+dependencies = [
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "digest 0.10.3",
+]
+
[[package]]
name = "sha1"
version = "0.6.1"
@@ -4115,6 +4180,17 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "tokio-stream"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
[[package]]
name = "tokio-util"
version = "0.6.9"
@@ -4151,6 +4227,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f"
dependencies = [
"cfg-if 1.0.0",
+ "log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@@ -4370,6 +4447,34 @@ dependencies = [
"try-lock",
]
+[[package]]
+name = "warp"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "headers",
+ "http",
+ "hyper",
+ "log",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "pin-project",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-stream",
+ "tokio-util",
+ "tower-service",
+ "tracing",
+]
+
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
diff --git a/Cargo.toml b/Cargo.toml
index 0d38a5bf1..8c0c40284 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -66,5 +66,6 @@ features = [
"encryption",
"sled_cryptostore",
"sled_state_store",
+ "sso_login",
"markdown",
]
diff --git a/data/resources/icons/scalable/actions/idp-apple-dark.svg
b/data/resources/icons/scalable/actions/idp-apple-dark.svg
new file mode 100644
index 000000000..a1c990815
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-apple-dark.svg
@@ -0,0 +1 @@
+<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="3.78 1.25 17.75 21.82"><path
fill-rule="evenodd" clip-rule="evenodd" d="M16.98 1.28a4.872 4.872 0 0 1-1.114 3.49 4.099 4.099 0 0 1-3.237
1.53 4.636 4.636 0 0 1 1.144-3.36 4.957 4.957 0 0 1 3.207-1.66Zm3.974 7.428a4.949 4.949 0 0 0-2.357 4.152
4.782 4.782 0 0 0 2.92 4.4 10.963 10.963 0 0 1-1.519 3.092c-.894 1.338-1.832 2.645-3.32
2.669-.708.016-1.186-.187-1.684-.4-.52-.22-1.06-.451-1.907-.451-.899
0-1.464.238-2.01.467-.47.198-.927.39-1.57.417-1.417.053-2.5-1.428-3.427-2.753-1.853-2.707-3.296-7.628-1.362-10.976a5.315
5.315 0 0 1 4.473-2.728c.804-.017 1.576.293 2.252.565.517.208.979.393 1.357.393.332 0 .78-.178
1.304-.386.824-.326 1.832-.727 2.859-.619 1.596.05 3.075.85 3.99 2.158Z" fill="#fff"/></svg>
\ 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
new file mode 100644
index 000000000..39d8356ca
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-apple.svg
@@ -0,0 +1 @@
+<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="3.78 1.25 17.75 21.82"><path
fill-rule="evenodd" clip-rule="evenodd" d="M16.98 1.28a4.872 4.872 0 0 1-1.114 3.49 4.099 4.099 0 0 1-3.237
1.53 4.636 4.636 0 0 1 1.144-3.36 4.957 4.957 0 0 1 3.207-1.66Zm3.974 7.428a4.949 4.949 0 0 0-2.357 4.152
4.782 4.782 0 0 0 2.92 4.4 10.963 10.963 0 0 1-1.519 3.092c-.894 1.338-1.832 2.645-3.32
2.669-.708.016-1.186-.187-1.684-.4-.52-.22-1.06-.451-1.907-.451-.899
0-1.464.238-2.01.467-.47.198-.927.39-1.57.417-1.417.053-2.5-1.428-3.427-2.753-1.853-2.707-3.296-7.628-1.362-10.976a5.315
5.315 0 0 1 4.473-2.728c.804-.017 1.576.293 2.252.565.517.208.979.393 1.357.393.332 0 .78-.178
1.304-.386.824-.326 1.832-.727 2.859-.619 1.596.05 3.075.85 3.99 2.158Z" fill="#17191C"/></svg>
\ 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
new file mode 100644
index 000000000..9aabcdc5b
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-facebook.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="22"
height="22"><defs><clipPath id="c"><path d="M0 0h23v22H0z"/></clipPath><clipPath id="d"><path d="M0
0h22v22H0z"/></clipPath><g id="e" clip-path="url(#c)"><path
style="stroke:none;fill-rule:evenodd;fill:#1877f2;fill-opacity:1" d="M22 11c0-6.074-4.926-11-11-11C4.922
0-.004 4.926-.004 11c0 5.488 4.024 10.043 9.285 10.867V14.18H6.484V11h2.793V8.578c0-2.758 1.645-4.281
4.157-4.281 1.207 0 2.46.215 2.46.215v2.707h-1.382c-1.367 0-1.793.847-1.793 1.718V11h3.05l-.488
3.18H12.72v7.687C17.977 21.043 22 16.492 22 11Zm0 0"/></g><g id="g" clip-path="url(#d)"><use xlink:href="#e"
mask="url(#f)"/></g><filter id="a" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%"
height="100%"><feColorMatrix in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1
0"/></filter><image id="b" width="23" height="22"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAWCAYAAAArdgcFAAAABmJLR0QA/wD
/AP+gvaeTAAAALUlEQVQ4je3MQQ0AIBADsB7Bv0WkgIp9yCqggytjVigGzZs3b968+cf5xknlDwBCAg1iefLzAAAAAElFTkSuQmCC"/><mask
id="f"><g filter="url(#a)"><use xlink:href="#b"/></g></mask></defs><use xlink:href="#g"/><path
style="stroke:none;fill-rule:evenodd;fill:#fff;fill-opacity:1" d="M15.281 14.18 15.77
11h-3.051V8.937c0-.87.426-1.718 1.793-1.718h1.386V4.512s-1.257-.215-2.46-.215c-2.516 0-4.157 1.523-4.157
4.281V11H6.488v3.18h2.793v7.687c1.137.18 2.297.18 3.438 0V14.18Zm0 0"/></svg>
\ 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
new file mode 100644
index 000000000..fee0a9fc3
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-github-dark.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22"><path
style="stroke:none;fill-rule:nonzero;fill:#fff;fill-opacity:1" d="M20.504 5.613a11.103 11.103 0 0
0-4-4.101C14.82.504 12.984 0 10.988 0 8.996 0 7.156.504 5.473 1.512a11.103 11.103 0 0 0-4 4.101C.493 7.34 0
9.227 0 11.273c0 2.454.7 4.665 2.098 6.625 1.394 1.961 3.199 3.317 5.414 4.07.258.052.449.016.574-.1a.576.576
0 0 0 .184-.438l-.008-.797c-.004-.496-.008-.934-.008-1.305l-.328.059a3.849 3.849 0 0 1-.793.05 5.667 5.667 0
0 1-.996-.101 2.214 2.214 0 0 1-.957-.441 1.87 1.87 0 0
1-.633-.899l-.14-.34c-.122-.27-.27-.52-.45-.75-.207-.273-.414-.457-.625-.554l-.098-.075a1.001 1.001 0 0
1-.316-.382c-.027-.07-.004-.125.07-.168.078-.047.215-.067.418-.067l.285.043c.192.04.426.156.707.352.286.199.512.453.696.765.218.403.484.707.793.918.312.207.625.313.937.313.317
0 .586-.024.817-.07.226-.051.441-.126.644-.223.086-.656.32-1.16.7-1.512a9.84 9.84 0 0 1-1.465-.262 5.758
5.758 0 0 1-1.344-.574 3.818 3.818 0 0 1-1.153-.984c-.308-.39-
.558-.906-.753-1.54-.196-.636-.293-1.37-.293-2.202 0-1.184.378-2.192
1.132-3.024-.355-.89-.32-1.887.098-2.996.277-.086.688-.02 1.23.2.543.222.942.41
1.196.566.254.156.457.289.61.398a9.776 9.776 0 0 1 2.745-.383c.946 0 1.86.125
2.75.383l.543-.355c.371-.235.809-.45 1.317-.645.504-.195.89-.25 1.156-.164.43 1.11.469 2.106.117 2.996.75.832
1.129 1.84 1.129 3.024 0 .832-.098 1.57-.293 2.207-.195.644-.45 1.156-.758
1.543-.324.398-.715.73-1.16.976a5.84 5.84 0 0 1-1.344.574 9.41 9.41 0 0 1-1.468.262c.496.442.746 1.137.746
2.086v3.094c0 .176.058.324.18.441.117.117.304.153.562.102 2.215-.754 4.02-2.11 5.418-4.07 1.394-1.965
2.094-4.172 2.094-6.63 0-2.042-.493-3.93-1.473-5.656Zm0 0"/></svg>
\ 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
new file mode 100644
index 000000000..cb628a472
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-github.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22"><path
style="stroke:none;fill-rule:nonzero;fill:#000;fill-opacity:1" d="M20.504 5.613a11.103 11.103 0 0
0-4-4.101C14.82.504 12.984 0 10.988 0 8.996 0 7.156.504 5.473 1.512a11.103 11.103 0 0 0-4 4.101C.493 7.34 0
9.227 0 11.273c0 2.454.7 4.665 2.098 6.625 1.394 1.961 3.199 3.317 5.414 4.07.258.052.449.016.574-.1a.576.576
0 0 0 .184-.438l-.008-.797c-.004-.496-.008-.934-.008-1.305l-.328.059a3.849 3.849 0 0 1-.793.05 5.667 5.667 0
0 1-.996-.101 2.214 2.214 0 0 1-.957-.441 1.87 1.87 0 0
1-.633-.899l-.14-.34c-.122-.27-.27-.52-.45-.75-.207-.273-.414-.457-.625-.554l-.098-.075a1.001 1.001 0 0
1-.316-.382c-.027-.07-.004-.125.07-.168.078-.047.215-.067.418-.067l.285.043c.192.04.426.156.707.352.286.199.512.453.696.765.218.403.484.707.793.918.312.207.625.313.937.313.317
0 .586-.024.817-.07.226-.051.441-.126.644-.223.086-.656.32-1.16.7-1.512a9.84 9.84 0 0 1-1.465-.262 5.758
5.758 0 0 1-1.344-.574 3.818 3.818 0 0 1-1.153-.984c-.308-.39-
.558-.906-.753-1.54-.196-.636-.293-1.37-.293-2.202 0-1.184.378-2.192
1.132-3.024-.355-.89-.32-1.887.098-2.996.277-.086.688-.02 1.23.2.543.222.942.41
1.196.566.254.156.457.289.61.398a9.776 9.776 0 0 1 2.745-.383c.946 0 1.86.125
2.75.383l.543-.355c.371-.235.809-.45 1.317-.645.504-.195.89-.25 1.156-.164.43 1.11.469 2.106.117 2.996.75.832
1.129 1.84 1.129 3.024 0 .832-.098 1.57-.293 2.207-.195.644-.45 1.156-.758
1.543-.324.398-.715.73-1.16.976a5.84 5.84 0 0 1-1.344.574 9.41 9.41 0 0 1-1.468.262c.496.442.746 1.137.746
2.086v3.094c0 .176.058.324.18.441.117.117.304.153.562.102 2.215-.754 4.02-2.11 5.418-4.07 1.394-1.965
2.094-4.172 2.094-6.63 0-2.042-.493-3.93-1.473-5.656Zm0 0"/></svg>
\ 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
new file mode 100644
index 000000000..47087debf
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-gitlab.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22"><path
style="stroke:none;fill-rule:nonzero;fill:#e24329;fill-opacity:1" d="m11.008 22 4.047-13.535h-8.09Zm0
0"/><path style="stroke:none;fill-rule:nonzero;fill:#fca326;fill-opacity:1" d="m1.297 8.465-1.23
4.113a.944.944 0 0 0 .304 1.016L11.008 22Zm0 0"/><path
style="stroke:none;fill-rule:nonzero;fill:#e24329;fill-opacity:1" d="M1.297
8.465h5.668L4.523.313c-.125-.418-.668-.418-.796 0Zm0 0"/><path
style="stroke:none;fill-rule:nonzero;fill:#fca326;fill-opacity:1" d="m20.719 8.465 1.226 4.113a.95.95 0 0
1-.3 1.016L11.004 22Zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#e24329;fill-opacity:1"
d="M20.723 8.465h-5.668L17.488.313c.125-.418.668-.418.797 0Zm0 0"/><path
style="stroke:none;fill-rule:nonzero;fill:#fc6d26;fill-opacity:1" d="M11.008 22 15.05 8.465h5.668Zm-.004
0L1.297 8.465h5.668L11.008 22Zm0 0"/></svg>
\ 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
new file mode 100644
index 000000000..11614169e
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-google-dark.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22"><path
style="stroke:none;fill-rule:nonzero;fill:#4285f4;fill-opacity:1" d="M21.98
11.234c0-.902-.074-1.562-.238-2.246H11.215v4.078h6.18c-.125 1.012-.797 2.54-2.293 3.563l-.02.137 3.328
2.527.23.023c2.118-1.918 3.34-4.738 3.34-8.082Zm0 0"/><path
style="stroke:none;fill-rule:nonzero;fill:#34a853;fill-opacity:1" d="M11.215 21.98c3.027 0 5.57-.976
7.426-2.664L15.1 16.63c-.945.648-2.218 1.101-3.886 1.101a6.736 6.736 0 0 1-6.38-4.566l-.132.012-3.46
2.625-.048.12c1.844 3.59 5.633 6.06 10.02 6.06Zm0 0"/><path
style="stroke:none;fill-rule:nonzero;fill:#fbbc05;fill-opacity:1" d="M4.836 13.164a6.652 6.652 0 0
1-.375-2.176c0-.754.137-1.488.363-2.172l-.008-.144-3.503-2.668-.114.05A10.846 10.846 0 0 0 0 10.989c0
1.774.438 3.446 1.2 4.934Zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#eb4335;fill-opacity:1"
d="M11.215 4.25c2.105 0 3.527.89 4.336 1.637l3.164-3.032C16.77 1.085 14.242 0 11.215 0 6.828 0 3.039 2.469
1.195 6.059L4.
82 8.816a6.773 6.773 0 0 1 6.395-4.566Zm0 0"/></svg>
\ 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
new file mode 100644
index 000000000..11614169e
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-google.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22"><path
style="stroke:none;fill-rule:nonzero;fill:#4285f4;fill-opacity:1" d="M21.98
11.234c0-.902-.074-1.562-.238-2.246H11.215v4.078h6.18c-.125 1.012-.797 2.54-2.293 3.563l-.02.137 3.328
2.527.23.023c2.118-1.918 3.34-4.738 3.34-8.082Zm0 0"/><path
style="stroke:none;fill-rule:nonzero;fill:#34a853;fill-opacity:1" d="M11.215 21.98c3.027 0 5.57-.976
7.426-2.664L15.1 16.63c-.945.648-2.218 1.101-3.886 1.101a6.736 6.736 0 0 1-6.38-4.566l-.132.012-3.46
2.625-.048.12c1.844 3.59 5.633 6.06 10.02 6.06Zm0 0"/><path
style="stroke:none;fill-rule:nonzero;fill:#fbbc05;fill-opacity:1" d="M4.836 13.164a6.652 6.652 0 0
1-.375-2.176c0-.754.137-1.488.363-2.172l-.008-.144-3.503-2.668-.114.05A10.846 10.846 0 0 0 0 10.989c0
1.774.438 3.446 1.2 4.934Zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#eb4335;fill-opacity:1"
d="M11.215 4.25c2.105 0 3.527.89 4.336 1.637l3.164-3.032C16.77 1.085 14.242 0 11.215 0 6.828 0 3.039 2.469
1.195 6.059L4.
82 8.816a6.773 6.773 0 0 1 6.395-4.566Zm0 0"/></svg>
\ No newline at end of file
diff --git a/data/resources/icons/scalable/actions/idp-twitter.svg
b/data/resources/icons/scalable/actions/idp-twitter.svg
new file mode 100644
index 000000000..d128bd5fd
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-twitter.svg
@@ -0,0 +1 @@
+<svg xml:space="preserve" width="246.15" height="200.013" xmlns="http://www.w3.org/2000/svg"><path
d="M220.95 49.793c.15 2.17.15 4.34.15 6.53 0 66.73-50.8 143.69-143.69
143.69v-.04c-27.44.04-54.31-7.82-77.41-22.64 3.99.48 8 .72 12.02.73 22.74.02 44.83-7.61
62.72-21.66-21.61-.41-40.56-14.5-47.18-35.07a50.338 50.338 0 0 0
22.8-.87c-23.56-4.76-40.51-25.46-40.51-49.5v-.64a50.18 50.18 0 0 0 22.92
6.32c-22.19-14.83-29.03-44.35-15.63-67.43a143.333 143.333 0 0 0 104.08 52.76 50.532 50.532 0 0 1
14.61-48.25c20.34-19.12 52.33-18.14 71.45 2.19 11.31-2.23 22.15-6.38 32.07-12.26a50.69 50.69 0 0 1-22.2
27.93c10.01-1.18 19.79-3.86 29-7.95a102.594 102.594 0 0 1-25.2 26.16z" style="fill:#1d9bf0"/></svg>
\ No newline at end of file
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 8d1d2cd8d..2b4f94aaf 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -5,6 +5,15 @@
<file preprocess="xml-stripblanks">assets/other-device.svg</file>
<file preprocess="xml-stripblanks">assets/setup-complete.svg</file>
<file preprocess="xml-stripblanks">assets/welcome.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/idp-apple-dark.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/idp-apple.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/idp-facebook.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/idp-github-dark.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/idp-github.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/idp-gitlab.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/idp-google-dark.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/idp-google.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/idp-twitter.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/status/devices-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/status/empty-page.svg</file>
@@ -64,6 +73,7 @@
<file compressed="true" preprocess="xml-stripblanks"
alias="event-source-dialog.ui">ui/event-source-dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="greeter.ui">ui/greeter.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="identity-verification-widget.ui">ui/identity-verification-widget.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks" alias="idp-button.ui">ui/idp-button.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="in-app-notification.ui">ui/in-app-notification.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="login-advanced-dialog.ui">ui/login-advanced-dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="login.ui">ui/login.ui</file>
diff --git a/data/resources/style.css b/data/resources/style.css
index 526933700..ccf0aa4bf 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -184,6 +184,12 @@ login entry {
padding: 18px 24px;
}
+.sso-button {
+ padding: 4px;
+ -gtk-icon-size: 26px;
+ min-height: 34px;
+}
+
/* Session */
.session-loading-spinner {
diff --git a/data/resources/ui/idp-button.ui b/data/resources/ui/idp-button.ui
new file mode 100644
index 000000000..45f237bcb
--- /dev/null
+++ b/data/resources/ui/idp-button.ui
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="IdpButton" parent="GtkButton">
+ <style>
+ <class name="card"/>
+ <class name="sso-button"/>
+ </style>
+ </template>
+</interface>
+
diff --git a/data/resources/ui/login.ui b/data/resources/ui/login.ui
index 790c4bf4e..2c354704b 100644
--- a/data/resources/ui/login.ui
+++ b/data/resources/ui/login.ui
@@ -115,7 +115,7 @@
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
- <property name="spacing">30</property>
+ <property name="spacing">30</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
@@ -182,6 +182,26 @@
<property name="use_underline">True</property>
<property name="label" translatable="yes">_Forgot Password?</property>
<property name="uri">https://app.element.io/#/forgot_password</property>
+ <property name="halign">center</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="sso_box">
+ <property name="visible">false</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <property name="homogeneous">true</property>
+ <property name="hexpand">true</property>
+ <property name="vexpand">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="more_sso_option">
+ <style>
+ <class name="pill"/>
+ </style>
+ <property name="halign">center</property>
+ <property name="label">More SSO Providers</property>
</object>
</child>
</object>
@@ -192,9 +212,35 @@
</property>
</object>
</child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">sso_message_page</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">360</property>
+ <property name="tightening-threshold">360</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="valign">center</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="justify">center</property>
+ <property name="label" translatable="yes">Please follow the steps in the
browser.</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
</object>
</child>
</object>
</child>
</template>
</interface>
+
+
diff --git a/src/idp_button.rs b/src/idp_button.rs
new file mode 100644
index 000000000..ac459b808
--- /dev/null
+++ b/src/idp_button.rs
@@ -0,0 +1,231 @@
+use std::convert::{TryFrom, TryInto};
+
+use gtk::{self, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+use matrix_sdk::ruma::api::client::session::get_login_types::v3::{
+ IdentityProvider, IdentityProviderBrand,
+};
+use url::Url;
+
+#[derive(Hash, Debug, Eq, PartialEq, Clone, Copy, glib::Enum)]
+#[repr(i32)]
+#[enum_type(name = "IdpBrand")]
+pub enum IdpBrand {
+ Apple = 0,
+ Facebook = 1,
+ GitHub = 2,
+ GitLab = 3,
+ Google = 4,
+ Twitter = 5,
+}
+impl Default for IdpBrand {
+ fn default() -> Self {
+ IdpBrand::Apple
+ }
+}
+
+impl TryFrom<&IdentityProviderBrand> for IdpBrand {
+ type Error = ();
+
+ fn try_from(item: &IdentityProviderBrand) -> Result<Self, Self::Error> {
+ match item {
+ IdentityProviderBrand::Apple => Ok(IdpBrand::Apple),
+ IdentityProviderBrand::Facebook => Ok(IdpBrand::Facebook),
+ IdentityProviderBrand::GitHub => Ok(IdpBrand::GitHub),
+ IdentityProviderBrand::GitLab => Ok(IdpBrand::GitLab),
+ IdentityProviderBrand::Google => Ok(IdpBrand::Google),
+ IdentityProviderBrand::Twitter => Ok(IdpBrand::Twitter),
+ _ => Err(()),
+ }
+ }
+}
+impl From<IdpBrand> for &str {
+ fn from(val: IdpBrand) -> Self {
+ let dark = adw::StyleManager::default().is_dark();
+ match val {
+ IdpBrand::Apple => {
+ if dark {
+ "idp-apple-dark"
+ } else {
+ "idp-apple"
+ }
+ }
+ IdpBrand::Facebook => "idp-facebook",
+ IdpBrand::GitHub => {
+ if dark {
+ "idp-github-dark"
+ } else {
+ "idp-github"
+ }
+ }
+ IdpBrand::GitLab => "idp-gitlab",
+ IdpBrand::Google => "idp-google",
+ IdpBrand::Twitter => "idp-twitter",
+ }
+ }
+}
+
+mod imp {
+ use std::cell::{Cell, RefCell};
+
+ use glib::subclass::InitializingObject;
+ use once_cell::sync::Lazy;
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/idp-button.ui")]
+ pub struct IdpButton {
+ pub brand: Cell<IdpBrand>,
+ pub id: RefCell<Option<String>>,
+ pub homeserver: RefCell<Option<String>>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for IdpButton {
+ const NAME: &'static str = "IdpButton";
+ type Type = super::IdpButton;
+ 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<Self>) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for IdpButton {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![
+ glib::ParamSpecEnum::new(
+ "brand",
+ "Brand",
+ "The brand of this button",
+ IdpBrand::static_type(),
+ IdpBrand::default() as i32,
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+ ),
+ glib::ParamSpecString::new(
+ "id",
+ "Id",
+ "The id of the selected identity-provider",
+ None,
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+ ),
+ glib::ParamSpecString::new(
+ "homeserver",
+ "homeserver",
+ "The homeserver url",
+ None,
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+ ),
+ ]
+ });
+
+ PROPERTIES.as_ref()
+ }
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "id" => obj.id().unwrap().to_value(),
+ "brand" => obj.brand().to_value(),
+ "homeserver" => obj.homeserver().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "brand" => {
+ obj.set_brand(value.get().unwrap());
+ }
+ "id" => {
+ obj.set_id(value.get().unwrap());
+ }
+ "homeserver" => {
+ obj.set_homeserver(value.get().unwrap());
+ }
+ _ => unimplemented!(),
+ };
+ }
+
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+ adw::StyleManager::default()
+ .connect_dark_notify(clone!(@weak obj => move |_| obj.update_icon()));
+ obj.update_icon();
+ }
+ }
+
+ impl WidgetImpl for IdpButton {}
+
+ impl ButtonImpl for IdpButton {}
+}
+glib::wrapper! {
+ pub struct IdpButton(ObjectSubclass<imp::IdpButton>)
+ @extends gtk::Widget, gtk::Button, @implements gtk::Accessible;
+}
+impl IdpButton {
+ pub fn update_icon(&self) {
+ self.set_icon_name(brand_to_icon(self.brand()));
+ }
+
+ pub fn set_id(&self, id: String) {
+ self.imp().id.replace(Some(id));
+ }
+ pub fn set_homeserver(&self, url: String) {
+ self.imp().homeserver.replace(Some(url));
+ }
+ pub fn set_brand(&self, brand: IdpBrand) {
+ self.imp().brand.replace(brand);
+ }
+ pub fn id(&self) -> Option<String> {
+ self.imp().id.borrow().clone()
+ }
+ pub fn homeserver(&self) -> Option<String> {
+ self.imp().homeserver.borrow().clone()
+ }
+ pub fn brand(&self) -> IdpBrand {
+ self.imp().brand.get()
+ }
+ pub fn new_from_identity_provider(homeserver: Url, idp: &IdentityProvider) -> Option<Self> {
+ let gidp: IdpBrand = idp.brand.as_ref()?.try_into().ok()?;
+ let ret: IdpButton = glib::Object::new(&[
+ ("brand", &gidp),
+ ("id", &idp.id),
+ ("homeserver", &homeserver.as_str()),
+ ])
+ .expect("Failed to create IdpButton");
+ Some(ret)
+ }
+}
+fn brand_to_icon(brand: IdpBrand) -> &'static str {
+ let dark = adw::StyleManager::default().is_dark();
+ match brand {
+ IdpBrand::Apple => {
+ if dark {
+ "idp-apple-dark"
+ } else {
+ "idp-apple"
+ }
+ }
+ IdpBrand::Facebook => "idp-facebook",
+ IdpBrand::GitHub => {
+ if dark {
+ "idp-github-dark"
+ } else {
+ "idp-github"
+ }
+ }
+ IdpBrand::GitLab => "idp-gitlab",
+ IdpBrand::Google => "idp-google",
+ IdpBrand::Twitter => "idp-twitter",
+ }
+}
diff --git a/src/login.rs b/src/login.rs
index fd6a2d5ae..8ad9aadf4 100644
--- a/src/login.rs
+++ b/src/login.rs
@@ -5,7 +5,14 @@ use log::{debug, warn};
use matrix_sdk::{
config::RequestConfig,
ruma::{
- api::client::discover::get_supported_versions, identifiers::Error as IdentifierError,
+ api::client::{
+ discover::get_supported_versions,
+ session::get_login_types::v3::{
+ LoginType::{Password, Sso},
+ SsoLoginType,
+ },
+ },
+ identifiers::Error as IdentifierError,
ServerName,
},
Client, Result as MatrixResult,
@@ -15,6 +22,7 @@ use url::{ParseError, Url};
use crate::{
components::{SpinnerButton, Toast},
+ idp_button::IdpButton,
login_advanced_dialog::LoginAdvancedDialog,
spawn, spawn_tokio,
user_facing_error::UserFacingError,
@@ -52,11 +60,16 @@ mod imp {
pub username_entry: TemplateChild<gtk::Entry>,
#[template_child]
pub password_entry: TemplateChild<gtk::PasswordEntry>,
+ #[template_child]
+ pub sso_box: TemplateChild<gtk::Box>,
+ #[template_child]
+ pub more_sso_option: TemplateChild<gtk::Button>,
pub prepared_source_id: RefCell<Option<SignalHandlerId>>,
pub logged_out_source_id: RefCell<Option<SignalHandlerId>>,
pub ready_source_id: RefCell<Option<SignalHandlerId>>,
/// Whether auto-discovery is enabled.
pub autodiscovery: Cell<bool>,
+ pub supports_password: Cell<bool>,
/// The homeserver to log into.
pub homeserver: RefCell<Option<Url>>,
}
@@ -159,6 +172,8 @@ mod imp {
.connect_changed(clone!(@weak obj => move |_| obj.update_next_action()));
self.password_entry
.connect_changed(clone!(@weak obj => move |_| obj.update_next_action()));
+ self.more_sso_option
+ .connect_clicked(clone!(@weak obj => move |_| obj.login_with_sso(None)));
}
}
@@ -182,6 +197,37 @@ impl Login {
self.imp().homeserver.borrow().clone()
}
+ fn reload_sso_panel(&self, login_types: &SsoLoginType) {
+ let priv_ = &mut imp::Login::from_instance(self);
+ let mut child = priv_.sso_box.first_child();
+ while child.is_some() {
+ priv_.sso_box.remove(&child.unwrap());
+ child = priv_.sso_box.first_child();
+ }
+ let mut has_unknown_methods = false;
+ let mut has_known_methods = false;
+ let homeserver: Url = self.homeserver().unwrap();
+ for provider in login_types.identity_providers.iter() {
+ let opt_brand = provider.brand.as_ref();
+ if opt_brand.is_none() {
+ has_unknown_methods = true;
+ continue;
+ }
+ let btn = IdpButton::new_from_identity_provider(homeserver.clone(), provider);
+ if let Some(real) = btn {
+ self.imp().sso_box.append(&real);
+ real.connect_clicked(
+ clone!(@weak self as obj => move |btn| obj.login_with_sso(btn.id())),
+ );
+ has_known_methods = true;
+ } else {
+ has_unknown_methods = true;
+ }
+ }
+ priv_.sso_box.set_visible(has_known_methods);
+ priv_.more_sso_option.set_visible(has_unknown_methods);
+ }
+
pub fn homeserver_pretty(&self) -> Option<String> {
let homeserver = self.homeserver();
homeserver
@@ -253,6 +299,13 @@ impl Login {
fn backward(&self) {
match self.visible_child().as_ref() {
"password" => self.set_visible_child("homeserver"),
+ "sso_message_page" => {
+ self.set_visible_child(if self.imp().supports_password.get() {
+ "password"
+ } else {
+ "homeserver"
+ });
+ }
_ => {
self.activate_action("app.show-greeter", None).unwrap();
}
@@ -307,7 +360,7 @@ impl Login {
Ok(client) => {
let homeserver = client.homeserver().await;
obj.set_homeserver(Some(homeserver));
- obj.show_password_page();
+ obj.check_login_types(client).await;
}
Err(error) => {
warn!("Failed to discover homeserver: {}", error);
@@ -319,6 +372,42 @@ impl Login {
);
}
+ fn switch_off_sso(&self) {
+ let priv_ = self.imp();
+ priv_.sso_box.set_visible(false);
+ priv_.more_sso_option.set_visible(false);
+ }
+
+ async fn check_login_types(&self, client: Client) {
+ let login_types = spawn_tokio!(async move { client.get_login_types().await })
+ .await
+ .unwrap()
+ .unwrap();
+ let sso = login_types
+ .flows
+ .iter()
+ .find(|flow| matches!(flow, Sso(_sso_providers)));
+ let password = login_types
+ .flows
+ .iter()
+ .find(|flow| matches!(flow, Password(_)));
+ let has_sso = sso.is_some();
+ let has_password = password.is_some();
+ self.imp().supports_password.replace(has_password);
+ if has_sso && has_password {
+ if let Sso(login_type) = sso.unwrap() {
+ self.reload_sso_panel(login_type);
+ }
+ } else if !has_sso {
+ self.switch_off_sso();
+ }
+ if has_password {
+ self.show_password_page();
+ } else {
+ self.login_with_sso(None);
+ }
+ }
+
fn check_homeserver(&self) {
let homeserver = build_homeserver_url(self.imp().homeserver_entry.text().as_str()).unwrap();
let homeserver_clone = homeserver.clone();
@@ -397,6 +486,23 @@ impl Login {
priv_.current_session.replace(Some(session));
}
+ fn login_with_sso(&self, idp_id: Option<String>) {
+ let priv_ = imp::Login::from_instance(self);
+ let homeserver = self.homeserver().unwrap();
+ self.set_visible_child("sso_message_page");
+
+ let session = Session::new();
+ self.set_handler_for_prepared_session(&session);
+ spawn!(
+ glib::PRIORITY_DEFAULT_IDLE,
+ clone!(@weak session, @weak self as s => async move {
+ session.login_with_sso(homeserver, idp_id).await;
+ s.set_visible_child("homeserver");
+ })
+ );
+ priv_.current_session.replace(Some(session));
+ }
+
pub fn clean(&self) {
let priv_ = self.imp();
priv_.homeserver_entry.set_text("");
diff --git a/src/main.rs b/src/main.rs
index 72577f0f6..3ba18e61a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,6 +11,7 @@ mod components;
mod contrib;
mod error_page;
mod greeter;
+mod idp_button;
mod login;
mod login_advanced_dialog;
mod secret;
diff --git a/src/session/mod.rs b/src/session/mod.rs
index c5ab3922f..db515e7e3 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -361,6 +361,62 @@ impl Session {
self.handle_login_result(handle.await.unwrap(), true).await;
}
+ pub async fn login_with_sso(&self, homeserver: Url, idp_id: Option<String>) {
+ self.imp().logout_on_dispose.set(true);
+ let mut path = glib::user_data_dir();
+ path.push(glib::uuid_string_random().as_str());
+ let passphrase: String = {
+ let mut rng = thread_rng();
+ (&mut rng)
+ .sample_iter(Alphanumeric)
+ .take(30)
+ .map(char::from)
+ .collect()
+ };
+ let handle = spawn_tokio!(async move {
+ let client = create_client(&homeserver, &path, &passphrase, true).await?;
+
+ let response = client
+ .login_with_sso(
+ |sso_url| async move {
+ let ctx = glib::MainContext::default();
+ ctx.spawn(async move {
+ gtk::show_uri(gtk::Window::NONE, &sso_url, gdk::CURRENT_TIME);
+ });
+ Ok(())
+ },
+ None,
+ None,
+ None,
+ Some("Fractal Next"),
+ idp_id.as_deref(),
+ )
+ .await;
+ match response {
+ Ok(response) => Ok((
+ client,
+ StoredSession {
+ homeserver,
+ path,
+ user_id: response.user_id,
+ device_id: response.device_id,
+ secret: Secret {
+ passphrase,
+ access_token: response.access_token,
+ },
+ },
+ )),
+ Err(error) => {
+ // Remove the store created by Client::new()
+ fs::remove_dir_all(path).unwrap();
+ Err(error.into())
+ }
+ }
+ });
+
+ self.handle_login_result(handle.await.unwrap(), true).await;
+ }
+
pub async fn login_with_previous_session(&self, session: StoredSession) {
let handle = spawn_tokio!(async move {
let client = create_client(
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]