1use aws_config::BehaviorVersion;
30use aws_sdk_verifiedpermissions::{
31 types::{ActionIdentifier, AttributeValue, EntitiesDefinition, EntityIdentifier, EntityItem},
32 Client,
33};
34use serde::{Deserialize, Serialize};
35use std::collections::{HashMap, HashSet};
36
37use crate::policy::{
38 AuthorizationDecision, OperationEntity, PolicyEvaluationError, PolicyEvaluator,
39 ServerConfigEntity,
40};
41
42#[cfg(feature = "openapi-code-mode")]
43use crate::policy::{normalize_operation_format, OpenAPIServerEntity, ScriptEntity};
44
45#[cfg(feature = "sql-code-mode")]
46use crate::policy::{SqlServerEntity, StatementEntity};
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct AvpConfig {
51 pub policy_store_id: String,
53
54 #[serde(default)]
56 pub region: Option<String>,
57}
58
59impl Default for AvpConfig {
60 fn default() -> Self {
61 Self {
62 policy_store_id: String::new(),
63 region: None,
64 }
65 }
66}
67
68#[derive(Debug, thiserror::Error)]
70pub enum AvpError {
71 #[error("AVP configuration error: {0}")]
72 ConfigError(String),
73
74 #[error("AVP SDK error: {0}")]
75 SdkError(String),
76
77 #[error("Authorization denied: {0}")]
78 Denied(String),
79}
80
81#[derive(Clone)]
87pub struct AvpClient {
88 client: Client,
89 policy_store_id: String,
90}
91
92impl AvpClient {
93 pub async fn new(config: AvpConfig) -> Result<Self, AvpError> {
99 if config.policy_store_id.is_empty() {
100 return Err(AvpError::ConfigError(
101 "Policy store ID is required".to_string(),
102 ));
103 }
104
105 let aws_config = if let Some(region) = &config.region {
106 aws_config::defaults(BehaviorVersion::latest())
107 .region(aws_config::Region::new(region.clone()))
108 .load()
109 .await
110 } else {
111 aws_config::load_defaults(BehaviorVersion::latest()).await
112 };
113
114 let client = Client::new(&aws_config);
115
116 Ok(Self {
117 client,
118 policy_store_id: config.policy_store_id,
119 })
120 }
121
122 pub async fn is_authorized(
124 &self,
125 operation: &OperationEntity,
126 server_config: &ServerConfigEntity,
127 ) -> Result<AuthorizationDecision, AvpError> {
128 let entities = self.build_entities(operation, server_config);
129
130 let action_id = if operation.has_introspection {
131 "Admin"
132 } else {
133 match operation.operation_type.as_str() {
134 "mutation" => {
135 let op_name = operation.operation_name.to_lowercase();
136 if op_name.starts_with("delete")
137 || op_name.starts_with("remove")
138 || op_name.starts_with("purge")
139 {
140 "Delete"
141 } else {
142 "Write"
143 }
144 },
145 "subscription" => "Write",
146 _ => "Read",
147 }
148 };
149
150 let response = self
151 .client
152 .is_authorized()
153 .policy_store_id(&self.policy_store_id)
154 .principal(
155 EntityIdentifier::builder()
156 .entity_type("CodeMode::Operation")
157 .entity_id(&operation.id)
158 .build()
159 .map_err(|e| AvpError::SdkError(e.to_string()))?,
160 )
161 .action(
162 ActionIdentifier::builder()
163 .action_type("CodeMode::Action")
164 .action_id(action_id)
165 .build()
166 .map_err(|e| AvpError::SdkError(e.to_string()))?,
167 )
168 .resource(
169 EntityIdentifier::builder()
170 .entity_type("CodeMode::Server")
171 .entity_id(&server_config.server_id)
172 .build()
173 .map_err(|e| AvpError::SdkError(e.to_string()))?,
174 )
175 .entities(entities)
176 .send()
177 .await
178 .map_err(|e| {
179 tracing::error!(error = ?e, "AVP is_authorized failed");
180 AvpError::SdkError(e.to_string())
181 })?;
182
183 Ok(self.parse_response(&response))
184 }
185
186 pub async fn is_authorized_raw(
191 &self,
192 principal_type: &str,
193 principal_id: &str,
194 action_type: &str,
195 action_id: &str,
196 resource_type: &str,
197 resource_id: &str,
198 entities: Vec<EntityItem>,
199 ) -> Result<AuthorizationDecision, AvpError> {
200 let response = self
201 .client
202 .is_authorized()
203 .policy_store_id(&self.policy_store_id)
204 .principal(
205 EntityIdentifier::builder()
206 .entity_type(principal_type)
207 .entity_id(principal_id)
208 .build()
209 .map_err(|e| AvpError::SdkError(e.to_string()))?,
210 )
211 .action(
212 ActionIdentifier::builder()
213 .action_type(action_type)
214 .action_id(action_id)
215 .build()
216 .map_err(|e| AvpError::SdkError(e.to_string()))?,
217 )
218 .resource(
219 EntityIdentifier::builder()
220 .entity_type(resource_type)
221 .entity_id(resource_id)
222 .build()
223 .map_err(|e| AvpError::SdkError(e.to_string()))?,
224 )
225 .entities(EntitiesDefinition::EntityList(entities))
226 .send()
227 .await
228 .map_err(|e| {
229 tracing::error!(error = ?e, "AVP is_authorized failed");
230 AvpError::SdkError(e.to_string())
231 })?;
232
233 Ok(self.parse_response(&response))
234 }
235
236 pub async fn batch_is_authorized(
238 &self,
239 requests: Vec<(OperationEntity, ServerConfigEntity)>,
240 ) -> Result<Vec<AuthorizationDecision>, AvpError> {
241 let mut results = Vec::new();
242
243 for chunk in requests.chunks(30) {
244 let batch_items: Vec<_> = chunk
245 .iter()
246 .map(|(op, config)| {
247 let action_id = Self::determine_action_id(op);
248
249 aws_sdk_verifiedpermissions::types::BatchIsAuthorizedInputItem::builder()
250 .principal(
251 EntityIdentifier::builder()
252 .entity_type("CodeMode::Operation")
253 .entity_id(&op.id)
254 .build()
255 .expect("valid entity identifier"),
256 )
257 .action(
258 ActionIdentifier::builder()
259 .action_type("CodeMode::Action")
260 .action_id(action_id)
261 .build()
262 .expect("valid action identifier"),
263 )
264 .resource(
265 EntityIdentifier::builder()
266 .entity_type("CodeMode::Server")
267 .entity_id(&config.server_id)
268 .build()
269 .expect("valid entity identifier"),
270 )
271 .build()
272 })
273 .collect();
274
275 let mut all_entities = Vec::new();
276 for (op, config) in chunk {
277 all_entities.push(self.build_operation_entity(op));
278 all_entities.push(self.build_server_config_entity(config));
279 }
280
281 let response = self
282 .client
283 .batch_is_authorized()
284 .policy_store_id(&self.policy_store_id)
285 .set_requests(Some(batch_items))
286 .entities(EntitiesDefinition::EntityList(all_entities))
287 .send()
288 .await
289 .map_err(|e| {
290 tracing::error!(error = ?e, "AVP is_authorized failed");
291 AvpError::SdkError(e.to_string())
292 })?;
293
294 for result in response.results() {
295 let allowed =
296 result.decision() == &aws_sdk_verifiedpermissions::types::Decision::Allow;
297 results.push(AuthorizationDecision {
298 allowed,
299 determining_policies: result
300 .determining_policies()
301 .iter()
302 .map(|p| p.policy_id().to_string())
303 .collect(),
304 errors: result
305 .errors()
306 .iter()
307 .map(|e| e.error_description().to_string())
308 .collect(),
309 });
310 }
311 }
312
313 Ok(results)
314 }
315
316 fn determine_action_id(op: &OperationEntity) -> &'static str {
317 if op.has_introspection {
318 "Admin"
319 } else {
320 match op.operation_type.as_str() {
321 "mutation" => {
322 let op_name = op.operation_name.to_lowercase();
323 if op_name.starts_with("delete")
324 || op_name.starts_with("remove")
325 || op_name.starts_with("purge")
326 {
327 "Delete"
328 } else {
329 "Write"
330 }
331 },
332 "subscription" => "Write",
333 _ => "Read",
334 }
335 }
336 }
337
338 fn parse_response(
339 &self,
340 response: &aws_sdk_verifiedpermissions::operation::is_authorized::IsAuthorizedOutput,
341 ) -> AuthorizationDecision {
342 let allowed = response.decision() == &aws_sdk_verifiedpermissions::types::Decision::Allow;
343 AuthorizationDecision {
344 allowed,
345 determining_policies: response
346 .determining_policies()
347 .iter()
348 .map(|p| p.policy_id().to_string())
349 .collect(),
350 errors: response
351 .errors()
352 .iter()
353 .map(|e| e.error_description().to_string())
354 .collect(),
355 }
356 }
357
358 fn build_entities(
359 &self,
360 operation: &OperationEntity,
361 server_config: &ServerConfigEntity,
362 ) -> EntitiesDefinition {
363 EntitiesDefinition::EntityList(vec![
364 self.build_operation_entity(operation),
365 self.build_server_config_entity(server_config),
366 ])
367 }
368
369 fn build_operation_entity(&self, operation: &OperationEntity) -> EntityItem {
370 let mut attrs: HashMap<String, AttributeValue> = HashMap::new();
371 attrs.insert(
372 "operationType".into(),
373 AttributeValue::String(operation.operation_type.clone()),
374 );
375 attrs.insert(
376 "operationName".into(),
377 AttributeValue::String(operation.operation_name.clone()),
378 );
379 attrs.insert("depth".into(), AttributeValue::Long(operation.depth as i64));
380 attrs.insert(
381 "fieldCount".into(),
382 AttributeValue::Long(operation.field_count as i64),
383 );
384 attrs.insert(
385 "estimatedCost".into(),
386 AttributeValue::Long(operation.estimated_cost as i64),
387 );
388 attrs.insert(
389 "hasIntrospection".into(),
390 AttributeValue::Boolean(operation.has_introspection),
391 );
392 attrs.insert(
393 "accessesSensitiveData".into(),
394 AttributeValue::Boolean(operation.accesses_sensitive_data),
395 );
396 attrs.insert(
397 "rootFields".into(),
398 Self::string_set(&operation.root_fields),
399 );
400 attrs.insert(
401 "accessedTypes".into(),
402 Self::string_set(&operation.accessed_types),
403 );
404 attrs.insert(
405 "accessedFields".into(),
406 Self::string_set(&operation.accessed_fields),
407 );
408 attrs.insert(
409 "sensitiveCategories".into(),
410 Self::string_set(&operation.sensitive_categories),
411 );
412
413 EntityItem::builder()
414 .identifier(
415 EntityIdentifier::builder()
416 .entity_type("CodeMode::Operation")
417 .entity_id(&operation.id)
418 .build()
419 .expect("valid entity identifier"),
420 )
421 .set_attributes(Some(attrs))
422 .build()
423 }
424
425 fn build_server_config_entity(&self, config: &ServerConfigEntity) -> EntityItem {
426 let mut attrs: HashMap<String, AttributeValue> = HashMap::new();
427 attrs.insert(
428 "serverId".into(),
429 AttributeValue::String(config.server_id.clone()),
430 );
431 attrs.insert(
432 "serverType".into(),
433 AttributeValue::String(config.server_type.clone()),
434 );
435 attrs.insert(
436 "allowWrite".into(),
437 AttributeValue::Boolean(config.allow_write),
438 );
439 attrs.insert(
440 "allowDelete".into(),
441 AttributeValue::Boolean(config.allow_delete),
442 );
443 attrs.insert(
444 "allowAdmin".into(),
445 AttributeValue::Boolean(config.allow_admin),
446 );
447 attrs.insert(
448 "maxDepth".into(),
449 AttributeValue::Long(config.max_depth as i64),
450 );
451 attrs.insert(
452 "maxFieldCount".into(),
453 AttributeValue::Long(config.max_field_count as i64),
454 );
455 attrs.insert(
456 "maxCost".into(),
457 AttributeValue::Long(config.max_cost as i64),
458 );
459 attrs.insert(
460 "maxApiCalls".into(),
461 AttributeValue::Long(config.max_api_calls as i64),
462 );
463 attrs.insert(
464 "allowedOperations".into(),
465 Self::string_set(&config.allowed_operations),
466 );
467 attrs.insert(
468 "blockedOperations".into(),
469 Self::string_set(&config.blocked_operations),
470 );
471 attrs.insert(
472 "blockedFields".into(),
473 Self::string_set(&config.blocked_fields),
474 );
475
476 EntityItem::builder()
477 .identifier(
478 EntityIdentifier::builder()
479 .entity_type("CodeMode::Server")
480 .entity_id(&config.server_id)
481 .build()
482 .expect("valid entity identifier"),
483 )
484 .set_attributes(Some(attrs))
485 .build()
486 }
487
488 fn string_set(set: &HashSet<String>) -> AttributeValue {
489 AttributeValue::Set(
490 set.iter()
491 .map(|s| AttributeValue::String(s.clone()))
492 .collect(),
493 )
494 }
495}
496
497#[cfg(feature = "openapi-code-mode")]
502impl AvpClient {
503 pub async fn is_script_authorized(
505 &self,
506 script: &ScriptEntity,
507 server: &OpenAPIServerEntity,
508 ) -> Result<AuthorizationDecision, AvpError> {
509 let entities = EntitiesDefinition::EntityList(vec![
510 self.build_script_entity(script),
511 self.build_openapi_server_entity(server),
512 ]);
513
514 let response = self
515 .client
516 .is_authorized()
517 .policy_store_id(&self.policy_store_id)
518 .principal(
519 EntityIdentifier::builder()
520 .entity_type("CodeMode::Script")
521 .entity_id(&script.id)
522 .build()
523 .map_err(|e| AvpError::SdkError(e.to_string()))?,
524 )
525 .action(
526 ActionIdentifier::builder()
527 .action_type("CodeMode::Action")
528 .action_id(script.action())
529 .build()
530 .map_err(|e| AvpError::SdkError(e.to_string()))?,
531 )
532 .resource(
533 EntityIdentifier::builder()
534 .entity_type("CodeMode::Server")
535 .entity_id(&server.server_id)
536 .build()
537 .map_err(|e| AvpError::SdkError(e.to_string()))?,
538 )
539 .entities(entities)
540 .send()
541 .await
542 .map_err(|e| {
543 tracing::error!(error = ?e, "AVP is_authorized failed");
544 AvpError::SdkError(e.to_string())
545 })?;
546
547 Ok(self.parse_response(&response))
548 }
549
550 fn build_script_entity(&self, script: &ScriptEntity) -> EntityItem {
551 let mut attrs: HashMap<String, AttributeValue> = HashMap::new();
552 attrs.insert(
553 "scriptType".into(),
554 AttributeValue::String(script.script_type.clone()),
555 );
556 attrs.insert(
557 "hasWrites".into(),
558 AttributeValue::Boolean(script.has_writes),
559 );
560 attrs.insert(
561 "hasDeletes".into(),
562 AttributeValue::Boolean(script.has_deletes),
563 );
564 attrs.insert(
565 "accessesSensitivePath".into(),
566 AttributeValue::Boolean(script.accesses_sensitive_path),
567 );
568 attrs.insert(
569 "hasUnboundedLoop".into(),
570 AttributeValue::Boolean(script.has_unbounded_loop),
571 );
572 attrs.insert(
573 "hasDynamicPath".into(),
574 AttributeValue::Boolean(script.has_dynamic_path),
575 );
576 attrs.insert(
577 "totalApiCalls".into(),
578 AttributeValue::Long(script.total_api_calls as i64),
579 );
580 attrs.insert(
581 "readCalls".into(),
582 AttributeValue::Long(script.read_calls as i64),
583 );
584 attrs.insert(
585 "writeCalls".into(),
586 AttributeValue::Long(script.write_calls as i64),
587 );
588 attrs.insert(
589 "deleteCalls".into(),
590 AttributeValue::Long(script.delete_calls as i64),
591 );
592 attrs.insert(
593 "loopIterations".into(),
594 AttributeValue::Long(script.loop_iterations as i64),
595 );
596 attrs.insert(
597 "nestingDepth".into(),
598 AttributeValue::Long(script.nesting_depth as i64),
599 );
600 attrs.insert(
601 "scriptLength".into(),
602 AttributeValue::Long(script.script_length as i64),
603 );
604 attrs.insert(
605 "accessedPaths".into(),
606 Self::string_set(&script.accessed_paths),
607 );
608 attrs.insert(
609 "accessedMethods".into(),
610 Self::string_set(&script.accessed_methods),
611 );
612 attrs.insert(
613 "pathPatterns".into(),
614 Self::string_set(&script.path_patterns),
615 );
616 attrs.insert(
617 "calledOperations".into(),
618 Self::string_set(&script.called_operations),
619 );
620 attrs.insert(
621 "hasOutputDeclaration".into(),
622 AttributeValue::Boolean(script.has_output_declaration),
623 );
624 attrs.insert(
625 "outputFields".into(),
626 Self::string_set(&script.output_fields),
627 );
628 attrs.insert(
629 "hasSpreadInOutput".into(),
630 AttributeValue::Boolean(script.has_spread_in_output),
631 );
632
633 EntityItem::builder()
634 .identifier(
635 EntityIdentifier::builder()
636 .entity_type("CodeMode::Script")
637 .entity_id(&script.id)
638 .build()
639 .expect("valid entity identifier"),
640 )
641 .set_attributes(Some(attrs))
642 .build()
643 }
644
645 fn build_openapi_server_entity(&self, server: &OpenAPIServerEntity) -> EntityItem {
646 let mut attrs: HashMap<String, AttributeValue> = HashMap::new();
647 attrs.insert(
648 "serverId".into(),
649 AttributeValue::String(server.server_id.clone()),
650 );
651 attrs.insert(
652 "serverType".into(),
653 AttributeValue::String(server.server_type.clone()),
654 );
655 attrs.insert(
656 "allowWrite".into(),
657 AttributeValue::Boolean(server.allow_write),
658 );
659 attrs.insert(
660 "allowDelete".into(),
661 AttributeValue::Boolean(server.allow_delete),
662 );
663 attrs.insert(
664 "allowAdmin".into(),
665 AttributeValue::Boolean(server.allow_admin),
666 );
667 attrs.insert(
668 "writeMode".into(),
669 AttributeValue::String(server.write_mode.clone()),
670 );
671 attrs.insert(
672 "maxDepth".into(),
673 AttributeValue::Long(server.max_depth as i64),
674 );
675 attrs.insert(
676 "maxCost".into(),
677 AttributeValue::Long(server.max_cost as i64),
678 );
679 attrs.insert(
680 "maxApiCalls".into(),
681 AttributeValue::Long(server.max_api_calls as i64),
682 );
683 attrs.insert(
684 "maxLoopIterations".into(),
685 AttributeValue::Long(server.max_loop_iterations as i64),
686 );
687 attrs.insert(
688 "maxScriptLength".into(),
689 AttributeValue::Long(server.max_script_length as i64),
690 );
691 attrs.insert(
692 "maxNestingDepth".into(),
693 AttributeValue::Long(server.max_nesting_depth as i64),
694 );
695 attrs.insert(
696 "executionTimeoutSeconds".into(),
697 AttributeValue::Long(server.execution_timeout_seconds as i64),
698 );
699 attrs.insert(
700 "allowedOperations".into(),
701 AttributeValue::Set(
702 server
703 .allowed_operations
704 .iter()
705 .map(|s| AttributeValue::String(normalize_operation_format(s)))
706 .collect(),
707 ),
708 );
709 attrs.insert(
710 "blockedOperations".into(),
711 AttributeValue::Set(
712 server
713 .blocked_operations
714 .iter()
715 .map(|s| AttributeValue::String(normalize_operation_format(s)))
716 .collect(),
717 ),
718 );
719 attrs.insert(
720 "allowedMethods".into(),
721 Self::string_set(&server.allowed_methods),
722 );
723 attrs.insert(
724 "blockedMethods".into(),
725 Self::string_set(&server.blocked_methods),
726 );
727 attrs.insert(
728 "allowedPathPatterns".into(),
729 Self::string_set(&server.allowed_path_patterns),
730 );
731 attrs.insert(
732 "blockedPathPatterns".into(),
733 Self::string_set(&server.blocked_path_patterns),
734 );
735 attrs.insert(
736 "sensitivePathPatterns".into(),
737 Self::string_set(&server.sensitive_path_patterns),
738 );
739 attrs.insert(
740 "autoApproveReadOnly".into(),
741 AttributeValue::Boolean(server.auto_approve_read_only),
742 );
743 attrs.insert(
744 "maxApiCallsForAutoApprove".into(),
745 AttributeValue::Long(server.max_api_calls_for_auto_approve as i64),
746 );
747 attrs.insert(
748 "internalBlockedFields".into(),
749 Self::string_set(&server.internal_blocked_fields),
750 );
751 attrs.insert(
752 "outputBlockedFields".into(),
753 Self::string_set(&server.output_blocked_fields),
754 );
755 attrs.insert(
756 "requireOutputDeclaration".into(),
757 AttributeValue::Boolean(server.require_output_declaration),
758 );
759
760 EntityItem::builder()
761 .identifier(
762 EntityIdentifier::builder()
763 .entity_type("CodeMode::Server")
764 .entity_id(&server.server_id)
765 .build()
766 .expect("valid entity identifier"),
767 )
768 .set_attributes(Some(attrs))
769 .build()
770 }
771}
772
773#[cfg(feature = "sql-code-mode")]
778impl AvpClient {
779 pub async fn is_statement_authorized(
781 &self,
782 statement: &StatementEntity,
783 server: &SqlServerEntity,
784 ) -> Result<AuthorizationDecision, AvpError> {
785 let entities = EntitiesDefinition::EntityList(vec![
786 self.build_statement_entity(statement),
787 self.build_sql_server_entity(server),
788 ]);
789
790 let response = self
791 .client
792 .is_authorized()
793 .policy_store_id(&self.policy_store_id)
794 .principal(
795 EntityIdentifier::builder()
796 .entity_type("CodeMode::Statement")
797 .entity_id(&statement.id)
798 .build()
799 .map_err(|e| AvpError::SdkError(e.to_string()))?,
800 )
801 .action(
802 ActionIdentifier::builder()
803 .action_type("CodeMode::Action")
804 .action_id(statement.action())
805 .build()
806 .map_err(|e| AvpError::SdkError(e.to_string()))?,
807 )
808 .resource(
809 EntityIdentifier::builder()
810 .entity_type("CodeMode::Server")
811 .entity_id(&server.server_id)
812 .build()
813 .map_err(|e| AvpError::SdkError(e.to_string()))?,
814 )
815 .entities(entities)
816 .send()
817 .await
818 .map_err(|e| AvpError::SdkError(e.to_string()))?;
819
820 Ok(self.parse_response(&response))
821 }
822
823 fn build_statement_entity(&self, statement: &StatementEntity) -> EntityItem {
824 let mut attrs: HashMap<String, AttributeValue> = HashMap::new();
825 attrs.insert(
826 "statementType".into(),
827 AttributeValue::String(statement.statement_type.clone()),
828 );
829 attrs.insert("tables".into(), Self::string_set(&statement.tables));
830 attrs.insert("columns".into(), Self::string_set(&statement.columns));
831 attrs.insert(
832 "hasWhere".into(),
833 AttributeValue::Boolean(statement.has_where),
834 );
835 attrs.insert(
836 "hasLimit".into(),
837 AttributeValue::Boolean(statement.has_limit),
838 );
839 attrs.insert(
840 "hasOrderBy".into(),
841 AttributeValue::Boolean(statement.has_order_by),
842 );
843 attrs.insert(
844 "estimatedRows".into(),
845 AttributeValue::Long(statement.estimated_rows as i64),
846 );
847 attrs.insert(
848 "joinCount".into(),
849 AttributeValue::Long(statement.join_count as i64),
850 );
851 attrs.insert(
852 "subqueryCount".into(),
853 AttributeValue::Long(statement.subquery_count as i64),
854 );
855
856 EntityItem::builder()
857 .identifier(
858 EntityIdentifier::builder()
859 .entity_type("CodeMode::Statement")
860 .entity_id(&statement.id)
861 .build()
862 .expect("valid entity identifier"),
863 )
864 .set_attributes(Some(attrs))
865 .build()
866 }
867
868 fn build_sql_server_entity(&self, server: &SqlServerEntity) -> EntityItem {
869 let mut attrs: HashMap<String, AttributeValue> = HashMap::new();
870 attrs.insert(
871 "serverId".into(),
872 AttributeValue::String(server.server_id.clone()),
873 );
874 attrs.insert(
875 "serverType".into(),
876 AttributeValue::String(server.server_type.clone()),
877 );
878 attrs.insert(
879 "allowWrite".into(),
880 AttributeValue::Boolean(server.allow_write),
881 );
882 attrs.insert(
883 "allowDelete".into(),
884 AttributeValue::Boolean(server.allow_delete),
885 );
886 attrs.insert(
887 "allowAdmin".into(),
888 AttributeValue::Boolean(server.allow_admin),
889 );
890 attrs.insert(
891 "maxRows".into(),
892 AttributeValue::Long(server.max_rows as i64),
893 );
894 attrs.insert(
895 "maxJoins".into(),
896 AttributeValue::Long(server.max_joins as i64),
897 );
898 attrs.insert(
899 "allowedOperations".into(),
900 Self::string_set(&server.allowed_operations),
901 );
902 attrs.insert(
903 "blockedOperations".into(),
904 Self::string_set(&server.blocked_operations),
905 );
906 attrs.insert(
907 "blockedTables".into(),
908 Self::string_set(&server.blocked_tables),
909 );
910 attrs.insert(
911 "blockedColumns".into(),
912 Self::string_set(&server.blocked_columns),
913 );
914
915 EntityItem::builder()
916 .identifier(
917 EntityIdentifier::builder()
918 .entity_type("CodeMode::Server")
919 .entity_id(&server.server_id)
920 .build()
921 .expect("valid entity identifier"),
922 )
923 .set_attributes(Some(attrs))
924 .build()
925 }
926}
927
928pub struct AvpPolicyEvaluator {
948 client: AvpClient,
949}
950
951impl AvpPolicyEvaluator {
952 pub fn new(client: AvpClient) -> Self {
954 Self { client }
955 }
956}
957
958#[async_trait::async_trait]
959impl PolicyEvaluator for AvpPolicyEvaluator {
960 async fn evaluate_operation(
961 &self,
962 operation: &OperationEntity,
963 server_config: &ServerConfigEntity,
964 ) -> Result<AuthorizationDecision, PolicyEvaluationError> {
965 self.client
966 .is_authorized(operation, server_config)
967 .await
968 .map_err(|e| PolicyEvaluationError::EvaluationError(e.to_string()))
969 }
970
971 #[cfg(feature = "openapi-code-mode")]
972 async fn evaluate_script(
973 &self,
974 script: &ScriptEntity,
975 server: &OpenAPIServerEntity,
976 ) -> Result<AuthorizationDecision, PolicyEvaluationError> {
977 self.client
978 .is_script_authorized(script, server)
979 .await
980 .map_err(|e| PolicyEvaluationError::EvaluationError(e.to_string()))
981 }
982
983 #[cfg(feature = "sql-code-mode")]
984 async fn evaluate_statement(
985 &self,
986 statement: &StatementEntity,
987 server: &SqlServerEntity,
988 ) -> Result<AuthorizationDecision, PolicyEvaluationError> {
989 self.client
990 .is_statement_authorized(statement, server)
991 .await
992 .map_err(|e| PolicyEvaluationError::EvaluationError(e.to_string()))
993 }
994
995 fn name(&self) -> &str {
996 "avp"
997 }
998}