diff --git a/Cargo.lock b/Cargo.lock index 7a5f9367..11e86206 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -375,12 +375,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.8.0" @@ -551,6 +545,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.39" @@ -929,7 +929,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" dependencies = [ - "bitflags 2.8.0", + "bitflags", "proc-macro2", "proc-macro2-diagnostics", "quote", @@ -943,7 +943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04001f23ba8843dc315804fa324000376084dfb1c30794ff68dd279e6e5696d5" dependencies = [ "bigdecimal", - "bitflags 2.8.0", + "bitflags", "byteorder", "chrono", "diesel_derives", @@ -1549,25 +1549,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.7.1", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.7" @@ -1828,7 +1809,6 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -1851,7 +1831,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.7", + "h2", "http 1.2.0", "http-body 1.0.1", "httparse", @@ -1862,20 +1842,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", -] - [[package]] name = "hyper-rustls" version = "0.27.5" @@ -1891,6 +1857,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.1", "tower-service", + "webpki-roots", ] [[package]] @@ -2662,16 +2629,16 @@ dependencies = [ [[package]] name = "oauth2" -version = "4.4.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", "chrono", "getrandom 0.2.15", - "http 0.2.12", + "http 1.2.0", "rand 0.8.5", - "reqwest 0.11.27", + "reqwest", "serde", "serde_json", "serde_path_to_error", @@ -2697,16 +2664,16 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openidconnect" -version = "3.5.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47e80a9cfae4462dd29c41e987edd228971d6565553fbc14b8a11e666d91590" +checksum = "6dd50d4a5e7730e754f94d977efe61f611aadd3131f6a2b464f6e3a4167e8ef7" dependencies = [ - "base64 0.13.1", + "base64 0.21.7", "chrono", "dyn-clone", "ed25519-dalek", "hmac", - "http 0.2.12", + "http 1.2.0", "itertools", "log", "oauth2", @@ -2716,7 +2683,6 @@ dependencies = [ "rsa", "serde", "serde-value", - "serde_derive", "serde_json", "serde_path_to_error", "serde_plain", @@ -2733,7 +2699,7 @@ version = "0.10.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" dependencies = [ - "bitflags 2.8.0", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -3184,7 +3150,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 2.8.0", + "bitflags", "memchr", "unicase", ] @@ -3210,6 +3176,58 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quinn" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.22", + "socket2", + "thiserror 2.0.11", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom 0.2.15", + "rand 0.8.5", + "ring", + "rustc-hash", + "rustls 0.23.22", + "rustls-pki-types", + "slab", + "thiserror 2.0.11", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.38" @@ -3303,7 +3321,7 @@ version = "11.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6928fa44c097620b706542d428957635951bade7143269085389d42c8a4927e" dependencies = [ - "bitflags 2.8.0", + "bitflags", ] [[package]] @@ -3312,7 +3330,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.8.0", + "bitflags", ] [[package]] @@ -3390,47 +3408,6 @@ dependencies = [ "signal-hook", ] -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-rustls 0.24.2", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration 0.5.1", - "tokio", - "tokio-rustls 0.24.1", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "winreg", -] - [[package]] name = "reqwest" version = "0.12.12" @@ -3446,12 +3423,12 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.7", + "h2", "http 1.2.0", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", - "hyper-rustls 0.27.5", + "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", @@ -3462,14 +3439,18 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.22", "rustls-pemfile 2.2.0", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", - "system-configuration 0.6.1", + "sync_wrapper", + "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls 0.26.1", "tokio-socks", "tokio-util", "tower", @@ -3479,6 +3460,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots", "windows-registry", ] @@ -3680,6 +3662,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + [[package]] name = "rustc_version" version = "0.4.1" @@ -3695,7 +3683,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.8.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -3721,6 +3709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" dependencies = [ "once_cell", + "ring", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle", @@ -3750,6 +3739,9 @@ name = "rustls-pki-types" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" @@ -3853,7 +3845,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.8.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -4210,12 +4202,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -4248,36 +4234,15 @@ dependencies = [ "time", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys 0.5.0", -] - [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.8.0", + "bitflags", "core-foundation", - "system-configuration-sys 0.6.0", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", + "system-configuration-sys", ] [[package]] @@ -4589,7 +4554,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -4853,7 +4818,7 @@ dependencies = [ "pico-args", "rand 0.9.0", "regex", - "reqwest 0.12.12", + "reqwest", "ring", "rmpv", "rocket", @@ -5045,9 +5010,12 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.4" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "which" @@ -5335,7 +5303,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "bitflags 2.8.0", + "bitflags", ] [[package]] @@ -5393,7 +5361,7 @@ dependencies = [ "futures", "hmac", "rand 0.8.5", - "reqwest 0.12.12", + "reqwest", "sha1", "threadpool", ] diff --git a/Cargo.toml b/Cargo.toml index fade3166..64401e4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,10 @@ enable_mimalloc = ["dep:mimalloc"] # if you want to turn off the logging for a specific run. query_logger = ["dep:diesel_logger"] +# OIDC specific features +oidc-accept-rfc3339-timestamps = ["openidconnect/accept-rfc3339-timestamps"] +oidc-accept-string-booleans = ["openidconnect/accept-string-booleans"] + # Enable unstable features, requires nightly # Currently only used to enable rusts official ip support unstable = [] @@ -157,7 +161,7 @@ paste = "1.0.15" governor = "0.8.0" # OIDC for SSO -openidconnect = "3.5.0" +openidconnect = { version = "4.0.0", features = ["reqwest", "native-tls"] } mini-moka = "0.10.2" # Check client versions for specific features. diff --git a/src/sso.rs b/src/sso.rs index 19531b27..ff30a13c 100644 --- a/src/sso.rs +++ b/src/sso.rs @@ -10,11 +10,11 @@ use once_cell::sync::Lazy; use openidconnect::core::{ CoreClient, CoreIdTokenVerifier, CoreProviderMetadata, CoreResponseType, CoreUserInfoClaims, }; -use openidconnect::reqwest::async_http_client; +use openidconnect::reqwest; use openidconnect::{ AccessToken, AuthDisplay, AuthPrompt, AuthenticationFlow, AuthorizationCode, AuthorizationRequest, ClientId, - ClientSecret, CsrfToken, Nonce, OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, RefreshToken, - ResponseType, Scope, + ClientSecret, CsrfToken, EndpointNotSet, EndpointSet, Nonce, OAuth2TokenResponse, PkceCodeChallenge, + PkceCodeVerifier, RefreshToken, ResponseType, Scope, }; use crate::{ @@ -32,7 +32,7 @@ static AC_CACHE: Lazy> = Lazy::new(|| Cache::builder().max_capacity(1000).time_to_live(Duration::from_secs(10 * 60)).build()); static CLIENT_CACHE_KEY: Lazy = Lazy::new(|| "sso-client".to_string()); -static CLIENT_CACHE: Lazy> = Lazy::new(|| { +static CLIENT_CACHE: Lazy> = Lazy::new(|| { Cache::builder().max_capacity(1).time_to_live(Duration::from_secs(CONFIG.sso_client_cache_expiration())).build() }); @@ -178,36 +178,55 @@ fn decode_token_claims(token_name: &str, token: &str) -> ApiResult ApiResult; - async fn cached() -> ApiResult; - - async fn user_info_async(&self, access_token: AccessToken) -> ApiResult; - - fn vw_id_token_verifier(&self) -> CoreIdTokenVerifier<'_>; +#[derive(Clone)] +struct Client { + http_client: reqwest::Client, + core_client: CoreClient, } -#[rocket::async_trait] -impl CoreClientExt for CoreClient { +impl Client { // Call the OpenId discovery endpoint to retrieve configuration - async fn _get_client() -> ApiResult { + async fn _get_client() -> ApiResult { let client_id = ClientId::new(CONFIG.sso_client_id()); let client_secret = ClientSecret::new(CONFIG.sso_client_secret()); let issuer_url = CONFIG.sso_issuer_url()?; - let provider_metadata = match CoreProviderMetadata::discover_async(issuer_url, async_http_client).await { + let http_client = match reqwest::ClientBuilder::new().redirect(reqwest::redirect::Policy::none()).build() { + Err(err) => err!(format!("Failed to build http client: {err}")), + Ok(client) => client, + }; + + let provider_metadata = match CoreProviderMetadata::discover_async(issuer_url, &http_client).await { Err(err) => err!(format!("Failed to discover OpenID provider: {err}")), Ok(metadata) => metadata, }; - Ok(CoreClient::from_provider_metadata(provider_metadata, client_id, Some(client_secret)) - .set_redirect_uri(CONFIG.sso_redirect_url()?)) + let base_client = CoreClient::from_provider_metadata(provider_metadata, client_id, Some(client_secret)); + + let token_uri = match base_client.token_uri() { + Some(uri) => uri.clone(), + None => err!("Failed to discover token_url, cannot proceed"), + }; + + let user_info_url = match base_client.user_info_url() { + Some(url) => url.clone(), + None => err!("Failed to discover user_info url, cannot proceed"), + }; + + let core_client = base_client + .set_redirect_uri(CONFIG.sso_redirect_url()?) + .set_token_uri(token_uri) + .set_user_info_url(user_info_url); + + Ok(Client { + http_client, + core_client, + }) } // Simple cache to prevent recalling the discovery endpoint each time - async fn cached() -> ApiResult { + async fn cached() -> ApiResult { if CONFIG.sso_client_cache_expiration() > 0 { match CLIENT_CACHE.get(&*CLIENT_CACHE_KEY) { Some(client) => Ok(client), @@ -221,20 +240,15 @@ impl CoreClientExt for CoreClient { } } - async fn user_info_async(&self, access_token: AccessToken) -> ApiResult { - let endpoint = match self.user_info(access_token, None) { - Err(err) => err!(format!("No user_info endpoint: {err}")), - Ok(endpoint) => endpoint, - }; - - match endpoint.request_async(async_http_client).await { + async fn user_info(&self, access_token: AccessToken) -> ApiResult { + match self.core_client.user_info(access_token, None).request_async(&self.http_client).await { Err(err) => err!(format!("Request to user_info endpoint failed: {err}")), Ok(user_info) => Ok(user_info), } } fn vw_id_token_verifier(&self) -> CoreIdTokenVerifier<'_> { - let mut verifier = self.id_token_verifier(); + let mut verifier = self.core_client.id_token_verifier(); if let Some(regex_str) = CONFIG.sso_audience_trusted() { match Regex::new(®ex_str) { Ok(regex) => { @@ -286,8 +300,9 @@ pub async fn authorize_url( _ => err!(format!("Unsupported client {client_id}")), }; - let client = CoreClient::cached().await?; + let client = Client::cached().await?; let mut auth_req = client + .core_client .authorize_url( AuthenticationFlow::::AuthorizationCode, || CsrfToken::new(base64_state), @@ -402,14 +417,14 @@ pub async fn exchange_code(wrapped_code: &str, conn: &mut DbConn) -> ApiResult err!(format!("Invalid state cannot retrieve nonce")), Some(nonce) => nonce, }; - let mut exchange = client.exchange_code(oidc_code); + let mut exchange = client.core_client.exchange_code(oidc_code); if CONFIG.sso_pkce() { match nonce.verifier { @@ -418,9 +433,9 @@ pub async fn exchange_code(wrapped_code: &str, conn: &mut DbConn) -> ApiResult { - let user_info = client.user_info_async(token_response.access_token().to_owned()).await?; + let user_info = client.user_info(token_response.access_token().to_owned()).await?; let oidc_nonce = Nonce::new(nonce.nonce.clone()); let id_token = match token_response.extra_fields().id_token() { @@ -578,12 +593,13 @@ pub async fn exchange_refresh_token( Some(TokenWrapper::Refresh(refresh_token)) => { let rt = RefreshToken::new(refresh_token.to_string()); - let client = CoreClient::cached().await?; + let client = Client::cached().await?; - let token_response = match client.exchange_refresh_token(&rt).request_async(async_http_client).await { - Err(err) => err!(format!("Request to exchange_refresh_token endpoint failed: {:?}", err)), - Ok(token_response) => token_response, - }; + let token_response = + match client.core_client.exchange_refresh_token(&rt).request_async(&client.http_client).await { + Err(err) => err!(format!("Request to exchange_refresh_token endpoint failed: {:?}", err)), + Ok(token_response) => token_response, + }; // Use new refresh_token if returned let rolled_refresh_token = token_response @@ -607,8 +623,8 @@ pub async fn exchange_refresh_token( err_silent!("Access token is close to expiration but we have no refresh token") } - let client = CoreClient::cached().await?; - match client.user_info_async(AccessToken::new(access_token.to_string())).await { + let client = Client::cached().await?; + match client.user_info(AccessToken::new(access_token.to_string())).await { Err(err) => { err_silent!(format!("Failed to retrieve user info, token has probably been invalidated: {err}")) }