mirror of
https://github.com/dani-garcia/vaultwarden.wiki.git
synced 2026-10-25 07:06:49 +02:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3714b504f6 | ||
|
|
f9f011d50c | ||
|
|
cb89088bc3 | ||
|
|
4444442075 | ||
|
|
061694d0cb | ||
|
|
32098ca7d1 |
No files matched your search
@@ -409,6 +409,7 @@
|
|||||||
## - "desktop-ui-migration-milestone-3": Special feature flag for desktop UI (Desktop >= 2026.2.0)
|
## - "desktop-ui-migration-milestone-3": Special feature flag for desktop UI (Desktop >= 2026.2.0)
|
||||||
## - "desktop-ui-migration-milestone-4": Special feature flag for desktop UI (Desktop >= 2026.2.0)
|
## - "desktop-ui-migration-milestone-4": Special feature flag for desktop UI (Desktop >= 2026.2.0)
|
||||||
## - "enable-basic-auth-response": Enable HTTP Basic Auth autofill in the browser extension (Browser >= 2026.9.0)
|
## - "enable-basic-auth-response": Enable HTTP Basic Auth autofill in the browser extension (Browser >= 2026.9.0)
|
||||||
|
## - "undetermined-cipher-scenario-logic": Enable the rewritten add/update login notification triggering logic in the browser extension (Browser >= 2026.2.0)
|
||||||
# EXPERIMENTAL_CLIENT_FEATURE_FLAGS=
|
# EXPERIMENTAL_CLIENT_FEATURE_FLAGS=
|
||||||
|
|
||||||
## Require new device emails. When a user logs in an email is required to be sent.
|
## Require new device emails. When a user logs in an email is required to be sent.
|
||||||
|
|||||||
@@ -101,9 +101,17 @@ RUN . /env-cargo && \
|
|||||||
# https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977
|
# https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977
|
||||||
# https://github.com/dani-garcia/vaultwarden/discussions/7522
|
# https://github.com/dani-garcia/vaultwarden/discussions/7522
|
||||||
if xx-info is-cross; then \
|
if xx-info is-cross; then \
|
||||||
XX_RUSTFLAGS="-C link-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \
|
XX_RUSTFLAGS="-Clink-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \
|
||||||
export XX_RUSTFLAGS; \
|
|
||||||
fi && \
|
fi && \
|
||||||
|
# Debian's GCC adds a special fix for cortex-a53 arch's which gets losts when using xx-cargo, which uses clang
|
||||||
|
# Checking if the arch is arm64 and adding this extra link-arg should cause this fix to be applied
|
||||||
|
# This should fix issues reported when running on a RPi3 which is a Cortex-A35 chip
|
||||||
|
# See: https://salsa.debian.org/toolchain-team/gcc/-/blob/master/debian/rules2?pow_referer=#L543-548
|
||||||
|
# See: https://github.com/dani-garcia/vaultwarden/issues/7754
|
||||||
|
if [ "$(xx-info arch)" = "arm64" ]; then \
|
||||||
|
XX_RUSTFLAGS="${XX_RUSTFLAGS} -Clink-arg=-Wl,--fix-cortex-a53-843419"; \
|
||||||
|
fi && \
|
||||||
|
export XX_RUSTFLAGS && \
|
||||||
PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" && \
|
PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" && \
|
||||||
find . -not -path "./target*" -delete
|
find . -not -path "./target*" -delete
|
||||||
|
|
||||||
@@ -123,9 +131,17 @@ RUN . /env-cargo && \
|
|||||||
# https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977
|
# https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977
|
||||||
# https://github.com/dani-garcia/vaultwarden/discussions/7522
|
# https://github.com/dani-garcia/vaultwarden/discussions/7522
|
||||||
if xx-info is-cross; then \
|
if xx-info is-cross; then \
|
||||||
XX_RUSTFLAGS="-C link-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \
|
XX_RUSTFLAGS="-Clink-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \
|
||||||
export XX_RUSTFLAGS; \
|
|
||||||
fi && \
|
fi && \
|
||||||
|
# Debian's GCC adds a special fix for cortex-a53 arch's which gets losts when using xx-cargo, which uses clang
|
||||||
|
# Checking if the arch is arm64 and adding this extra link-arg should cause this fix to be applied
|
||||||
|
# This should fix issues reported when running on a RPi3 which is a Cortex-A35 chip
|
||||||
|
# See: https://salsa.debian.org/toolchain-team/gcc/-/blob/master/debian/rules2?pow_referer=#L543-548
|
||||||
|
# See: https://github.com/dani-garcia/vaultwarden/issues/7754
|
||||||
|
if [ "$(xx-info arch)" = "arm64" ]; then \
|
||||||
|
XX_RUSTFLAGS="${XX_RUSTFLAGS} -Clink-arg=-Wl,--fix-cortex-a53-843419"; \
|
||||||
|
fi && \
|
||||||
|
export XX_RUSTFLAGS && \
|
||||||
PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" && \
|
PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" && \
|
||||||
if [ "${CARGO_PROFILE}" = "dev" ] ; then \
|
if [ "${CARGO_PROFILE}" = "dev" ] ; then \
|
||||||
ln -vfsr "/app/target/${CARGO_TARGET}/debug" /app/target/final ; \
|
ln -vfsr "/app/target/${CARGO_TARGET}/debug" /app/target/final ; \
|
||||||
|
|||||||
+10
-2
@@ -32,9 +32,17 @@
|
|||||||
# https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977
|
# https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977
|
||||||
# https://github.com/dani-garcia/vaultwarden/discussions/7522
|
# https://github.com/dani-garcia/vaultwarden/discussions/7522
|
||||||
if xx-info is-cross; then \
|
if xx-info is-cross; then \
|
||||||
XX_RUSTFLAGS="-C link-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \
|
XX_RUSTFLAGS="-Clink-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \
|
||||||
export XX_RUSTFLAGS; \
|
|
||||||
fi && \
|
fi && \
|
||||||
|
# Debian's GCC adds a special fix for cortex-a53 arch's which gets losts when using xx-cargo, which uses clang
|
||||||
|
# Checking if the arch is arm64 and adding this extra link-arg should cause this fix to be applied
|
||||||
|
# This should fix issues reported when running on a RPi3 which is a Cortex-A35 chip
|
||||||
|
# See: https://salsa.debian.org/toolchain-team/gcc/-/blob/master/debian/rules2?pow_referer=#L543-548
|
||||||
|
# See: https://github.com/dani-garcia/vaultwarden/issues/7754
|
||||||
|
if [ "$(xx-info arch)" = "arm64" ]; then \
|
||||||
|
XX_RUSTFLAGS="${XX_RUSTFLAGS} -Clink-arg=-Wl,--fix-cortex-a53-843419"; \
|
||||||
|
fi && \
|
||||||
|
export XX_RUSTFLAGS && \
|
||||||
PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}"
|
PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}"
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@{{ vault_image_digest }} AS vault
|
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@{{ vault_image_digest }} AS vault
|
||||||
|
|||||||
+13
-13
@@ -905,23 +905,23 @@ mod tests {
|
|||||||
fn validate_web_vault_compare() {
|
fn validate_web_vault_compare() {
|
||||||
// web_vault_compare(active, latest)
|
// web_vault_compare(active, latest)
|
||||||
// Test normal versions
|
// Test normal versions
|
||||||
assert!(web_vault_compare("2025.12.0", "2025.12.1") == -1);
|
assert_eq!(web_vault_compare("2025.12.0", "2025.12.1"), -1);
|
||||||
assert!(web_vault_compare("2025.12.1", "2025.12.1") == 0);
|
assert_eq!(web_vault_compare("2025.12.1", "2025.12.1"), 0);
|
||||||
assert!(web_vault_compare("2025.12.2", "2025.12.1") == 1);
|
assert_eq!(web_vault_compare("2025.12.2", "2025.12.1"), 1);
|
||||||
|
|
||||||
// Test patched/+build.n versions
|
// Test patched/+build.n versions
|
||||||
// Newer latest version
|
// Newer latest version
|
||||||
assert!(web_vault_compare("2025.12.0+build.1", "2025.12.1") == -1);
|
assert_eq!(web_vault_compare("2025.12.0+build.1", "2025.12.1"), -1);
|
||||||
assert!(web_vault_compare("2025.12.1", "2025.12.1+build.1") == -1);
|
assert_eq!(web_vault_compare("2025.12.1", "2025.12.1+build.1"), -1);
|
||||||
assert!(web_vault_compare("2025.12.0+build.1", "2025.12.1+build.1") == -1);
|
assert_eq!(web_vault_compare("2025.12.0+build.1", "2025.12.1+build.1"), -1);
|
||||||
assert!(web_vault_compare("2025.12.1+build.1", "2025.12.1+build.2") == -1);
|
assert_eq!(web_vault_compare("2025.12.1+build.1", "2025.12.1+build.2"), -1);
|
||||||
// Equal versions
|
// Equal versions
|
||||||
assert!(web_vault_compare("2025.12.1+build.1", "2025.12.1+build.1") == 0);
|
assert_eq!(web_vault_compare("2025.12.1+build.1", "2025.12.1+build.1"), 0);
|
||||||
assert!(web_vault_compare("2025.12.2+build.2", "2025.12.2+build.2") == 0);
|
assert_eq!(web_vault_compare("2025.12.2+build.2", "2025.12.2+build.2"), 0);
|
||||||
// Newer active version
|
// Newer active version
|
||||||
assert!(web_vault_compare("2025.12.1+build.1", "2025.12.1") == 1);
|
assert_eq!(web_vault_compare("2025.12.1+build.1", "2025.12.1"), 1);
|
||||||
assert!(web_vault_compare("2025.12.2", "2025.12.1+build.1") == 1);
|
assert_eq!(web_vault_compare("2025.12.2", "2025.12.1+build.1"), 1);
|
||||||
assert!(web_vault_compare("2025.12.2+build.1", "2025.12.1+build.1") == 1);
|
assert_eq!(web_vault_compare("2025.12.2+build.1", "2025.12.1+build.1"), 1);
|
||||||
assert!(web_vault_compare("2025.12.1+build.3", "2025.12.1+build.2") == 1);
|
assert_eq!(web_vault_compare("2025.12.1+build.3", "2025.12.1+build.2"), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2180,8 +2180,15 @@ impl CipherSyncData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate a HashMap with the Organization UUID as key and the Membership record
|
||||||
|
let members: HashMap<OrganizationId, Membership> = Membership::find_confirmed_by_user(user_id, conn)
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.map(|m| (m.org_uuid.clone(), m))
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Generate a list of Cipher UUID's containing a Vec with one or more Attachment records
|
// Generate a list of Cipher UUID's containing a Vec with one or more Attachment records
|
||||||
let orgs = Membership::get_orgs_by_user(user_id, conn).await;
|
let orgs: Vec<OrganizationId> = members.keys().cloned().collect();
|
||||||
let attachments = Attachment::find_all_by_user_and_orgs(user_id, &orgs, conn).await;
|
let attachments = Attachment::find_all_by_user_and_orgs(user_id, &orgs, conn).await;
|
||||||
let mut cipher_attachments: HashMap<CipherId, Vec<Attachment>> = HashMap::with_capacity(attachments.len());
|
let mut cipher_attachments: HashMap<CipherId, Vec<Attachment>> = HashMap::with_capacity(attachments.len());
|
||||||
for attachment in attachments {
|
for attachment in attachments {
|
||||||
@@ -2196,13 +2203,6 @@ impl CipherSyncData {
|
|||||||
cipher_collections.entry(cipher).or_default().push(collection);
|
cipher_collections.entry(cipher).or_default().push(collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a HashMap with the Organization UUID as key and the Membership record
|
|
||||||
let members: HashMap<OrganizationId, Membership> = Membership::find_confirmed_by_user(user_id, conn)
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.map(|m| (m.org_uuid.clone(), m))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Generate a HashMap with the User_Collections UUID as key and the CollectionUser record
|
// Generate a HashMap with the User_Collections UUID as key and the CollectionUser record
|
||||||
let user_collections: HashMap<CollectionId, CollectionUser> = CollectionUser::find_by_user(user_id, conn)
|
let user_collections: HashMap<CollectionId, CollectionUser> = CollectionUser::find_by_user(user_id, conn)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -415,8 +415,9 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea
|
|||||||
let col_users = CollectionUser::find_by_organization_swap_user_uuid_with_member_uuid(&org_id, &conn).await;
|
let col_users = CollectionUser::find_by_organization_swap_user_uuid_with_member_uuid(&org_id, &conn).await;
|
||||||
// Generate a HashMap to get the correct MembershipType per user to determine the manage permission
|
// Generate a HashMap to get the correct MembershipType per user to determine the manage permission
|
||||||
// We use the uuid instead of the user_uuid here, since that is what is used in CollectionUser
|
// We use the uuid instead of the user_uuid here, since that is what is used in CollectionUser
|
||||||
|
// This lists other members for admins, so it must not depend on the membership status
|
||||||
let membership_type: HashMap<MembershipId, i32> =
|
let membership_type: HashMap<MembershipId, i32> =
|
||||||
Membership::find_confirmed_by_org(&org_id, &conn).await.into_iter().map(|m| (m.uuid, m.atype)).collect();
|
Membership::find_by_org(&org_id, &conn).await.into_iter().map(|m| (m.uuid, m.atype)).collect();
|
||||||
|
|
||||||
// check if current user has full access to the organization (either directly or via any group)
|
// check if current user has full access to the organization (either directly or via any group)
|
||||||
let has_full_access_to_org = member.has_full_access()
|
let has_full_access_to_org = member.has_full_access()
|
||||||
@@ -818,11 +819,9 @@ async fn get_org_collection_detail(
|
|||||||
|
|
||||||
// Generate a HashMap to get the correct MembershipType per user to determine the manage permission
|
// Generate a HashMap to get the correct MembershipType per user to determine the manage permission
|
||||||
// We use the uuid instead of the user_uuid here, since that is what is used in CollectionUser
|
// We use the uuid instead of the user_uuid here, since that is what is used in CollectionUser
|
||||||
let membership_type: HashMap<MembershipId, i32> = Membership::find_confirmed_by_org(&org_id, &conn)
|
// This lists other members for admins, so it must not depend on the membership status
|
||||||
.await
|
let membership_type: HashMap<MembershipId, i32> =
|
||||||
.into_iter()
|
Membership::find_by_org(&org_id, &conn).await.into_iter().map(|m| (m.uuid, m.atype)).collect();
|
||||||
.map(|m| (m.uuid, m.atype))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let users: Vec<Value> =
|
let users: Vec<Value> =
|
||||||
CollectionUser::find_by_org_and_coll_swap_user_uuid_with_member_uuid(&org_id, &collection.uuid, &conn)
|
CollectionUser::find_by_org_and_coll_swap_user_uuid_with_member_uuid(&org_id, &collection.uuid, &conn)
|
||||||
@@ -960,8 +959,9 @@ async fn get_members(
|
|||||||
|
|
||||||
let mut users_json = Vec::new();
|
let mut users_json = Vec::new();
|
||||||
for u in Membership::find_by_org(&org_id, &conn).await {
|
for u in Membership::find_by_org(&org_id, &conn).await {
|
||||||
|
// The user can be a manager instead of an admin, but we've checked above that they have full access
|
||||||
users_json.push(
|
users_json.push(
|
||||||
u.to_json_user_details(
|
u.to_json_details_for_admin(
|
||||||
data.include_collections.unwrap_or(false),
|
data.include_collections.unwrap_or(false),
|
||||||
data.include_groups.unwrap_or(false),
|
data.include_groups.unwrap_or(false),
|
||||||
&conn,
|
&conn,
|
||||||
@@ -1516,7 +1516,9 @@ async fn get_user(
|
|||||||
// In this case, when groups are requested we also need to include collections.
|
// In this case, when groups are requested we also need to include collections.
|
||||||
// Else these will not be shown in the interface, and could lead to missing collections when saved.
|
// Else these will not be shown in the interface, and could lead to missing collections when saved.
|
||||||
let include_groups = data.include_groups.unwrap_or(false);
|
let include_groups = data.include_groups.unwrap_or(false);
|
||||||
Ok(Json(user.to_json_user_details(data.include_collections.unwrap_or(include_groups), include_groups, &conn).await))
|
Ok(Json(
|
||||||
|
user.to_json_details_for_admin(data.include_collections.unwrap_or(include_groups), include_groups, &conn).await,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -1835,6 +1837,10 @@ async fn post_org_import(
|
|||||||
if org_id != headers.membership.org_uuid {
|
if org_id != headers.membership.org_uuid {
|
||||||
err!("Organization not found", "Organization id's do not match");
|
err!("Organization not found", "Organization id's do not match");
|
||||||
}
|
}
|
||||||
|
// OrgMemberHeaders also allows invited and accepted members, which are not allowed to import
|
||||||
|
if headers.membership.status != MembershipStatus::Confirmed as i32 {
|
||||||
|
err!("You need to be a Member of the Organization to call this endpoint")
|
||||||
|
}
|
||||||
let data: ImportData = data.into_inner();
|
let data: ImportData = data.into_inner();
|
||||||
|
|
||||||
// Validate the import before continuing
|
// Validate the import before continuing
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ fn vaultwarden_css() -> EtagCached<Css<String>> {
|
|||||||
|
|
||||||
// Else, there is either no cache, or reload_templates is true and we need to rebuild the CSS
|
// Else, there is either no cache, or reload_templates is true and we need to rebuild the CSS
|
||||||
let css_options = json!({
|
let css_options = json!({
|
||||||
|
"email_change_allowed": CONFIG.email_change_allowed(),
|
||||||
"emergency_access_allowed": CONFIG.emergency_access_allowed(),
|
"emergency_access_allowed": CONFIG.emergency_access_allowed(),
|
||||||
"load_user_scss": true,
|
"load_user_scss": true,
|
||||||
"mail_2fa_enabled": CONFIG._enable_email_2fa(),
|
"mail_2fa_enabled": CONFIG._enable_email_2fa(),
|
||||||
|
|||||||
@@ -1427,6 +1427,7 @@ pub const SUPPORTED_FEATURE_FLAGS: &[&str] = &[
|
|||||||
"pm-5594-safari-account-switching",
|
"pm-5594-safari-account-switching",
|
||||||
"pm-32413-multi-client-password-management",
|
"pm-32413-multi-client-password-management",
|
||||||
// Autofill Team
|
// Autofill Team
|
||||||
|
"undetermined-cipher-scenario-logic",
|
||||||
"enable-basic-auth-response",
|
"enable-basic-auth-response",
|
||||||
"ssh-agent",
|
"ssh-agent",
|
||||||
"ssh-agent-v2",
|
"ssh-agent-v2",
|
||||||
@@ -1434,6 +1435,7 @@ pub const SUPPORTED_FEATURE_FLAGS: &[&str] = &[
|
|||||||
"ssh-key-vault-item",
|
"ssh-key-vault-item",
|
||||||
"pm-25373-windows-biometrics-v2",
|
"pm-25373-windows-biometrics-v2",
|
||||||
"pm-26340-linux-biometrics-v2",
|
"pm-26340-linux-biometrics-v2",
|
||||||
|
"windows-native-credential-sync",
|
||||||
// Mobile Team
|
// Mobile Team
|
||||||
"anon-addy-self-host-alias",
|
"anon-addy-self-host-alias",
|
||||||
"simple-login-self-host-alias",
|
"simple-login-self-host-alias",
|
||||||
|
|||||||
@@ -1003,6 +1003,7 @@ impl Cipher {
|
|||||||
.eq(ciphers_collections::collection_uuid)
|
.eq(ciphers_collections::collection_uuid)
|
||||||
.and(collections_groups::groups_uuid.eq(groups::uuid))),
|
.and(collections_groups::groups_uuid.eq(groups::uuid))),
|
||||||
)
|
)
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.filter(
|
.filter(
|
||||||
users_organizations::access_all
|
users_organizations::access_all
|
||||||
.eq(true) // User has access all
|
.eq(true) // User has access all
|
||||||
@@ -1034,6 +1035,7 @@ impl Cipher {
|
|||||||
.eq(ciphers_collections::collection_uuid)
|
.eq(ciphers_collections::collection_uuid)
|
||||||
.and(users_collections::user_uuid.eq(user_uuid.clone()))),
|
.and(users_collections::user_uuid.eq(user_uuid.clone()))),
|
||||||
)
|
)
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.filter(
|
.filter(
|
||||||
users_organizations::access_all
|
users_organizations::access_all
|
||||||
.eq(true) // User has access all
|
.eq(true) // User has access all
|
||||||
@@ -1078,6 +1080,7 @@ impl Cipher {
|
|||||||
.eq(ciphers_collections::collection_uuid)
|
.eq(ciphers_collections::collection_uuid)
|
||||||
.and(collections_groups::groups_uuid.eq(groups::uuid))),
|
.and(collections_groups::groups_uuid.eq(groups::uuid))),
|
||||||
)
|
)
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.filter(
|
.filter(
|
||||||
users_organizations::access_all
|
users_organizations::access_all
|
||||||
.eq(true) // User has access all
|
.eq(true) // User has access all
|
||||||
@@ -1110,6 +1113,7 @@ impl Cipher {
|
|||||||
.eq(ciphers_collections::collection_uuid)
|
.eq(ciphers_collections::collection_uuid)
|
||||||
.and(users_collections::user_uuid.eq(user_uuid.clone()))),
|
.and(users_collections::user_uuid.eq(user_uuid.clone()))),
|
||||||
)
|
)
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.filter(
|
.filter(
|
||||||
users_organizations::access_all
|
users_organizations::access_all
|
||||||
.eq(true) // User has access all
|
.eq(true) // User has access all
|
||||||
@@ -1161,6 +1165,7 @@ impl Cipher {
|
|||||||
.or_filter(users_organizations::atype.le(MembershipType::Admin as i32)) // User is admin or owner
|
.or_filter(users_organizations::atype.le(MembershipType::Admin as i32)) // User is admin or owner
|
||||||
.or_filter(groups::access_all.eq(true)) //Access via group
|
.or_filter(groups::access_all.eq(true)) //Access via group
|
||||||
.or_filter(collections_groups::collections_uuid.is_not_null()) //Access via group
|
.or_filter(collections_groups::collections_uuid.is_not_null()) //Access via group
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.select(ciphers_collections::all_columns)
|
.select(ciphers_collections::all_columns)
|
||||||
.distinct()
|
.distinct()
|
||||||
.load::<(CipherId, CollectionId)>(conn)
|
.load::<(CipherId, CollectionId)>(conn)
|
||||||
|
|||||||
@@ -367,6 +367,7 @@ impl Collection {
|
|||||||
.and(collections_groups::collections_uuid.eq(collections::uuid))),
|
.and(collections_groups::collections_uuid.eq(collections::uuid))),
|
||||||
)
|
)
|
||||||
.filter(collections::uuid.eq(uuid))
|
.filter(collections::uuid.eq(uuid))
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.filter(
|
.filter(
|
||||||
users_collections::collection_uuid
|
users_collections::collection_uuid
|
||||||
.eq(uuid)
|
.eq(uuid)
|
||||||
@@ -406,6 +407,7 @@ impl Collection {
|
|||||||
.and(users_organizations::user_uuid.eq(user_uuid))),
|
.and(users_organizations::user_uuid.eq(user_uuid))),
|
||||||
)
|
)
|
||||||
.filter(collections::uuid.eq(uuid))
|
.filter(collections::uuid.eq(uuid))
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.filter(users_collections::collection_uuid.eq(uuid).or(
|
.filter(users_collections::collection_uuid.eq(uuid).or(
|
||||||
// Directly accessed collection
|
// Directly accessed collection
|
||||||
users_organizations::access_all.eq(true).or(
|
users_organizations::access_all.eq(true).or(
|
||||||
@@ -430,7 +432,8 @@ impl Collection {
|
|||||||
.inner_join(
|
.inner_join(
|
||||||
users_organizations::table.on(collections::org_uuid
|
users_organizations::table.on(collections::org_uuid
|
||||||
.eq(users_organizations::org_uuid)
|
.eq(users_organizations::org_uuid)
|
||||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))),
|
.and(users_organizations::user_uuid.eq(user_uuid.clone()))
|
||||||
|
.and(users_organizations::status.eq(MembershipStatus::Confirmed as i32))),
|
||||||
)
|
)
|
||||||
.left_join(
|
.left_join(
|
||||||
users_collections::table.on(users_collections::collection_uuid
|
users_collections::table.on(users_collections::collection_uuid
|
||||||
@@ -476,7 +479,8 @@ impl Collection {
|
|||||||
.inner_join(
|
.inner_join(
|
||||||
users_organizations::table.on(collections::org_uuid
|
users_organizations::table.on(collections::org_uuid
|
||||||
.eq(users_organizations::org_uuid)
|
.eq(users_organizations::org_uuid)
|
||||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))),
|
.and(users_organizations::user_uuid.eq(user_uuid.clone()))
|
||||||
|
.and(users_organizations::status.eq(MembershipStatus::Confirmed as i32))),
|
||||||
)
|
)
|
||||||
.left_join(
|
.left_join(
|
||||||
users_collections::table.on(users_collections::collection_uuid
|
users_collections::table.on(users_collections::collection_uuid
|
||||||
@@ -527,6 +531,7 @@ impl Collection {
|
|||||||
.and(collections_groups::collections_uuid.eq(collections::uuid))),
|
.and(collections_groups::collections_uuid.eq(collections::uuid))),
|
||||||
)
|
)
|
||||||
.filter(collections::uuid.eq(&self.uuid))
|
.filter(collections::uuid.eq(&self.uuid))
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.filter(
|
.filter(
|
||||||
users_collections::collection_uuid
|
users_collections::collection_uuid
|
||||||
.eq(&self.uuid)
|
.eq(&self.uuid)
|
||||||
@@ -586,6 +591,7 @@ impl Collection {
|
|||||||
.and(collections_groups::collections_uuid.eq(collections::uuid))),
|
.and(collections_groups::collections_uuid.eq(collections::uuid))),
|
||||||
)
|
)
|
||||||
.filter(collections::uuid.eq(&uuid))
|
.filter(collections::uuid.eq(&uuid))
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.filter(
|
.filter(
|
||||||
users_collections::collection_uuid
|
users_collections::collection_uuid
|
||||||
.eq(&uuid)
|
.eq(&uuid)
|
||||||
@@ -650,6 +656,7 @@ impl Collection {
|
|||||||
.and(collections_groups::collections_uuid.eq(collections::uuid))),
|
.and(collections_groups::collections_uuid.eq(collections::uuid))),
|
||||||
)
|
)
|
||||||
.filter(collections::org_uuid.eq(&org_uuid))
|
.filter(collections::org_uuid.eq(&org_uuid))
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.filter(
|
.filter(
|
||||||
// Manage permission on a collection assigned directly or via a group.
|
// Manage permission on a collection assigned directly or via a group.
|
||||||
users_collections::manage.eq(true).or(collections_groups::manage.eq(true)),
|
users_collections::manage.eq(true).or(collections_groups::manage.eq(true)),
|
||||||
@@ -819,10 +826,18 @@ impl CollectionUser {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only returns the collections of organizations the user is a confirmed member of
|
||||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||||
conn.run(move |conn| {
|
conn.run(move |conn| {
|
||||||
users_collections::table
|
users_collections::table
|
||||||
|
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
|
||||||
|
.inner_join(
|
||||||
|
users_organizations::table.on(users_organizations::org_uuid
|
||||||
|
.eq(collections::org_uuid)
|
||||||
|
.and(users_organizations::user_uuid.eq(users_collections::user_uuid))),
|
||||||
|
)
|
||||||
.filter(users_collections::user_uuid.eq(user_uuid))
|
.filter(users_collections::user_uuid.eq(user_uuid))
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.select(users_collections::all_columns)
|
.select(users_collections::all_columns)
|
||||||
.load::<Self>(conn)
|
.load::<Self>(conn)
|
||||||
.expect("Error loading users_collections")
|
.expect("Error loading users_collections")
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ impl Group {
|
|||||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
|
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
|
||||||
)
|
)
|
||||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.filter(groups::access_all.eq(true))
|
.filter(groups::access_all.eq(true))
|
||||||
.select(groups::organizations_uuid)
|
.select(groups::organizations_uuid)
|
||||||
.distinct()
|
.distinct()
|
||||||
@@ -387,6 +388,7 @@ impl CollectionGroup {
|
|||||||
.and(collections::org_uuid.eq(groups::organizations_uuid))),
|
.and(collections::org_uuid.eq(groups::organizations_uuid))),
|
||||||
)
|
)
|
||||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.select(collections_groups::all_columns)
|
.select(collections_groups::all_columns)
|
||||||
.load::<Self>(conn)
|
.load::<Self>(conn)
|
||||||
.expect("Error loading user collection groups")
|
.expect("Error loading user collection groups")
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
use std::{
|
use std::cmp::Ordering;
|
||||||
cmp::Ordering,
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
};
|
|
||||||
|
|
||||||
use chrono::{NaiveDateTime, Utc};
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use derive_more::{AsRef, Deref, Display, From};
|
use derive_more::{AsRef, Deref, Display, From};
|
||||||
@@ -24,8 +21,8 @@ use crate::{
|
|||||||
use macros::UuidFromParam;
|
use macros::UuidFromParam;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Cipher, CipherId, Collection, CollectionGroup, CollectionId, CollectionUser, Group, GroupId, GroupUser, OrgPolicy,
|
Cipher, CipherId, Collection, CollectionId, CollectionUser, Group, GroupId, GroupUser, OrgPolicy, OrgPolicyType,
|
||||||
OrgPolicyType, TwoFactor, User, UserId,
|
TwoFactor, User, UserId,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||||
@@ -536,7 +533,13 @@ impl Membership {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn to_json_user_details(&self, include_collections: bool, include_groups: bool, conn: &DbConn) -> Value {
|
// Used by admins to view other members, so nothing here may depend on this member's status
|
||||||
|
pub async fn to_json_details_for_admin(
|
||||||
|
&self,
|
||||||
|
include_collections: bool,
|
||||||
|
include_groups: bool,
|
||||||
|
conn: &DbConn,
|
||||||
|
) -> Value {
|
||||||
let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
|
let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
|
||||||
|
|
||||||
// Because BitWarden want the status to be -1 for revoked users we need to catch that here.
|
// Because BitWarden want the status to be -1 for revoked users we need to catch that here.
|
||||||
@@ -559,52 +562,23 @@ impl Membership {
|
|||||||
|
|
||||||
// Check if a user is in a group which has access to all collections
|
// Check if a user is in a group which has access to all collections
|
||||||
// If that is the case, we should not return individual collections!
|
// If that is the case, we should not return individual collections!
|
||||||
|
// This is used by admins to view other members, so it must not depend on the membership status
|
||||||
let full_access_group =
|
let full_access_group =
|
||||||
CONFIG.org_groups_enabled() && Group::is_in_full_access_group(&self.user_uuid, &self.org_uuid, conn).await;
|
CONFIG.org_groups_enabled() && GroupUser::has_full_access_by_member(&self.org_uuid, &self.uuid, conn).await;
|
||||||
|
|
||||||
// If collections are to be included, only include them if the user does not have full access via a group or defined to the user it self
|
// If collections are to be included, only include them if the user does not have full access via a group or defined to the user it self
|
||||||
|
// Only the collections assigned directly are returned, the ones assigned via a group are returned via a special group endpoint
|
||||||
let collections: Vec<Value> = if include_collections && !(full_access_group || self.access_all) {
|
let collections: Vec<Value> = if include_collections && !(full_access_group || self.access_all) {
|
||||||
// Get all collections for the user here already to prevent more queries
|
CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn)
|
||||||
let cu: HashMap<CollectionId, CollectionUser> =
|
|
||||||
CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn)
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.map(|cu| (cu.collection_uuid.clone(), cu))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Get all collection groups for this user to prevent there inclusion
|
|
||||||
let cg: HashSet<CollectionId> = CollectionGroup::find_by_user(&self.user_uuid, conn)
|
|
||||||
.await
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|cg| cg.collections_uuid)
|
.map(|cu| {
|
||||||
.collect();
|
json!({
|
||||||
|
"id": cu.collection_uuid,
|
||||||
Collection::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn)
|
"readOnly": cu.read_only,
|
||||||
.await
|
"hidePasswords": cu.hide_passwords,
|
||||||
.into_iter()
|
"manage": cu.manage || (self.atype == MembershipType::Manager && !cu.read_only && !cu.hide_passwords),
|
||||||
.filter_map(|c| {
|
})
|
||||||
let (read_only, hide_passwords, manage) = if self.has_full_access() {
|
|
||||||
(false, false, self.atype >= MembershipType::Manager)
|
|
||||||
} else if let Some(cu) = cu.get(&c.uuid) {
|
|
||||||
(
|
|
||||||
cu.read_only,
|
|
||||||
cu.hide_passwords,
|
|
||||||
cu.manage || (self.atype == MembershipType::Manager && !cu.read_only && !cu.hide_passwords),
|
|
||||||
)
|
|
||||||
// If previous checks failed it might be that this user has access via a group, but we should not return those elements here
|
|
||||||
// Those are returned via a special group endpoint
|
|
||||||
} else if cg.contains(&c.uuid) {
|
|
||||||
return None;
|
|
||||||
} else {
|
|
||||||
(true, true, false)
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(json!({
|
|
||||||
"id": c.uuid,
|
|
||||||
"readOnly": read_only,
|
|
||||||
"hidePasswords": hide_passwords,
|
|
||||||
"manage": manage,
|
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
@@ -1032,17 +1006,6 @@ impl Membership {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_orgs_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<OrganizationId> {
|
|
||||||
conn.run(move |conn| {
|
|
||||||
users_organizations::table
|
|
||||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
|
||||||
.select(users_organizations::org_uuid)
|
|
||||||
.load::<OrganizationId>(conn)
|
|
||||||
.unwrap_or_default()
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn find_by_user_and_policy(user_uuid: &UserId, policy_type: OrgPolicyType, conn: &DbConn) -> Vec<Self> {
|
pub async fn find_by_user_and_policy(user_uuid: &UserId, policy_type: OrgPolicyType, conn: &DbConn) -> Vec<Self> {
|
||||||
conn.run(move |conn| {
|
conn.run(move |conn| {
|
||||||
users_organizations::table
|
users_organizations::table
|
||||||
@@ -1126,6 +1089,7 @@ impl Membership {
|
|||||||
.and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()))),
|
.and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()))),
|
||||||
)
|
)
|
||||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||||
|
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||||
.filter(
|
.filter(
|
||||||
users_organizations::atype.eq_any(vec![MembershipType::Owner as i32, MembershipType::Admin as i32]),
|
users_organizations::atype.eq_any(vec![MembershipType::Owner as i32, MembershipType::Admin as i32]),
|
||||||
)
|
)
|
||||||
|
|||||||
+9
-9
@@ -466,7 +466,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn dotted_decimal_loopback_normalizes() {
|
fn dotted_decimal_loopback_normalizes() {
|
||||||
let ip = parse_to_ip("127.0.0.1").unwrap();
|
let ip = parse_to_ip("127.0.0.1").unwrap();
|
||||||
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
|
||||||
assert!(!is_global_hardcoded(ip));
|
assert!(!is_global_hardcoded(ip));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,21 +474,21 @@ mod tests {
|
|||||||
fn single_decimal_loopback_normalizes() {
|
fn single_decimal_loopback_normalizes() {
|
||||||
// 127.0.0.1 == 2130706433
|
// 127.0.0.1 == 2130706433
|
||||||
let ip = parse_to_ip("2130706433").unwrap();
|
let ip = parse_to_ip("2130706433").unwrap();
|
||||||
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
|
||||||
assert!(!is_global_hardcoded(ip));
|
assert!(!is_global_hardcoded(ip));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hex_loopback_normalizes() {
|
fn hex_loopback_normalizes() {
|
||||||
let ip = parse_to_ip("0x7f000001").unwrap();
|
let ip = parse_to_ip("0x7f000001").unwrap();
|
||||||
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
|
||||||
assert!(!is_global_hardcoded(ip));
|
assert!(!is_global_hardcoded(ip));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dotted_hex_loopback_normalizes() {
|
fn dotted_hex_loopback_normalizes() {
|
||||||
let ip = parse_to_ip("0x7f.0.0.1").unwrap();
|
let ip = parse_to_ip("0x7f.0.0.1").unwrap();
|
||||||
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
|
||||||
assert!(!is_global_hardcoded(ip));
|
assert!(!is_global_hardcoded(ip));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,14 +496,14 @@ mod tests {
|
|||||||
fn octal_loopback_normalizes() {
|
fn octal_loopback_normalizes() {
|
||||||
// 017700000001 == 127.0.0.1
|
// 017700000001 == 127.0.0.1
|
||||||
let ip = parse_to_ip("017700000001").unwrap();
|
let ip = parse_to_ip("017700000001").unwrap();
|
||||||
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
|
||||||
assert!(!is_global_hardcoded(ip));
|
assert!(!is_global_hardcoded(ip));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dotted_octal_loopback_normalizes() {
|
fn dotted_octal_loopback_normalizes() {
|
||||||
let ip = parse_to_ip("0177.0.0.01").unwrap();
|
let ip = parse_to_ip("0177.0.0.01").unwrap();
|
||||||
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
|
||||||
assert!(!is_global_hardcoded(ip));
|
assert!(!is_global_hardcoded(ip));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,19 +535,19 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn get_valid_host_normalizes_decimal_int() {
|
fn get_valid_host_normalizes_decimal_int() {
|
||||||
let h = get_valid_host("2130706433").expect("valid");
|
let h = get_valid_host("2130706433").expect("valid");
|
||||||
assert!(matches!(h, Host::Ipv4(ip) if ip == Ipv4Addr::new(127, 0, 0, 1)));
|
assert!(matches!(h, Host::Ipv4(ip) if ip == Ipv4Addr::LOCALHOST));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_valid_host_normalizes_hex() {
|
fn get_valid_host_normalizes_hex() {
|
||||||
let h = get_valid_host("0x7f000001").expect("valid");
|
let h = get_valid_host("0x7f000001").expect("valid");
|
||||||
assert!(matches!(h, Host::Ipv4(ip) if ip == Ipv4Addr::new(127, 0, 0, 1)));
|
assert!(matches!(h, Host::Ipv4(ip) if ip == Ipv4Addr::LOCALHOST));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_valid_host_normalizes_octal() {
|
fn get_valid_host_normalizes_octal() {
|
||||||
let h = get_valid_host("017700000001").expect("valid");
|
let h = get_valid_host("017700000001").expect("valid");
|
||||||
assert!(matches!(h, Host::Ipv4(ip) if ip == Ipv4Addr::new(127, 0, 0, 1)));
|
assert!(matches!(h, Host::Ipv4(ip) if ip == Ipv4Addr::LOCALHOST));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===
|
// ===
|
||||||
|
|||||||
@@ -220,6 +220,13 @@ auth-input-password > form > bit-form-field:nth-child(4) {
|
|||||||
@extend %vw-hide;
|
@extend %vw-hide;
|
||||||
}
|
}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
|
{{#unless email_change_allowed}}
|
||||||
|
/* Hide the whole `Change email` section, including its heading, if not allowed */
|
||||||
|
div:has(> app-change-email) {
|
||||||
|
@extend %vw-hide;
|
||||||
|
}
|
||||||
|
{{/unless}}
|
||||||
/**** End Dynamic Vaultwarden Changes ****/
|
/**** End Dynamic Vaultwarden Changes ****/
|
||||||
/**** Include a special user stylesheet for custom changes ****/
|
/**** Include a special user stylesheet for custom changes ****/
|
||||||
{{#if load_user_scss}}
|
{{#if load_user_scss}}
|
||||||
|
|||||||
+7
-7
@@ -943,15 +943,15 @@ mod tests {
|
|||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
#[ignore = "exhaustive IPv4 check is too slow for the regular test suite"]
|
||||||
fn test_ipv4_global() {
|
fn test_ipv4_global() {
|
||||||
for a in 0..u8::MAX {
|
for a in 0..u8::MAX {
|
||||||
println!("Iter: {}/255", a);
|
println!("Iter: {a}/255");
|
||||||
for b in 0..u8::MAX {
|
for b in 0..u8::MAX {
|
||||||
for c in 0..u8::MAX {
|
for c in 0..u8::MAX {
|
||||||
for d in 0..u8::MAX {
|
for d in 0..u8::MAX {
|
||||||
let ip = IpAddr::V4(std::net::Ipv4Addr::new(a, b, c, d));
|
let ip = IpAddr::V4(std::net::Ipv4Addr::new(a, b, c, d));
|
||||||
assert_eq!(ip.is_global(), is_global_hardcoded(ip), "IP mismatch: {}", ip)
|
assert_eq!(ip.is_global(), is_global_hardcoded(ip), "IP mismatch: {ip}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -959,15 +959,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
#[ignore = "random IPv6 check is too slow for the regular test suite"]
|
||||||
fn test_ipv6_global() {
|
fn test_ipv6_global() {
|
||||||
use rand::Rng;
|
use rand::RngExt;
|
||||||
|
|
||||||
std::thread::scope(|s| {
|
std::thread::scope(|s| {
|
||||||
for t in 0..16 {
|
for t in 0..16 {
|
||||||
let handle = s.spawn(move || {
|
let _handle = s.spawn(move || {
|
||||||
let mut v = [0u8; 16];
|
let mut v = [0u8; 16];
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::rng();
|
||||||
|
|
||||||
for i in 0..20 {
|
for i in 0..20 {
|
||||||
println!("Thread {t} Iter: {i}/50");
|
println!("Thread {t} Iter: {i}/50");
|
||||||
|
|||||||
Reference in new issue
Block a user