From b60a4a68c7858f54b1d49678c6314473ab873d21 Mon Sep 17 00:00:00 2001 From: BlackDex Date: Sun, 18 Dec 2022 17:57:35 +0100 Subject: [PATCH 1/3] Fix a panic during Yubikey register/login The yubico crate uses blocking reqwest, and we called the `verify` from a async thread. To prevent issues we need to wrap it within a `spawn_blocking`. --- src/api/core/two_factor/yubikey.rs | 14 ++++++++------ src/api/identity.rs | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/api/core/two_factor/yubikey.rs b/src/api/core/two_factor/yubikey.rs index 00ef7df2..b076dd00 100644 --- a/src/api/core/two_factor/yubikey.rs +++ b/src/api/core/two_factor/yubikey.rs @@ -67,14 +67,16 @@ fn get_yubico_credentials() -> Result<(String, String), Error> { } } -fn verify_yubikey_otp(otp: String) -> EmptyResult { +async fn verify_yubikey_otp(otp: String) -> EmptyResult { let (yubico_id, yubico_secret) = get_yubico_credentials()?; let config = Config::default().set_client_id(yubico_id).set_key(yubico_secret); match CONFIG.yubico_server() { - Some(server) => verify(otp, config.set_api_hosts(vec![server])), - None => verify(otp, config), + Some(server) => { + tokio::task::spawn_blocking(move || verify(otp, config.set_api_hosts(vec![server]))).await.unwrap() + } + None => tokio::task::spawn_blocking(move || verify(otp, config)).await.unwrap(), } .map_res("Failed to verify OTP") .and(Ok(())) @@ -152,7 +154,7 @@ async fn activate_yubikey( continue; } - verify_yubikey_otp(yubikey.to_owned()).map_res("Invalid Yubikey OTP provided")?; + verify_yubikey_otp(yubikey.to_owned()).await.map_res("Invalid Yubikey OTP provided")?; } let yubikey_ids: Vec = yubikeys.into_iter().map(|x| (x[..12]).to_owned()).collect(); @@ -188,7 +190,7 @@ async fn activate_yubikey_put( activate_yubikey(data, headers, conn, ip).await } -pub fn validate_yubikey_login(response: &str, twofactor_data: &str) -> EmptyResult { +pub async fn validate_yubikey_login(response: &str, twofactor_data: &str) -> EmptyResult { if response.len() != 44 { err!("Invalid Yubikey OTP length"); } @@ -200,7 +202,7 @@ pub fn validate_yubikey_login(response: &str, twofactor_data: &str) -> EmptyResu err!("Given Yubikey is not registered"); } - let result = verify_yubikey_otp(response.to_owned()); + let result = verify_yubikey_otp(response.to_owned()).await; match result { Ok(_answer) => Ok(()), diff --git a/src/api/identity.rs b/src/api/identity.rs index 6499ee38..1939aabc 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -395,7 +395,7 @@ async fn twofactor_auth( Some(TwoFactorType::Webauthn) => { _tf::webauthn::validate_webauthn_login(user_uuid, twofactor_code, conn).await? } - Some(TwoFactorType::YubiKey) => _tf::yubikey::validate_yubikey_login(twofactor_code, &selected_data?)?, + Some(TwoFactorType::YubiKey) => _tf::yubikey::validate_yubikey_login(twofactor_code, &selected_data?).await?, Some(TwoFactorType::Duo) => { _tf::duo::validate_duo_login(data.username.as_ref().unwrap().trim(), twofactor_code, conn).await? } From 8e5f03972e978cec9254a7b52bdbb01ffbd891e6 Mon Sep 17 00:00:00 2001 From: BlackDex Date: Thu, 15 Dec 2022 15:57:30 +0100 Subject: [PATCH 2/3] Fix recover-2fa not working. When audit logging was introduced there entered a small bug preventing the recover-2fa from working. This PR fixes that by add a new headers check to extract the device-type when possible and use that for the logging. Fixes #2985 --- src/api/core/two_factor/mod.rs | 11 ++++++++--- src/api/identity.rs | 18 +++++++++++------- src/auth.rs | 22 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index ce3cfb72..a2bbc806 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -6,7 +6,7 @@ use serde_json::Value; use crate::{ api::{core::log_user_event, JsonResult, JsonUpcase, NumberOrString, PasswordData}, - auth::{ClientIp, Headers}, + auth::{ClientHeaders, ClientIp, Headers}, crypto, db::{models::*, DbConn, DbPool}, mail, CONFIG, @@ -73,7 +73,12 @@ struct RecoverTwoFactor { } #[post("/two-factor/recover", data = "")] -async fn recover(data: JsonUpcase, headers: Headers, mut conn: DbConn, ip: ClientIp) -> JsonResult { +async fn recover( + data: JsonUpcase, + client_headers: ClientHeaders, + mut conn: DbConn, + ip: ClientIp, +) -> JsonResult { let data: RecoverTwoFactor = data.into_inner().data; use crate::db::models::User; @@ -97,7 +102,7 @@ async fn recover(data: JsonUpcase, headers: Headers, mut conn: // Remove all twofactors from the user TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?; - log_user_event(EventType::UserRecovered2fa as i32, &user.uuid, headers.device.atype, &ip.ip, &mut conn).await; + log_user_event(EventType::UserRecovered2fa as i32, &user.uuid, client_headers.device_type, &ip.ip, &mut conn).await; // Remove the recovery code, not needed without twofactors user.totp_recover = None; diff --git a/src/api/identity.rs b/src/api/identity.rs index 1939aabc..0cb1c03a 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -14,7 +14,7 @@ use crate::{ core::two_factor::{duo, email, email::EmailTokenData, yubikey}, ApiResult, EmptyResult, JsonResult, JsonUpcase, }, - auth::ClientIp, + auth::{ClientHeaders, ClientIp}, db::{models::*, DbConn}, error::MapResult, mail, util, CONFIG, @@ -25,11 +25,10 @@ pub fn routes() -> Vec { } #[post("/connect/token", data = "")] -async fn login(data: Form, mut conn: DbConn, ip: ClientIp) -> JsonResult { +async fn login(data: Form, client_header: ClientHeaders, mut conn: DbConn, ip: ClientIp) -> JsonResult { let data: ConnectData = data.into_inner(); let mut user_uuid: Option = None; - let device_type = data.device_type.clone(); let login_result = match data.grant_type.as_ref() { "refresh_token" => { @@ -59,15 +58,20 @@ async fn login(data: Form, mut conn: DbConn, ip: ClientIp) -> JsonR }; if let Some(user_uuid) = user_uuid { - // When unknown or unable to parse, return 14, which is 'Unknown Browser' - let device_type = util::try_parse_string(device_type).unwrap_or(14); match &login_result { Ok(_) => { - log_user_event(EventType::UserLoggedIn as i32, &user_uuid, device_type, &ip.ip, &mut conn).await; + log_user_event( + EventType::UserLoggedIn as i32, + &user_uuid, + client_header.device_type, + &ip.ip, + &mut conn, + ) + .await; } Err(e) => { if let Some(ev) = e.get_event() { - log_user_event(ev.event as i32, &user_uuid, device_type, &ip.ip, &mut conn).await + log_user_event(ev.event as i32, &user_uuid, client_header.device_type, &ip.ip, &mut conn).await } } } diff --git a/src/auth.rs b/src/auth.rs index f3e3af8b..69c5203d 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -315,6 +315,28 @@ impl<'r> FromRequest<'r> for Host { } } +pub struct ClientHeaders { + pub host: String, + pub device_type: i32, +} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for ClientHeaders { + type Error = &'static str; + + async fn from_request(request: &'r Request<'_>) -> Outcome { + let host = try_outcome!(Host::from_request(request).await).host; + // When unknown or unable to parse, return 14, which is 'Unknown Browser' + let device_type: i32 = + request.headers().get_one("device-type").map(|d| d.parse().unwrap_or(14)).unwrap_or_else(|| 14); + + Outcome::Success(ClientHeaders { + host, + device_type, + }) + } +} + pub struct Headers { pub host: String, pub device: Device, From 67a584c1d461016907efdf9bfee4ce26cba29178 Mon Sep 17 00:00:00 2001 From: BlackDex Date: Thu, 15 Dec 2022 17:15:48 +0100 Subject: [PATCH 3/3] Disable groups by default and Some optimizations - Put groups support behind a feature flag, and disabled by default. The reason is that it has some known issues, but we want to keep optimizing this feature. Putting it behind a feature flag could help some users, and the developers into optimizing this feature without to much trouble. Further: - Updates Rust to v1.66.0 - Updated GHA workflows - Updated Alpine to 3.17 - Updated jquery to v3.6.2 - Moved jdenticon.js to load at the bottom, fixes an issue on chromium - Added autocomplete attribute to admin login password field - Added some extra CSP options (Tested this on Safari, Firefox, Chrome, Bitwarden Desktop) - Moved uppercase convertion from runtime to compile-time using `paste` for building the environment variables, lowers heap allocations. --- .env.template | 7 ++ .github/workflows/build.yml | 5 +- .github/workflows/hadolint.yml | 5 +- .github/workflows/release.yml | 5 +- .pre-commit-config.yaml | 4 +- Cargo.lock | 41 +++---- Cargo.toml | 4 +- docker/Dockerfile.j2 | 18 +-- docker/amd64/Dockerfile | 2 +- docker/amd64/Dockerfile.alpine | 4 +- docker/amd64/Dockerfile.buildx | 2 +- docker/amd64/Dockerfile.buildx.alpine | 4 +- docker/arm64/Dockerfile | 2 +- docker/arm64/Dockerfile.alpine | 4 +- docker/arm64/Dockerfile.buildx | 2 +- docker/arm64/Dockerfile.buildx.alpine | 4 +- docker/armv6/Dockerfile | 2 +- docker/armv6/Dockerfile.alpine | 4 +- docker/armv6/Dockerfile.buildx | 2 +- docker/armv6/Dockerfile.buildx.alpine | 4 +- docker/armv7/Dockerfile | 2 +- docker/armv7/Dockerfile.alpine | 4 +- docker/armv7/Dockerfile.buildx | 2 +- docker/armv7/Dockerfile.buildx.alpine | 4 +- rust-toolchain | 2 +- src/api/core/organizations.rs | 44 +++++++ src/api/web.rs | 4 +- src/config.rs | 9 +- src/db/models/organization.rs | 4 +- ...ery-3.6.1.slim.js => jquery-3.6.2.slim.js} | 107 ++++++++++++++++-- src/static/templates/admin/base.hbs | 2 +- src/static/templates/admin/login.hbs | 2 +- src/static/templates/admin/organizations.hbs | 2 +- src/static/templates/admin/users.hbs | 2 +- src/util.rs | 8 +- 35 files changed, 229 insertions(+), 94 deletions(-) rename src/static/scripts/{jquery-3.6.1.slim.js => jquery-3.6.2.slim.js} (98%) diff --git a/.env.template b/.env.template index 7ccdacd3..4b323706 100644 --- a/.env.template +++ b/.env.template @@ -90,6 +90,13 @@ ## If unset (the default), events are kept indefinitely and the scheduled job is disabled! # EVENTS_DAYS_RETAIN= +## BETA FEATURE: Groups +## Controls whether group support is enabled for organizations +## This setting applies to organizations. +## Disabled by default because this is a beta feature, it contains known issues! +## KNOW WHAT YOU ARE DOING! +# ORG_GROUPS_ENABLED=false + ## Job scheduler settings ## ## Job schedules use a cron-like syntax (as parsed by https://crates.io/crates/cron), diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1afb9c3..b8718a20 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,6 +21,7 @@ on: jobs: build: runs-on: ubuntu-20.04 + timeout-minutes: 120 # Make warnings errors, this is to prevent warnings slipping through. # This is done globally to prevent rebuilds when the RUSTFLAGS env variable changes. env: @@ -40,7 +41,7 @@ jobs: steps: # Checkout the repo - name: "Checkout" - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0 # End Checkout the repo # Install dependencies @@ -78,7 +79,7 @@ jobs: # Enable Rust Caching - - uses: Swatinem/rust-cache@b5ec9edd911d3bf82c74038b0a28791e0aa24d6f # v2.0.2 + - uses: Swatinem/rust-cache@359a70e43a0bb8a13953b04a90f76428b4959bb6 # v2.2.0 # End Enable Rust Caching diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index 5b1e77ad..6598311a 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -9,10 +9,11 @@ jobs: hadolint: name: Validate Dockerfile syntax runs-on: ubuntu-20.04 + timeout-minutes: 30 steps: # Checkout the repo - name: Checkout - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0 # End Checkout the repo @@ -23,7 +24,7 @@ jobs: sudo curl -L https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}/hadolint-$(uname -s)-$(uname -m) -o /usr/local/bin/hadolint && \ sudo chmod +x /usr/local/bin/hadolint env: - HADOLINT_VERSION: 2.10.0 + HADOLINT_VERSION: 2.12.0 # End Download hadolint # Test Dockerfiles diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aca21ca5..b3690ceb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Skip Duplicates Actions id: skip_check - uses: fkirc/skip-duplicate-actions@f11521568414503656a5af807dc3018c012552c4 # v5.2.0 + uses: fkirc/skip-duplicate-actions@12aca0a884f6137d619d6a8a09fcc3406ced5281 # v5.3.0 with: cancel_others: 'true' # Only run this when not creating a tag @@ -39,6 +39,7 @@ jobs: docker-build: runs-on: ubuntu-20.04 + timeout-minutes: 120 needs: skip_check # Start a local docker registry to be used to generate multi-arch images. services: @@ -60,7 +61,7 @@ jobs: steps: # Checkout the repo - name: Checkout - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0 with: fetch-depth: 0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eb44b47f..a8a68242 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: language: system args: ["--features", "sqlite,mysql,postgresql,enable_mimalloc", "--"] types_or: [rust, file] - files: (Cargo.toml|Cargo.lock|.*\.rs$) + files: (Cargo.toml|Cargo.lock|rust-toolchain|.*\.rs$) pass_filenames: false - id: cargo-clippy name: cargo clippy @@ -36,5 +36,5 @@ repos: language: system args: ["--features", "sqlite,mysql,postgresql,enable_mimalloc", "--", "-D", "warnings"] types_or: [rust, file] - files: (Cargo.toml|Cargo.lock|.*\.rs$) + files: (Cargo.toml|Cargo.lock|rust-toolchain|.*\.rs$) pass_filenames: false diff --git a/Cargo.lock b/Cargo.lock index 5da48e65..8bf1ee42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ "gimli", ] @@ -165,15 +165,15 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line", "cc", "cfg-if", "libc", - "miniz_oxide 0.5.4", + "miniz_oxide", "object", "rustc-demangle", ] @@ -283,9 +283,9 @@ checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" [[package]] name = "cc" -version = "1.0.77" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg-if" @@ -773,7 +773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", - "miniz_oxide 0.6.2", + "miniz_oxide", ] [[package]] @@ -947,9 +947,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.2" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" [[package]] name = "glob" @@ -1492,15 +1492,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.6.2" @@ -1663,9 +1654,9 @@ dependencies = [ [[package]] name = "object" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb" dependencies = [ "memchr", ] @@ -1777,9 +1768,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "cf1c2c742266c2f1041c914ba65355a83ae8747b05f208319784083583494b4b" [[package]] name = "pear" @@ -2891,9 +2882,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" dependencies = [ "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 3dcbe689..4f4e4f0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ log = "0.4.17" fern = { version = "0.6.1", features = ["syslog-6"] } tracing = { version = "0.1.37", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work -backtrace = "0.3.66" # Logging panics to logfile instead stderr only +backtrace = "0.3.67" # Logging panics to logfile instead stderr only # A `dotenv` implementation for Rust dotenvy = { version = "0.15.6", default-features = false } @@ -142,7 +142,7 @@ openssl = "0.10.44" pico-args = "0.5.0" # Macro ident concatenation -paste = "1.0.9" +paste = "1.0.10" governor = "0.5.1" # Check client versions for specific features. diff --git a/docker/Dockerfile.j2 b/docker/Dockerfile.j2 index 826f1360..54f3f325 100644 --- a/docker/Dockerfile.j2 +++ b/docker/Dockerfile.j2 @@ -3,23 +3,23 @@ # This file was generated using a Jinja2 template. # Please make your changes in `Dockerfile.j2` and then `make` the individual Dockerfiles. -{% set build_stage_base_image = "rust:1.65-bullseye" %} +{% set build_stage_base_image = "rust:1.66-bullseye" %} {% if "alpine" in target_file %} {% if "amd64" in target_file %} -{% set build_stage_base_image = "blackdex/rust-musl:x86_64-musl-stable-1.65.0" %} -{% set runtime_stage_base_image = "alpine:3.16" %} +{% set build_stage_base_image = "blackdex/rust-musl:x86_64-musl-stable-1.66.0" %} +{% set runtime_stage_base_image = "alpine:3.17" %} {% set package_arch_target = "x86_64-unknown-linux-musl" %} {% elif "armv7" in target_file %} -{% set build_stage_base_image = "blackdex/rust-musl:armv7-musleabihf-stable-1.65.0" %} -{% set runtime_stage_base_image = "balenalib/armv7hf-alpine:3.16" %} +{% set build_stage_base_image = "blackdex/rust-musl:armv7-musleabihf-stable-1.66.0" %} +{% set runtime_stage_base_image = "balenalib/armv7hf-alpine:3.17" %} {% set package_arch_target = "armv7-unknown-linux-musleabihf" %} {% elif "armv6" in target_file %} -{% set build_stage_base_image = "blackdex/rust-musl:arm-musleabi-stable-1.65.0" %} -{% set runtime_stage_base_image = "balenalib/rpi-alpine:3.16" %} +{% set build_stage_base_image = "blackdex/rust-musl:arm-musleabi-stable-1.66.0" %} +{% set runtime_stage_base_image = "balenalib/rpi-alpine:3.17" %} {% set package_arch_target = "arm-unknown-linux-musleabi" %} {% elif "arm64" in target_file %} -{% set build_stage_base_image = "blackdex/rust-musl:aarch64-musl-stable-1.65.0" %} -{% set runtime_stage_base_image = "balenalib/aarch64-alpine:3.16" %} +{% set build_stage_base_image = "blackdex/rust-musl:aarch64-musl-stable-1.66.0" %} +{% set runtime_stage_base_image = "balenalib/aarch64-alpine:3.17" %} {% set package_arch_target = "aarch64-unknown-linux-musl" %} {% endif %} {% elif "amd64" in target_file %} diff --git a/docker/amd64/Dockerfile b/docker/amd64/Dockerfile index a93e5b39..32af309f 100644 --- a/docker/amd64/Dockerfile +++ b/docker/amd64/Dockerfile @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM rust:1.65-bullseye as build +FROM rust:1.66-bullseye as build diff --git a/docker/amd64/Dockerfile.alpine b/docker/amd64/Dockerfile.alpine index 26faf679..4f05a095 100644 --- a/docker/amd64/Dockerfile.alpine +++ b/docker/amd64/Dockerfile.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:x86_64-musl-stable-1.65.0 as build +FROM blackdex/rust-musl:x86_64-musl-stable-1.66.0 as build @@ -81,7 +81,7 @@ RUN cargo build --features ${DB} --release --target=x86_64-unknown-linux-musl ######################## RUNTIME IMAGE ######################## # Create a new stage with a minimal image # because we already have a binary built -FROM alpine:3.16 +FROM alpine:3.17 ENV ROCKET_PROFILE="release" \ ROCKET_ADDRESS=0.0.0.0 \ diff --git a/docker/amd64/Dockerfile.buildx b/docker/amd64/Dockerfile.buildx index ae901769..c93d1a2d 100644 --- a/docker/amd64/Dockerfile.buildx +++ b/docker/amd64/Dockerfile.buildx @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM rust:1.65-bullseye as build +FROM rust:1.66-bullseye as build diff --git a/docker/amd64/Dockerfile.buildx.alpine b/docker/amd64/Dockerfile.buildx.alpine index 8bb49c28..bd018afd 100644 --- a/docker/amd64/Dockerfile.buildx.alpine +++ b/docker/amd64/Dockerfile.buildx.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:x86_64-musl-stable-1.65.0 as build +FROM blackdex/rust-musl:x86_64-musl-stable-1.66.0 as build @@ -81,7 +81,7 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/. ######################## RUNTIME IMAGE ######################## # Create a new stage with a minimal image # because we already have a binary built -FROM alpine:3.16 +FROM alpine:3.17 ENV ROCKET_PROFILE="release" \ ROCKET_ADDRESS=0.0.0.0 \ diff --git a/docker/arm64/Dockerfile b/docker/arm64/Dockerfile index e45f0d8c..62f4549d 100644 --- a/docker/arm64/Dockerfile +++ b/docker/arm64/Dockerfile @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM rust:1.65-bullseye as build +FROM rust:1.66-bullseye as build diff --git a/docker/arm64/Dockerfile.alpine b/docker/arm64/Dockerfile.alpine index ab0b3dd5..77f8a013 100644 --- a/docker/arm64/Dockerfile.alpine +++ b/docker/arm64/Dockerfile.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:aarch64-musl-stable-1.65.0 as build +FROM blackdex/rust-musl:aarch64-musl-stable-1.66.0 as build @@ -81,7 +81,7 @@ RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-musl ######################## RUNTIME IMAGE ######################## # Create a new stage with a minimal image # because we already have a binary built -FROM balenalib/aarch64-alpine:3.16 +FROM balenalib/aarch64-alpine:3.17 ENV ROCKET_PROFILE="release" \ ROCKET_ADDRESS=0.0.0.0 \ diff --git a/docker/arm64/Dockerfile.buildx b/docker/arm64/Dockerfile.buildx index 0ac3403e..c8c4356d 100644 --- a/docker/arm64/Dockerfile.buildx +++ b/docker/arm64/Dockerfile.buildx @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM rust:1.65-bullseye as build +FROM rust:1.66-bullseye as build diff --git a/docker/arm64/Dockerfile.buildx.alpine b/docker/arm64/Dockerfile.buildx.alpine index c56642a9..d293c26c 100644 --- a/docker/arm64/Dockerfile.buildx.alpine +++ b/docker/arm64/Dockerfile.buildx.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:aarch64-musl-stable-1.65.0 as build +FROM blackdex/rust-musl:aarch64-musl-stable-1.66.0 as build @@ -81,7 +81,7 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/. ######################## RUNTIME IMAGE ######################## # Create a new stage with a minimal image # because we already have a binary built -FROM balenalib/aarch64-alpine:3.16 +FROM balenalib/aarch64-alpine:3.17 ENV ROCKET_PROFILE="release" \ ROCKET_ADDRESS=0.0.0.0 \ diff --git a/docker/armv6/Dockerfile b/docker/armv6/Dockerfile index 6d0ef129..7f4ec834 100644 --- a/docker/armv6/Dockerfile +++ b/docker/armv6/Dockerfile @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM rust:1.65-bullseye as build +FROM rust:1.66-bullseye as build diff --git a/docker/armv6/Dockerfile.alpine b/docker/armv6/Dockerfile.alpine index 3d0f390f..879d1061 100644 --- a/docker/armv6/Dockerfile.alpine +++ b/docker/armv6/Dockerfile.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:arm-musleabi-stable-1.65.0 as build +FROM blackdex/rust-musl:arm-musleabi-stable-1.66.0 as build @@ -83,7 +83,7 @@ RUN cargo build --features ${DB} --release --target=arm-unknown-linux-musleabi ######################## RUNTIME IMAGE ######################## # Create a new stage with a minimal image # because we already have a binary built -FROM balenalib/rpi-alpine:3.16 +FROM balenalib/rpi-alpine:3.17 ENV ROCKET_PROFILE="release" \ ROCKET_ADDRESS=0.0.0.0 \ diff --git a/docker/armv6/Dockerfile.buildx b/docker/armv6/Dockerfile.buildx index 338e3193..7e543ef5 100644 --- a/docker/armv6/Dockerfile.buildx +++ b/docker/armv6/Dockerfile.buildx @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM rust:1.65-bullseye as build +FROM rust:1.66-bullseye as build diff --git a/docker/armv6/Dockerfile.buildx.alpine b/docker/armv6/Dockerfile.buildx.alpine index 248b8c16..4320cb76 100644 --- a/docker/armv6/Dockerfile.buildx.alpine +++ b/docker/armv6/Dockerfile.buildx.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:arm-musleabi-stable-1.65.0 as build +FROM blackdex/rust-musl:arm-musleabi-stable-1.66.0 as build @@ -83,7 +83,7 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/. ######################## RUNTIME IMAGE ######################## # Create a new stage with a minimal image # because we already have a binary built -FROM balenalib/rpi-alpine:3.16 +FROM balenalib/rpi-alpine:3.17 ENV ROCKET_PROFILE="release" \ ROCKET_ADDRESS=0.0.0.0 \ diff --git a/docker/armv7/Dockerfile b/docker/armv7/Dockerfile index 49d91ad0..d279cc65 100644 --- a/docker/armv7/Dockerfile +++ b/docker/armv7/Dockerfile @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM rust:1.65-bullseye as build +FROM rust:1.66-bullseye as build diff --git a/docker/armv7/Dockerfile.alpine b/docker/armv7/Dockerfile.alpine index a818461d..691900e2 100644 --- a/docker/armv7/Dockerfile.alpine +++ b/docker/armv7/Dockerfile.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:armv7-musleabihf-stable-1.65.0 as build +FROM blackdex/rust-musl:armv7-musleabihf-stable-1.66.0 as build @@ -81,7 +81,7 @@ RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-musleabi ######################## RUNTIME IMAGE ######################## # Create a new stage with a minimal image # because we already have a binary built -FROM balenalib/armv7hf-alpine:3.16 +FROM balenalib/armv7hf-alpine:3.17 ENV ROCKET_PROFILE="release" \ ROCKET_ADDRESS=0.0.0.0 \ diff --git a/docker/armv7/Dockerfile.buildx b/docker/armv7/Dockerfile.buildx index 1630d49a..de9887ad 100644 --- a/docker/armv7/Dockerfile.buildx +++ b/docker/armv7/Dockerfile.buildx @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM rust:1.65-bullseye as build +FROM rust:1.66-bullseye as build diff --git a/docker/armv7/Dockerfile.buildx.alpine b/docker/armv7/Dockerfile.buildx.alpine index 9c7fe045..49d66f52 100644 --- a/docker/armv7/Dockerfile.buildx.alpine +++ b/docker/armv7/Dockerfile.buildx.alpine @@ -27,7 +27,7 @@ FROM vaultwarden/web-vault@sha256:1f124e2d1a8e9678d7b9d17587b6340fba0db298e96995552698108a43f5c1c1 as vault ########################## BUILD IMAGE ########################## -FROM blackdex/rust-musl:armv7-musleabihf-stable-1.65.0 as build +FROM blackdex/rust-musl:armv7-musleabihf-stable-1.66.0 as build @@ -81,7 +81,7 @@ RUN --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/root/. ######################## RUNTIME IMAGE ######################## # Create a new stage with a minimal image # because we already have a binary built -FROM balenalib/armv7hf-alpine:3.16 +FROM balenalib/armv7hf-alpine:3.17 ENV ROCKET_PROFILE="release" \ ROCKET_ADDRESS=0.0.0.0 \ diff --git a/rust-toolchain b/rust-toolchain index 902c7418..b6148bc0 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.65.0 +1.66.0 diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index e605080d..d50a1f12 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -1990,6 +1990,10 @@ async fn _restore_organization_user( #[get("/organizations//groups")] async fn get_groups(org_id: String, _headers: ManagerHeadersLoose, mut conn: DbConn) -> JsonResult { + if !CONFIG.org_groups_enabled() { + err!("Group support is disabled"); + } + let groups = Group::find_by_organization(&org_id, &mut conn).await.iter().map(Group::to_json).collect::(); Ok(Json(json!({ @@ -2089,6 +2093,10 @@ async fn post_groups( mut conn: DbConn, ip: ClientIp, ) -> JsonResult { + if !CONFIG.org_groups_enabled() { + err!("Group support is disabled"); + } + let group_request = data.into_inner().data; let group = group_request.to_group(&org_id)?; @@ -2115,6 +2123,10 @@ async fn put_group( mut conn: DbConn, ip: ClientIp, ) -> JsonResult { + if !CONFIG.org_groups_enabled() { + err!("Group support is disabled"); + } + let group = match Group::find_by_uuid(&group_id, &mut conn).await { Some(group) => group, None => err!("Group not found"), @@ -2159,6 +2171,10 @@ async fn add_update_group(mut group: Group, collections: Vec, #[get("/organizations/<_org_id>/groups//details")] async fn get_group_details(_org_id: String, group_id: String, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { + if !CONFIG.org_groups_enabled() { + err!("Group support is disabled"); + } + let group = match Group::find_by_uuid(&group_id, &mut conn).await { Some(group) => group, _ => err!("Group could not be found!"), @@ -2199,6 +2215,10 @@ async fn delete_group( mut conn: DbConn, ip: ClientIp, ) -> EmptyResult { + if !CONFIG.org_groups_enabled() { + err!("Group support is disabled"); + } + let group = match Group::find_by_uuid(&group_id, &mut conn).await { Some(group) => group, _ => err!("Group not found"), @@ -2220,6 +2240,10 @@ async fn delete_group( #[get("/organizations/<_org_id>/groups/")] async fn get_group(_org_id: String, group_id: String, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { + if !CONFIG.org_groups_enabled() { + err!("Group support is disabled"); + } + let group = match Group::find_by_uuid(&group_id, &mut conn).await { Some(group) => group, _ => err!("Group not found"), @@ -2230,6 +2254,10 @@ async fn get_group(_org_id: String, group_id: String, _headers: AdminHeaders, mu #[get("/organizations/<_org_id>/groups//users")] async fn get_group_users(_org_id: String, group_id: String, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { + if !CONFIG.org_groups_enabled() { + err!("Group support is disabled"); + } + match Group::find_by_uuid(&group_id, &mut conn).await { Some(_) => { /* Do nothing */ } _ => err!("Group could not be found!"), @@ -2253,6 +2281,10 @@ async fn put_group_users( mut conn: DbConn, ip: ClientIp, ) -> EmptyResult { + if !CONFIG.org_groups_enabled() { + err!("Group support is disabled"); + } + match Group::find_by_uuid(&group_id, &mut conn).await { Some(_) => { /* Do nothing */ } _ => err!("Group could not be found!"), @@ -2282,6 +2314,10 @@ async fn put_group_users( #[get("/organizations/<_org_id>/users//groups")] async fn get_user_groups(_org_id: String, user_id: String, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { + if !CONFIG.org_groups_enabled() { + err!("Group support is disabled"); + } + match UserOrganization::find_by_uuid(&user_id, &mut conn).await { Some(_) => { /* Do nothing */ } _ => err!("User could not be found!"), @@ -2320,6 +2356,10 @@ async fn put_user_groups( mut conn: DbConn, ip: ClientIp, ) -> EmptyResult { + if !CONFIG.org_groups_enabled() { + err!("Group support is disabled"); + } + match UserOrganization::find_by_uuid(&org_user_id, &mut conn).await { Some(_) => { /* Do nothing */ } _ => err!("User could not be found!"), @@ -2368,6 +2408,10 @@ async fn delete_group_user( mut conn: DbConn, ip: ClientIp, ) -> EmptyResult { + if !CONFIG.org_groups_enabled() { + err!("Group support is disabled"); + } + match UserOrganization::find_by_uuid(&org_user_id, &mut conn).await { Some(_) => { /* Do nothing */ } _ => err!("User could not be found!"), diff --git a/src/api/web.rs b/src/api/web.rs index 72bb66d0..3742a088 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -107,8 +107,8 @@ pub fn static_files(filename: String) -> Result<(ContentType, &'static [u8]), Er "jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.js"))), "datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))), "datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))), - "jquery-3.6.1.slim.js" => { - Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.1.slim.js"))) + "jquery-3.6.2.slim.js" => { + Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.2.slim.js"))) } _ => err!(format!("Static file not found: {}", filename)), } diff --git a/src/config.rs b/src/config.rs index edf5bbfe..00d4737c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -85,7 +85,7 @@ macro_rules! make_config { let mut builder = ConfigBuilder::default(); $($( - builder.$name = make_config! { @getenv &stringify!($name).to_uppercase(), $ty }; + builder.$name = make_config! { @getenv paste::paste!(stringify!([<$name:upper>])), $ty }; )+)+ builder @@ -105,7 +105,7 @@ macro_rules! make_config { builder.$name = v.clone(); if self.$name.is_some() { - overrides.push(stringify!($name).to_uppercase()); + overrides.push(paste::paste!(stringify!([<$name:upper>])).into()); } } )+)+ @@ -195,7 +195,7 @@ macro_rules! make_config { element.insert("default".into(), serde_json::to_value(def.$name).unwrap()); element.insert("type".into(), (_get_form_type(stringify!($ty))).into()); element.insert("doc".into(), (_get_doc(concat!($($doc),+))).into()); - element.insert("overridden".into(), (overriden.contains(&stringify!($name).to_uppercase())).into()); + element.insert("overridden".into(), (overriden.contains(&paste::paste!(stringify!([<$name:upper>])).into())).into()); element }), )+ @@ -564,6 +564,9 @@ make_config! { admin_ratelimit_seconds: u64, false, def, 300; /// Max burst size for admin login requests |> Allow a burst of requests of up to this size, while maintaining the average indicated by `admin_ratelimit_seconds` admin_ratelimit_max_burst: u32, false, def, 3; + + /// Enable groups (BETA!) (Know the risks!) |> Enables groups support for organizations (Currently contains known issues!). + org_groups_enabled: bool, false, def, false; }, /// Yubikey settings diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 3bc2ddad..a2ab75a7 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -149,7 +149,7 @@ impl Organization { "Use2fa": true, "UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet) "UseEvents": CONFIG.org_events_enabled(), - "UseGroups": true, + "UseGroups": CONFIG.org_groups_enabled(), "UseTotp": true, "UsePolicies": true, // "UseScim": false, // Not supported (Not AGPLv3 Licensed) @@ -304,7 +304,7 @@ impl UserOrganization { "Use2fa": true, "UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet) "UseEvents": CONFIG.org_events_enabled(), - "UseGroups": true, + "UseGroups": CONFIG.org_groups_enabled(), "UseTotp": true, // "UseScim": false, // Not supported (Not AGPLv3 Licensed) "UsePolicies": true, diff --git a/src/static/scripts/jquery-3.6.1.slim.js b/src/static/scripts/jquery-3.6.2.slim.js similarity index 98% rename from src/static/scripts/jquery-3.6.1.slim.js rename to src/static/scripts/jquery-3.6.2.slim.js index 91512c95..4c41f3eb 100644 --- a/src/static/scripts/jquery-3.6.1.slim.js +++ b/src/static/scripts/jquery-3.6.2.slim.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v3.6.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector + * jQuery JavaScript Library v3.6.2 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector * https://jquery.com/ * * Includes Sizzle.js @@ -9,7 +9,7 @@ * Released under the MIT license * https://jquery.org/license * - * Date: 2022-08-26T17:52Z + * Date: 2022-12-13T14:56Z */ ( function( global, factory ) { @@ -151,7 +151,7 @@ function toType( obj ) { var - version = "3.6.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector", + version = "3.6.2 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector", // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -522,14 +522,14 @@ function isArrayLike( obj ) { } var Sizzle = /*! - * Sizzle CSS Selector Engine v2.3.6 + * Sizzle CSS Selector Engine v2.3.8 * https://sizzlejs.com/ * * Copyright JS Foundation and other contributors * Released under the MIT license * https://js.foundation/ * - * Date: 2021-02-16 + * Date: 2022-11-16 */ ( function( window ) { var i, @@ -879,6 +879,27 @@ function Sizzle( selector, context, results, seed ) { } try { + + // `qSA` may not throw for unrecognized parts using forgiving parsing: + // https://drafts.csswg.org/selectors/#forgiving-selector + // like the `:has()` pseudo-class: + // https://drafts.csswg.org/selectors/#relational + // `CSS.supports` is still expected to return `false` then: + // https://drafts.csswg.org/css-conditional-4/#typedef-supports-selector-fn + // https://drafts.csswg.org/css-conditional-4/#dfn-support-selector + if ( support.cssSupportsSelector && + + // eslint-disable-next-line no-undef + !CSS.supports( "selector(" + newSelector + ")" ) ) { + + // Support: IE 11+ + // Throw to get to the same code path as an error directly in qSA. + // Note: once we only support browser supporting + // `CSS.supports('selector(...)')`, we can most likely drop + // the `try-catch`. IE doesn't implement the API. + throw new Error(); + } + push.apply( results, newContext.querySelectorAll( newSelector ) ); @@ -1174,6 +1195,31 @@ setDocument = Sizzle.setDocument = function( node ) { !el.querySelectorAll( ":scope fieldset div" ).length; } ); + // Support: Chrome 105+, Firefox 104+, Safari 15.4+ + // Make sure forgiving mode is not used in `CSS.supports( "selector(...)" )`. + // + // `:is()` uses a forgiving selector list as an argument and is widely + // implemented, so it's a good one to test against. + support.cssSupportsSelector = assert( function() { + /* eslint-disable no-undef */ + + return CSS.supports( "selector(*)" ) && + + // Support: Firefox 78-81 only + // In old Firefox, `:is()` didn't use forgiving parsing. In that case, + // fail this test as there's no selector to test against that. + // `CSS.supports` uses unforgiving parsing + document.querySelectorAll( ":is(:jqfake)" ) && + + // `*` is needed as Safari & newer Chrome implemented something in between + // for `:has()` - it throws in `qSA` if it only contains an unsupported + // argument but multiple ones, one of which is supported, are fine. + // We want to play safe in case `:is()` gets the same treatment. + !CSS.supports( "selector(:is(*,:jqfake))" ); + + /* eslint-enable */ + } ); + /* Attributes ---------------------------------------------------------------------- */ @@ -1440,6 +1486,18 @@ setDocument = Sizzle.setDocument = function( node ) { } ); } + if ( !support.cssSupportsSelector ) { + + // Support: Chrome 105+, Safari 15.4+ + // `:has()` uses a forgiving selector list as an argument so our regular + // `try-catch` mechanism fails to catch `:has()` with arguments not supported + // natively like `:has(:contains("Foo"))`. Where supported & spec-compliant, + // we now use `CSS.supports("selector(SELECTOR_TO_BE_TESTED)")` but outside + // that, let's mark `:has` as buggy to always use jQuery traversal for + // `:has()`. + rbuggyQSA.push( ":has" ); + } + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); @@ -1452,7 +1510,14 @@ setDocument = Sizzle.setDocument = function( node ) { // As in, an element does not contain itself contains = hasCompare || rnative.test( docElem.contains ) ? function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, + + // Support: IE <9 only + // IE doesn't have `contains` on `document` so we need to check for + // `documentElement` presence. + // We need to fall back to `a` when `documentElement` is missing + // as `ownerDocument` of elements within `