@@ -9,7 +9,7 @@ use axum::{
99use chrono:: NaiveDateTime ;
1010use defguard_common:: db:: Id ;
1111use serde_json:: { Value , json} ;
12- use sqlx:: query_as;
12+ use sqlx:: { PgConnection , query_as} ;
1313use utoipa:: ToSchema ;
1414
1515use super :: LicenseInfo ;
@@ -137,17 +137,52 @@ pub struct EditAclRule {
137137}
138138
139139impl EditAclRule {
140- pub fn validate ( & self ) -> Result < ( ) , WebError > {
141- let manual_configured = self . any_address
142- || self . any_port
143- || self . any_protocol
144- || !self . addresses . trim ( ) . is_empty ( )
145- || !self . ports . trim ( ) . is_empty ( )
146- || !self . protocols . is_empty ( ) ;
140+ pub async fn validate ( & self , conn : & mut PgConnection ) -> Result < ( ) , WebError > {
147141 if self . use_manual_destination_settings {
148- if !manual_configured {
142+ // Determine what the selected component aliases collectively contribute.
143+ // Note: Component-kind aliases always have any_address/any_port/any_protocol = false,
144+ // so we only check whether they have non-empty arrays.
145+ let ( alias_has_address, alias_has_port, alias_has_protocol) = if self . aliases . is_empty ( )
146+ {
147+ ( false , false , false )
148+ } else {
149+ let row = query_as :: < _ , ( Option < bool > , Option < bool > , Option < bool > ) > (
150+ "SELECT \
151+ bool_or(array_length(addresses, 1) > 0) \
152+ OR EXISTS ( \
153+ SELECT 1 FROM aclaliasdestinationrange dr \
154+ JOIN aclalias a ON a.id = dr.alias_id \
155+ WHERE a.id = ANY($1) \
156+ AND a.kind = 'component'::aclalias_kind \
157+ ), \
158+ bool_or(array_length(ports, 1) > 0), \
159+ bool_or(array_length(protocols, 1) > 0) \
160+ FROM aclalias WHERE id = ANY($1) AND kind = 'component'::aclalias_kind",
161+ )
162+ . bind ( & self . aliases )
163+ . fetch_one ( & mut * conn)
164+ . await
165+ . map_err ( WebError :: from) ?;
166+ (
167+ row. 0 . unwrap_or ( false ) ,
168+ row. 1 . unwrap_or ( false ) ,
169+ row. 2 . unwrap_or ( false ) ,
170+ )
171+ } ;
172+
173+ if !self . any_address && self . addresses . trim ( ) . is_empty ( ) && !alias_has_address {
149174 return Err ( WebError :: BadRequest (
150- "Must provide manual destination settings" . to_string ( ) ,
175+ "Must provide destination address, or enable any address" . to_string ( ) ,
176+ ) ) ;
177+ }
178+ if !self . any_port && self . ports . trim ( ) . is_empty ( ) && !alias_has_port {
179+ return Err ( WebError :: BadRequest (
180+ "Must provide destination port, or enable any port" . to_string ( ) ,
181+ ) ) ;
182+ }
183+ if !self . any_protocol && self . protocols . is_empty ( ) && !alias_has_protocol {
184+ return Err ( WebError :: BadRequest (
185+ "Must provide destination protocol, or enable any protocol" . to_string ( ) ,
151186 ) ) ;
152187 }
153188 } else if self . destinations . is_empty ( ) {
@@ -347,15 +382,15 @@ pub(crate) async fn create_acl_rule(
347382) -> ApiResult {
348383 debug ! ( "User {} creating ACL rule {data:?}" , session. user. username) ;
349384
350- // validate submitted ACL rule
351- data. validate ( ) ?;
352-
353- let rule = AclRule :: create_from_api ( & appstate. pool , & data, & session. user . username )
385+ let mut tx = appstate. pool . begin ( ) . await ?;
386+ data. validate ( & mut tx) . await ?;
387+ let rule = AclRule :: create_from_api ( & mut tx, & data, & session. user . username )
354388 . await
355389 . map_err ( |err| {
356390 error ! ( "Error creating ACL rule {data:?}: {err}" ) ;
357391 err
358392 } ) ?;
393+ tx. commit ( ) . await ?;
359394 info ! (
360395 "User {} created ACL rule {}" ,
361396 session. user. username, rule. id
@@ -386,15 +421,15 @@ pub(crate) async fn update_acl_rule(
386421) -> ApiResult {
387422 debug ! ( "User {} updating ACL rule {data:?}" , session. user. username) ;
388423
389- // validate submitted ACL rule
390- data. validate ( ) ?;
391-
392- let rule = AclRule :: update_from_api ( & appstate. pool , id, & data, & session. user . username )
424+ let mut tx = appstate. pool . begin ( ) . await ?;
425+ data. validate ( & mut tx) . await ?;
426+ let rule = AclRule :: update_from_api ( & mut tx, id, & data, & session. user . username )
393427 . await
394428 . map_err ( |err| {
395429 error ! ( "Error updating ACL rule {data:?}: {err}" ) ;
396430 err
397431 } ) ?;
432+ tx. commit ( ) . await ?;
398433 info ! ( "User {} updated ACL rule" , session. user. username) ;
399434 Ok ( ApiResponse :: json ( rule, StatusCode :: OK ) )
400435}
0 commit comments