refactor(oidc_cli): Use LocalServerBuilder
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
This commit is contained in:
committed by
Ivan Enderlin
parent
1b5e6462ee
commit
f8abb85e9e
Generated
+6
-84
@@ -377,47 +377,13 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core 0.4.5",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit 0.7.3",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
|
||||
dependencies = [
|
||||
"axum-core 0.5.0",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
@@ -427,7 +393,7 @@ dependencies = [
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit 0.8.4",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
@@ -439,28 +405,7 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -1601,7 +1546,6 @@ name = "example-oidc-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
"dirs 5.0.1",
|
||||
"futures-util",
|
||||
"matrix-sdk",
|
||||
@@ -1610,7 +1554,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tower 0.4.13",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
]
|
||||
@@ -2871,12 +2814,6 @@ dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
@@ -2920,7 +2857,7 @@ dependencies = [
|
||||
"async-channel",
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"axum 0.8.1",
|
||||
"axum",
|
||||
"backoff",
|
||||
"bytes",
|
||||
"bytesize",
|
||||
@@ -2968,7 +2905,7 @@ dependencies = [
|
||||
"tokio-stream",
|
||||
"tokio-test",
|
||||
"tokio-util",
|
||||
"tower 0.5.2",
|
||||
"tower",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uniffi",
|
||||
@@ -4380,7 +4317,7 @@ dependencies = [
|
||||
"tokio-rustls",
|
||||
"tokio-socks",
|
||||
"tokio-util",
|
||||
"tower 0.5.2",
|
||||
"tower",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
@@ -5454,21 +5391,6 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
|
||||
@@ -11,7 +11,6 @@ test = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
axum = "0.7.4"
|
||||
dirs = "5.0.1"
|
||||
futures-util = { workspace = true }
|
||||
matrix-sdk-ui = { path = "../../crates/matrix-sdk-ui" }
|
||||
@@ -19,13 +18,12 @@ rand = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
||||
tower = { version = "0.4.13", features = ["util"] }
|
||||
tracing-subscriber = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
[dependencies.matrix-sdk]
|
||||
path = "../../crates/matrix-sdk"
|
||||
features = ["experimental-oidc"]
|
||||
features = ["experimental-oidc", "local-server"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+25
-109
@@ -13,20 +13,13 @@
|
||||
// limitations under the License.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::IntoFuture,
|
||||
io::{self, Write},
|
||||
ops::Range,
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail};
|
||||
use axum::{
|
||||
http::{Method, Request, StatusCode},
|
||||
response::IntoResponse,
|
||||
routing::any_service,
|
||||
};
|
||||
use futures_util::StreamExt;
|
||||
use matrix_sdk::{
|
||||
authentication::oidc::{
|
||||
@@ -42,13 +35,13 @@ use matrix_sdk::{
|
||||
events::room::message::{MessageType, OriginalSyncRoomMessageEvent},
|
||||
serde::Raw,
|
||||
},
|
||||
utils::local_server::{LocalServerBuilder, LocalServerRedirectHandle},
|
||||
Client, ClientBuildError, Result, RoomState,
|
||||
};
|
||||
use matrix_sdk_ui::sync_service::SyncService;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{fs, io::AsyncBufReadExt as _, net::TcpListener, sync::oneshot};
|
||||
use tower::service_fn;
|
||||
use tokio::{fs, io::AsyncBufReadExt as _};
|
||||
use url::Url;
|
||||
|
||||
/// A command-line tool to demonstrate the steps requiring an interaction with
|
||||
@@ -224,12 +217,12 @@ impl OidcCli {
|
||||
// Here we spawn a server to listen on the loopback interface. Another option
|
||||
// would be to register a custom URI scheme with the system and handle
|
||||
// the redirect when the custom URI scheme is opened.
|
||||
let (redirect_uri, data_rx, signal_tx) = spawn_local_server().await?;
|
||||
let (redirect_uri, server_handle) = LocalServerBuilder::new().spawn().await?;
|
||||
|
||||
let OidcAuthorizationData { url, state } =
|
||||
oidc.login(redirect_uri, None)?.build().await?;
|
||||
|
||||
let authorization_code = match use_auth_url(&url, &state, data_rx, signal_tx).await {
|
||||
let authorization_code = match use_auth_url(&url, &state, server_handle).await {
|
||||
Ok(code) => code,
|
||||
Err(err) => {
|
||||
oidc.abort_authorization(&state).await;
|
||||
@@ -707,10 +700,13 @@ async fn build_client(data_dir: &Path) -> anyhow::Result<(Client, ClientSession)
|
||||
/// possible, for example by using the most secure signing algorithms supported
|
||||
/// by the provider.
|
||||
fn client_metadata() -> Raw<ClientMetadata> {
|
||||
// Native clients should be able to register the loopback interface and then
|
||||
// point to any port when needing a redirect URI. An alternative is to use a
|
||||
// custom URI scheme registered with the OS.
|
||||
let redirect_uri = Url::parse("http://127.0.0.1").expect("Couldn't parse redirect URI");
|
||||
// Native clients should be able to register the IPv4 and IPv6 loopback
|
||||
// interfaces and then point to any port when needing a redirect URI. An
|
||||
// alternative is to use a custom URI scheme registered with the OS.
|
||||
let ipv4_localhost_uri = Url::parse(&format!("http://{}/", Ipv4Addr::LOCALHOST))
|
||||
.expect("Couldn't parse IPv4 redirect URI");
|
||||
let ipv6_localhost_uri = Url::parse(&format!("http://[{}]/", Ipv6Addr::LOCALHOST))
|
||||
.expect("Couldn't parse IPv6 redirect URI");
|
||||
let client_uri = Localized::new(
|
||||
Url::parse("https://github.com/matrix-org/matrix-rust-sdk")
|
||||
.expect("Couldn't parse client URI"),
|
||||
@@ -730,7 +726,9 @@ fn client_metadata() -> Raw<ClientMetadata> {
|
||||
// browser).
|
||||
ApplicationType::Native,
|
||||
// We are going to use the Authorization Code flow.
|
||||
vec![OauthGrantType::AuthorizationCode { redirect_uris: vec![redirect_uri] }],
|
||||
vec![OauthGrantType::AuthorizationCode {
|
||||
redirect_uris: vec![ipv4_localhost_uri, ipv6_localhost_uri],
|
||||
}],
|
||||
client_uri,
|
||||
)
|
||||
};
|
||||
@@ -744,21 +742,20 @@ fn client_metadata() -> Raw<ClientMetadata> {
|
||||
async fn use_auth_url(
|
||||
url: &Url,
|
||||
state: &CsrfToken,
|
||||
data_rx: oneshot::Receiver<String>,
|
||||
signal_tx: oneshot::Sender<()>,
|
||||
server_handle: LocalServerRedirectHandle,
|
||||
) -> anyhow::Result<AuthorizationCode> {
|
||||
println!("\nPlease authenticate yourself at: {url}\n");
|
||||
println!("Then proceed to the authorization.\n");
|
||||
|
||||
let response_query = data_rx.await?;
|
||||
signal_tx.send(()).expect("Receiver is still alive");
|
||||
let response_query = server_handle.await;
|
||||
|
||||
let code = match AuthorizationResponse::parse_query(&response_query)? {
|
||||
AuthorizationResponse::Success(code) => code,
|
||||
AuthorizationResponse::Error(err) => {
|
||||
return Err(anyhow!(err.error));
|
||||
}
|
||||
};
|
||||
let code =
|
||||
match AuthorizationResponse::parse_query(response_query.as_deref().unwrap_or_default())? {
|
||||
AuthorizationResponse::Success(code) => code,
|
||||
AuthorizationResponse::Error(err) => {
|
||||
return Err(anyhow!(err.error));
|
||||
}
|
||||
};
|
||||
|
||||
// Here we only manage one authorization at a time so, if the state string is
|
||||
// wrong, it is an error. Some clients might want to allow several
|
||||
@@ -771,87 +768,6 @@ async fn use_auth_url(
|
||||
Ok(code)
|
||||
}
|
||||
|
||||
/// Spawn a local server to listen on redirects at the end of the authorization
|
||||
/// process.
|
||||
///
|
||||
/// Returns the URL the server listens to, the receiver that will receive the
|
||||
/// data returned by the provider and a sender to shutdown the server.
|
||||
async fn spawn_local_server(
|
||||
) -> anyhow::Result<(Url, oneshot::Receiver<String>, oneshot::Sender<()>)> {
|
||||
/// The range of ports the SSO server will try to bind to randomly.
|
||||
///
|
||||
/// This is used to avoid binding to a port blocked by browsers.
|
||||
/// See <https://fetch.spec.whatwg.org/#port-blocking>.
|
||||
const SSO_SERVER_BIND_RANGE: Range<u16> = 20000..30000;
|
||||
/// The number of times the SSO server will try to bind to a random port
|
||||
const SSO_SERVER_BIND_TRIES: u8 = 10;
|
||||
|
||||
// The channel used to shutdown the server when we are done with it.
|
||||
let (signal_tx, signal_rx) = oneshot::channel::<()>();
|
||||
// The channel used to transmit the data received a the redirect URL.
|
||||
let (data_tx, data_rx) = oneshot::channel::<String>();
|
||||
let data_tx_mutex = Arc::new(Mutex::new(Some(data_tx)));
|
||||
|
||||
// We bind to the IPv4 loopback interface.
|
||||
let mut redirect_url = Url::parse("http://127.0.0.1:0/")
|
||||
.expect("Couldn't parse good known loopback interface URL");
|
||||
|
||||
// Bind a TCP listener to a random port.
|
||||
let listener = {
|
||||
let host = redirect_url.host_str().expect("The redirect URL doesn't have a host");
|
||||
let mut n = 0u8;
|
||||
|
||||
loop {
|
||||
let port = thread_rng().gen_range(SSO_SERVER_BIND_RANGE);
|
||||
match TcpListener::bind((host, port)).await {
|
||||
Ok(l) => {
|
||||
redirect_url
|
||||
.set_port(Some(port))
|
||||
.expect("Could not set new port on redirect URL");
|
||||
break l;
|
||||
}
|
||||
Err(_) if n < SSO_SERVER_BIND_TRIES => {
|
||||
n += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Set up the server.
|
||||
let router = any_service(service_fn(move |request: Request<_>| {
|
||||
let data_tx_mutex = data_tx_mutex.clone();
|
||||
async move {
|
||||
// Reject methods others than HEAD or GET.
|
||||
if request.method() != Method::HEAD && request.method() != Method::GET {
|
||||
return Ok::<_, Infallible>(StatusCode::METHOD_NOT_ALLOWED.into_response());
|
||||
}
|
||||
|
||||
// We only need to get the first response so we consume the transmitter the
|
||||
// first time.
|
||||
if let Some(data_tx) = data_tx_mutex.lock().unwrap().take() {
|
||||
let query_string = request.uri().query().unwrap_or_default();
|
||||
|
||||
data_tx.send(query_string.to_owned()).expect("The receiver is still alive");
|
||||
}
|
||||
|
||||
Ok("The authorization step is complete. You can close this page and go back to the oidc-cli.".into_response())
|
||||
}
|
||||
}));
|
||||
|
||||
let server = axum::serve(listener, router)
|
||||
.with_graceful_shutdown(async {
|
||||
signal_rx.await.ok();
|
||||
})
|
||||
.into_future();
|
||||
|
||||
tokio::spawn(server);
|
||||
|
||||
Ok((redirect_url, data_rx, signal_tx))
|
||||
}
|
||||
|
||||
/// Handle room messages.
|
||||
async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) {
|
||||
// We only want to log text messages in joined rooms.
|
||||
|
||||
Reference in New Issue
Block a user