fraiseql_core/graphql/
require_permission_directive.rs1use std::collections::HashMap;
7
8use serde_json::Value as JsonValue;
9
10use crate::graphql::directive_evaluator::{
11 DirectiveError, DirectiveHandler, DirectiveResult, EvaluationContext,
12};
13
14pub struct RequirePermissionDirective;
45
46impl RequirePermissionDirective {
47 #[must_use]
49 pub const fn new() -> Self {
50 Self
51 }
52
53 pub(crate) fn permission_matches(user_permission: &str, required_permission: &str) -> bool {
61 if user_permission == required_permission {
63 return true;
64 }
65
66 if user_permission == "*:*" {
68 return true;
69 }
70
71 let user_parts: Vec<&str> = user_permission.split(':').collect();
73 let required_parts: Vec<&str> = required_permission.split(':').collect();
74
75 if let Some(&last_part) = user_parts.last() {
77 if last_part == "*" {
78 let user_prefix_len = user_parts.len() - 1;
79 if user_prefix_len <= required_parts.len() {
80 return user_parts[..user_prefix_len] == required_parts[..user_prefix_len];
81 }
82 }
83 }
84
85 false
86 }
87
88 fn get_user_permissions(context: &EvaluationContext) -> Vec<String> {
92 context
93 .get_user_context("permissions")
94 .and_then(|v| v.as_array())
95 .map(|perms| perms.iter().filter_map(|p| p.as_str().map(String::from)).collect())
96 .unwrap_or_default()
97 }
98
99 fn user_has_permission(required_permission: &str, user_permissions: &[String]) -> bool {
103 user_permissions
104 .iter()
105 .any(|perm| Self::permission_matches(perm, required_permission))
106 }
107}
108
109impl Default for RequirePermissionDirective {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114
115impl DirectiveHandler for RequirePermissionDirective {
116 fn name(&self) -> &'static str {
117 "require_permission"
118 }
119
120 fn evaluate(
121 &self,
122 args: &HashMap<String, JsonValue>,
123 context: &EvaluationContext,
124 ) -> Result<DirectiveResult, DirectiveError> {
125 let required_permission = args
127 .get("permission")
128 .and_then(|v| v.as_str())
129 .ok_or_else(|| DirectiveError::MissingDirectiveArgument("permission".to_string()))?;
130
131 let user_permissions = Self::get_user_permissions(context);
133
134 if Self::user_has_permission(required_permission, &user_permissions) {
136 return Ok(DirectiveResult::Include);
138 }
139
140 if let Some(mask_value) = args.get("maskValue") {
142 return Ok(DirectiveResult::Transform(mask_value.clone()));
143 }
144
145 Ok(DirectiveResult::Error(format!(
147 "User lacks required permission: {}",
148 required_permission
149 )))
150 }
151
152 fn validate_args(&self, args: &HashMap<String, JsonValue>) -> Result<(), DirectiveError> {
153 if !args.contains_key("permission") {
155 return Err(DirectiveError::MissingDirectiveArgument("permission".to_string()));
156 }
157
158 let permission = args
159 .get("permission")
160 .and_then(|v| v.as_str())
161 .ok_or(DirectiveError::InvalidDirectiveArgument)?;
162
163 if permission.is_empty() {
165 return Err(DirectiveError::InvalidDirectiveArgument);
166 }
167
168 if let Some(mask) = args.get("maskValue") {
170 if !mask.is_string() && !mask.is_number() && !mask.is_null() {
171 return Err(DirectiveError::InvalidDirectiveArgument);
172 }
173 }
174
175 Ok(())
176 }
177}