diff --git a/.sqlx/query-25fd6f4b68013a7f4d532840adfa6ef3d1292dee92c02f41912ad87280658622.json b/.sqlx/query-25fd6f4b68013a7f4d532840adfa6ef3d1292dee92c02f41912ad87280658622.json new file mode 100644 index 0000000000..adbaa53331 --- /dev/null +++ b/.sqlx/query-25fd6f4b68013a7f4d532840adfa6ef3d1292dee92c02f41912ad87280658622.json @@ -0,0 +1,112 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, parent_id, name, kind \"kind: _\", state \"state: _\", destination, ports, protocols, any_destination, any_port, any_protocol FROM aclalias WHERE kind = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "parent_id", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "kind: _", + "type_info": { + "Custom": { + "name": "aclalias_kind", + "kind": { + "Enum": [ + "destination", + "component" + ] + } + } + } + }, + { + "ordinal": 4, + "name": "state: _", + "type_info": { + "Custom": { + "name": "aclalias_state", + "kind": { + "Enum": [ + "applied", + "modified" + ] + } + } + } + }, + { + "ordinal": 5, + "name": "destination", + "type_info": "InetArray" + }, + { + "ordinal": 6, + "name": "ports", + "type_info": "Int4RangeArray" + }, + { + "ordinal": 7, + "name": "protocols", + "type_info": "Int4Array" + }, + { + "ordinal": 8, + "name": "any_destination", + "type_info": "Bool" + }, + { + "ordinal": 9, + "name": "any_port", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "any_protocol", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + { + "Custom": { + "name": "aclalias_kind", + "kind": { + "Enum": [ + "destination", + "component" + ] + } + } + } + ] + }, + "nullable": [ + false, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "25fd6f4b68013a7f4d532840adfa6ef3d1292dee92c02f41912ad87280658622" +} diff --git a/.sqlx/query-d10c9a7b0b391aeb8b4869f6bddf997807b66e0b532da747b146513c34e15c5c.json b/.sqlx/query-d10c9a7b0b391aeb8b4869f6bddf997807b66e0b532da747b146513c34e15c5c.json index b843b3c06c..a2e62691a2 100644 --- a/.sqlx/query-d10c9a7b0b391aeb8b4869f6bddf997807b66e0b532da747b146513c34e15c5c.json +++ b/.sqlx/query-d10c9a7b0b391aeb8b4869f6bddf997807b66e0b532da747b146513c34e15c5c.json @@ -45,12 +45,12 @@ }, { "ordinal": 8, - "name": "name", + "name": "version", "type_info": "Text" }, { "ordinal": 9, - "name": "version", + "name": "name", "type_info": "Text" } ], @@ -68,8 +68,8 @@ true, false, true, - false, - true + true, + false ] }, "hash": "d10c9a7b0b391aeb8b4869f6bddf997807b66e0b532da747b146513c34e15c5c" diff --git a/.sqlx/query-e9ca71b61f7a3736ca335d90aca36ab5a93dc8a00ad622267f13b3cd4cdb4a5a.json b/.sqlx/query-e9ca71b61f7a3736ca335d90aca36ab5a93dc8a00ad622267f13b3cd4cdb4a5a.json index d3ffd878ef..db1d8414a5 100644 --- a/.sqlx/query-e9ca71b61f7a3736ca335d90aca36ab5a93dc8a00ad622267f13b3cd4cdb4a5a.json +++ b/.sqlx/query-e9ca71b61f7a3736ca335d90aca36ab5a93dc8a00ad622267f13b3cd4cdb4a5a.json @@ -45,12 +45,12 @@ }, { "ordinal": 8, - "name": "name", + "name": "version", "type_info": "Text" }, { "ordinal": 9, - "name": "version", + "name": "name", "type_info": "Text" } ], @@ -68,8 +68,8 @@ true, false, true, - false, - true + true, + false ] }, "hash": "e9ca71b61f7a3736ca335d90aca36ab5a93dc8a00ad622267f13b3cd4cdb4a5a" diff --git a/.sqlx/query-f49f70e1e6c0e250f95e790016142b39dd99f34a47caa0cebe57cee504e90cde.json b/.sqlx/query-f49f70e1e6c0e250f95e790016142b39dd99f34a47caa0cebe57cee504e90cde.json new file mode 100644 index 0000000000..93792833c5 --- /dev/null +++ b/.sqlx/query-f49f70e1e6c0e250f95e790016142b39dd99f34a47caa0cebe57cee504e90cde.json @@ -0,0 +1,113 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, parent_id, name, kind \"kind: _\", state \"state: _\", destination, ports, protocols, any_destination, any_port, any_protocol FROM aclalias WHERE id = $1 AND kind = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "parent_id", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "kind: _", + "type_info": { + "Custom": { + "name": "aclalias_kind", + "kind": { + "Enum": [ + "destination", + "component" + ] + } + } + } + }, + { + "ordinal": 4, + "name": "state: _", + "type_info": { + "Custom": { + "name": "aclalias_state", + "kind": { + "Enum": [ + "applied", + "modified" + ] + } + } + } + }, + { + "ordinal": 5, + "name": "destination", + "type_info": "InetArray" + }, + { + "ordinal": 6, + "name": "ports", + "type_info": "Int4RangeArray" + }, + { + "ordinal": 7, + "name": "protocols", + "type_info": "Int4Array" + }, + { + "ordinal": 8, + "name": "any_destination", + "type_info": "Bool" + }, + { + "ordinal": 9, + "name": "any_port", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "any_protocol", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Int8", + { + "Custom": { + "name": "aclalias_kind", + "kind": { + "Enum": [ + "destination", + "component" + ] + } + } + } + ] + }, + "nullable": [ + false, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "f49f70e1e6c0e250f95e790016142b39dd99f34a47caa0cebe57cee504e90cde" +} diff --git a/Cargo.lock b/Cargo.lock index 6c7a531cc5..c1444f291d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -596,9 +596,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.54" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "jobserver", @@ -1820,9 +1820,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -4638,9 +4638,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", "ref-cast", @@ -4866,7 +4866,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.2.0", + "schemars 1.2.1", "serde_core", "serde_json", "serde_with_macros", @@ -5036,9 +5036,9 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slug" @@ -6957,18 +6957,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.36" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.36" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" dependencies = [ "proc-macro2", "quote", @@ -7071,9 +7071,9 @@ checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" [[package]] name = "zmij" -version = "1.0.17" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" [[package]] name = "zopfli" diff --git a/crates/defguard_core/src/enterprise/db/models/acl.rs b/crates/defguard_core/src/enterprise/db/models/acl.rs index 3a9db0bbdb..d9b635c5aa 100644 --- a/crates/defguard_core/src/enterprise/db/models/acl.rs +++ b/crates/defguard_core/src/enterprise/db/models/acl.rs @@ -1591,6 +1591,47 @@ impl TryFrom<&EditAclAlias> for AclAlias { } } +impl AclAlias { + /// Fetch [`AclAlias`] of a given kind. + pub(crate) async fn all_of_kind<'e, E>( + executor: E, + kind: AliasKind, + ) -> Result, sqlx::Error> + where + E: PgExecutor<'e>, + { + sqlx::query_as!( + Self, + "SELECT id, parent_id, name, kind \"kind: _\", state \"state: _\", \ + destination, ports, protocols, any_destination, any_port, any_protocol \ + FROM aclalias WHERE kind = $1", + kind as AliasKind + ) + .fetch_all(executor) + .await + } + + pub async fn find_by_id_and_kind<'e, E>( + executor: E, + id: Id, + kind: AliasKind, + ) -> Result, sqlx::Error> + where + E: sqlx::PgExecutor<'e>, + { + sqlx::query_as!( + Self, + "SELECT id, parent_id, name, kind \"kind: _\", state \"state: _\", \ + destination, ports, protocols, any_destination, any_port, any_protocol \ + FROM aclalias WHERE id = $1 AND kind = $2", + id, + kind as AliasKind + ) + .fetch_optional(executor) + .await + } +} + impl TryFrom<&EditAclDestination> for AclAlias { type Error = AclError; diff --git a/crates/defguard_core/src/enterprise/handlers/acl/alias.rs b/crates/defguard_core/src/enterprise/handlers/acl/alias.rs index 7e42138b6e..d0cbb64f5f 100644 --- a/crates/defguard_core/src/enterprise/handlers/acl/alias.rs +++ b/crates/defguard_core/src/enterprise/handlers/acl/alias.rs @@ -99,12 +99,13 @@ impl ApiAclAlias { let mut transaction = pool.begin().await?; // find existing alias - let existing_alias = AclAlias::find_by_id(&mut *transaction, id) - .await? - .ok_or_else(|| { - warn!("Update of nonexistent alias ({id}) failed"); - AclError::AliasNotFoundError(id) - })?; + let existing_alias = + AclAlias::find_by_id_and_kind(&mut *transaction, id, AliasKind::Component) + .await? + .ok_or_else(|| { + warn!("Update of nonexistent alias ({id}) failed"); + AclError::AliasNotFoundError(id) + })?; // Convert alias from API to model. let mut alias = AclAlias::try_from(api_alias)?; @@ -192,7 +193,7 @@ pub(crate) async fn list_acl_aliases( session: SessionInfo, ) -> ApiResult { debug!("User {} listing ACL aliases", session.user.username); - let aliases = AclAlias::all(&appstate.pool).await?; + let aliases = AclAlias::all_of_kind(&appstate.pool, AliasKind::Component).await?; let mut api_aliases = Vec::::with_capacity(aliases.len()); for alias in &aliases { // TODO: may require optimisation wrt. sql queries @@ -226,18 +227,19 @@ pub(crate) async fn get_acl_alias( Path(id): Path, ) -> ApiResult { debug!("User {} retrieving ACL alias {id}", session.user.username); - let (alias, status) = match AclAlias::find_by_id(&appstate.pool, id).await? { - Some(alias) => ( - json!(ApiAclAlias::from( - alias.to_info(&appstate.pool).await.map_err(|err| { - error!("Error retrieving ACL alias {alias:?}: {err}"); - err - })? - )), - StatusCode::OK, - ), - None => (Value::Null, StatusCode::NOT_FOUND), - }; + let (alias, status) = + match AclAlias::find_by_id_and_kind(&appstate.pool, id, AliasKind::Component).await? { + Some(alias) => ( + json!(ApiAclAlias::from( + alias.to_info(&appstate.pool).await.map_err(|err| { + error!("Error retrieving ACL alias {alias:?}: {err}"); + err + })? + )), + StatusCode::OK, + ), + None => (Value::Null, StatusCode::NOT_FOUND), + }; info!("User {} retrieved ACL alias {id}", session.user.username); Ok(ApiResponse::new(alias, status)) diff --git a/crates/defguard_core/src/enterprise/handlers/acl/destination.rs b/crates/defguard_core/src/enterprise/handlers/acl/destination.rs index 5b4d2ad793..6986346cd5 100644 --- a/crates/defguard_core/src/enterprise/handlers/acl/destination.rs +++ b/crates/defguard_core/src/enterprise/handlers/acl/destination.rs @@ -105,12 +105,13 @@ impl ApiAclDestination { let mut transaction = pool.begin().await?; // find existing alias - let existing_alias = AclAlias::find_by_id(&mut *transaction, id) - .await? - .ok_or_else(|| { - warn!("Update of nonexistent alias ({id}) failed"); - AclError::AliasNotFoundError(id) - })?; + let existing_alias = + AclAlias::find_by_id_and_kind(&mut *transaction, id, AliasKind::Destination) + .await? + .ok_or_else(|| { + warn!("Update of nonexistent alias ({id}) failed"); + AclError::AliasNotFoundError(id) + })?; // Convert alias from API to model. let mut alias = AclAlias::try_from(api_alias)?; @@ -201,7 +202,7 @@ pub(crate) async fn list_acl_destinations( session: SessionInfo, ) -> ApiResult { debug!("User {} listing ACL destinations", session.user.username); - let aliases = AclAlias::all(&appstate.pool).await?; + let aliases = AclAlias::all_of_kind(&appstate.pool, AliasKind::Destination).await?; let mut api_aliases = Vec::::with_capacity(aliases.len()); for alias in &aliases { // TODO: may require optimisation wrt. sql queries @@ -238,18 +239,19 @@ pub(crate) async fn get_acl_destination( "User {} retrieving ACL destination {id}", session.user.username ); - let (alias, status) = match AclAlias::find_by_id(&appstate.pool, id).await? { - Some(alias) => ( - json!(ApiAclDestination::from( - alias.to_info(&appstate.pool).await.map_err(|err| { - error!("Error retrieving ACL destination {alias:?}: {err}"); - err - })? - )), - StatusCode::OK, - ), - None => (Value::Null, StatusCode::NOT_FOUND), - }; + let (alias, status) = + match AclAlias::find_by_id_and_kind(&appstate.pool, id, AliasKind::Destination).await? { + Some(alias) => ( + json!(ApiAclDestination::from( + alias.to_info(&appstate.pool).await.map_err(|err| { + error!("Error retrieving ACL destination {alias:?}: {err}"); + err + })? + )), + StatusCode::OK, + ), + None => (Value::Null, StatusCode::NOT_FOUND), + }; info!( "User {} retrieved ACL destination {id}", diff --git a/crates/defguard_core/tests/integration/api/acl.rs b/crates/defguard_core/tests/integration/api/acl.rs index 36fc67de09..d25a34332f 100644 --- a/crates/defguard_core/tests/integration/api/acl.rs +++ b/crates/defguard_core/tests/integration/api/acl.rs @@ -31,7 +31,7 @@ use tokio::net::TcpListener; use super::common::{ authenticate_admin, client::TestClient, exceed_enterprise_limits, make_base_client, - make_test_client, omit_id, setup_pool, + make_test_client, setup_pool, }; use crate::common::{init_config, initialize_users}; @@ -493,7 +493,7 @@ async fn test_related_objects(_: PgPoolOptions, options: PgConnectOptions) { AclAlias::new( "alias1", AliasState::Applied, - AliasKind::Destination, + AliasKind::Component, Vec::new(), Vec::new(), Vec::new(), @@ -507,7 +507,7 @@ async fn test_related_objects(_: PgPoolOptions, options: PgConnectOptions) { AclAlias::new( "alias2", AliasState::Applied, - AliasKind::Destination, + AliasKind::Component, Vec::new(), Vec::new(), Vec::new(), @@ -530,20 +530,20 @@ async fn test_related_objects(_: PgPoolOptions, options: PgConnectOptions) { // create let response = client.post("/api/v1/acl/rule").json(&rule).send().await; assert_eq!(response.status(), StatusCode::CREATED); - let response_rule: EditAclRule = response.json().await; + let response_rule = response.json::().await; assert_eq!(response_rule, rule); // retrieve let response = client.get("/api/v1/acl/rule/1").send().await; assert_eq!(response.status(), StatusCode::OK); - let response_rule: EditAclRule = omit_id(response.json().await); + let response_rule = response.json::().await; assert_eq!(response_rule, rule); // related rules in alias details let response = client.get("/api/v1/acl/alias/1").send().await; assert_eq!(response.status(), StatusCode::OK); - let response_alias: ApiAclAlias = response.json().await; - assert_eq!(response_alias.rules, vec![1]); + let response_alias = response.json::().await; + assert_eq!(response_alias.rules, [1]); // add another rule let mut rule = make_rule(); @@ -553,12 +553,12 @@ async fn test_related_objects(_: PgPoolOptions, options: PgConnectOptions) { let response = client.get("/api/v1/acl/alias/1").send().await; assert_eq!(response.status(), StatusCode::OK); - let response_alias: ApiAclAlias = response.json().await; - assert_eq!(response_alias.rules, vec![1, 2]); + let response_alias = response.json::().await; + assert_eq!(response_alias.rules, [1, 2]); let response = client.get("/api/v1/acl/alias/2").send().await; assert_eq!(response.status(), StatusCode::OK); - let response_alias: ApiAclAlias = response.json().await; - assert_eq!(response_alias.rules, vec![1]); + let response_alias = response.json::().await; + assert_eq!(response_alias.rules, [1]); } #[sqlx::test] diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index 7447bf2d36..333baf466b 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -10,7 +10,7 @@ use defguard_common::{ VERSION, config::DefGuardConfig, db::{ - Id, NoId, + Id, models::{Device, User, WireguardNetwork, settings::initialize_current_settings}, }, }; @@ -26,8 +26,7 @@ use defguard_core::{ use defguard_mail::Mail; use reqwest::{StatusCode, header::HeaderName}; use semver::Version; -use serde::de::DeserializeOwned; -use serde_json::{Value, json}; +use serde_json::json; use sqlx::PgPool; use tokio::{ net::TcpListener, @@ -206,12 +205,6 @@ pub(crate) async fn make_network(client: &TestClient, name: &str) -> TestRespons response } -/// Replaces id field in json response with NoId -pub(crate) fn omit_id(mut value: Value) -> T { - *value.get_mut("id").unwrap() = json!(NoId); - serde_json::from_value(value).unwrap() -} - pub(crate) async fn make_client(pool: PgPool) -> TestClient { let (client, _) = make_test_client(pool).await; client