Skip to main content

alien_permissions/generators/
azure_runtime.rs

1use crate::{
2    error::{ErrorData, Result},
3    variables::VariableInterpolator,
4    BindingTarget, PermissionContext,
5};
6use alien_core::PermissionSet;
7use serde::{Deserialize, Serialize};
8
9/// Azure role definition
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
11#[serde(rename_all = "PascalCase")]
12pub struct AzureRoleDefinition {
13    /// Human-readable role name
14    pub name: String,
15    /// Role ID (will be generated by Azure)
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub id: Option<String>,
18    /// Whether this is a custom role
19    pub is_custom: bool,
20    /// Description of what the role allows
21    pub description: String,
22    /// List of allowed actions
23    pub actions: Vec<String>,
24    /// List of denied actions
25    pub not_actions: Vec<String>,
26    /// List of allowed data actions
27    pub data_actions: Vec<String>,
28    /// List of denied data actions
29    pub not_data_actions: Vec<String>,
30    /// Scopes where this role can be assigned
31    pub assignable_scopes: Vec<String>,
32}
33
34/// Azure role assignment properties
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
36#[serde(rename_all = "camelCase")]
37pub struct AzureRoleAssignmentProperties {
38    /// Role definition ID
39    pub role_definition_id: String,
40    /// Principal ID (user, group, or service principal)
41    pub principal_id: String,
42    /// Scope where the role is assigned
43    pub scope: String,
44}
45
46/// Azure role assignment
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
48#[serde(rename_all = "camelCase")]
49pub struct AzureRoleAssignment {
50    /// Role assignment properties
51    pub properties: AzureRoleAssignmentProperties,
52}
53
54/// Azure runtime permissions generator for role definitions and role assignments
55pub struct AzureRuntimePermissionsGenerator;
56
57impl AzureRuntimePermissionsGenerator {
58    /// Create a new Azure runtime permissions generator
59    pub fn new() -> Self {
60        Self
61    }
62
63    /// Generate an Azure role definition from a permission set
64    ///
65    /// Takes a PermissionSet and produces Azure role definitions
66    /// that can be created at runtime.
67    pub fn generate_role_definition(
68        &self,
69        permission_set: &PermissionSet,
70        binding_target: BindingTarget,
71        context: &PermissionContext,
72    ) -> Result<AzureRoleDefinition> {
73        let azure_platform_permissions =
74            permission_set.platforms.azure.as_ref().ok_or_else(|| {
75                alien_error::AlienError::new(ErrorData::PlatformNotSupported {
76                    platform: "azure".to_string(),
77                    permission_set_id: permission_set.id.clone(),
78                })
79            })?;
80
81        let role_name = self.generate_role_name(&permission_set.id);
82
83        // Aggregate actions and data actions from all platform permissions
84        let mut all_actions = Vec::new();
85        let mut all_data_actions = Vec::new();
86        let mut assignable_scopes = Vec::new();
87
88        for platform_permission in azure_platform_permissions {
89            // Extract actions and data actions
90            if let Some(actions) = &platform_permission.grant.actions {
91                all_actions.extend(actions.clone());
92            }
93            if let Some(data_actions) = &platform_permission.grant.data_actions {
94                all_data_actions.extend(data_actions.clone());
95            }
96
97            // Generate assignable scopes based on binding target
98            let binding_spec = match binding_target {
99                BindingTarget::Stack => {
100                    platform_permission.binding.stack.as_ref().ok_or_else(|| {
101                        alien_error::AlienError::new(ErrorData::BindingTargetNotSupported {
102                            platform: "azure".to_string(),
103                            binding_target: "stack".to_string(),
104                            permission_set_id: permission_set.id.clone(),
105                        })
106                    })?
107                }
108                BindingTarget::Resource => platform_permission
109                    .binding
110                    .resource
111                    .as_ref()
112                    .ok_or_else(|| {
113                        alien_error::AlienError::new(ErrorData::BindingTargetNotSupported {
114                            platform: "azure".to_string(),
115                            binding_target: "resource".to_string(),
116                            permission_set_id: permission_set.id.clone(),
117                        })
118                    })?,
119            };
120
121            // Interpolate variables in the scope
122            let interpolated_scope =
123                VariableInterpolator::interpolate_variables(&binding_spec.scope, context)?;
124            assignable_scopes.push(interpolated_scope);
125        }
126
127        // Remove duplicates and sort
128        all_actions.sort();
129        all_actions.dedup();
130        all_data_actions.sort();
131        all_data_actions.dedup();
132        assignable_scopes.sort();
133        assignable_scopes.dedup();
134
135        Ok(AzureRoleDefinition {
136            name: role_name,
137            id: None, // Will be generated by Azure
138            is_custom: true,
139            description: permission_set.description.clone(),
140            actions: all_actions,
141            not_actions: vec![],
142            data_actions: all_data_actions,
143            not_data_actions: vec![],
144            assignable_scopes,
145        })
146    }
147
148    /// Generate an Azure role assignment
149    ///
150    /// Takes a PermissionSet and binding target, produces Azure role assignments
151    /// that can be created at runtime.
152    pub fn generate_role_assignment(
153        &self,
154        permission_set: &PermissionSet,
155        binding_target: BindingTarget,
156        context: &PermissionContext,
157    ) -> Result<AzureRoleAssignment> {
158        let azure_platform_permissions =
159            permission_set.platforms.azure.as_ref().ok_or_else(|| {
160                alien_error::AlienError::new(ErrorData::PlatformNotSupported {
161                    platform: "azure".to_string(),
162                    permission_set_id: permission_set.id.clone(),
163                })
164            })?;
165
166        // For this example, we'll use placeholder values
167        let role_definition_id = format!(
168            "/subscriptions/{}/providers/Microsoft.Authorization/roleDefinitions/${{roleDefinitionGuid}}",
169            context.subscription_id.as_deref().unwrap_or("SUBSCRIPTION_ID")
170        );
171
172        let principal_id = context
173            .principal_id
174            .as_deref()
175            .unwrap_or("PRINCIPAL_ID")
176            .to_string();
177
178        // Use the first platform permission's binding for simplicity
179        // In practice, you might want to handle multiple bindings differently
180        let first_platform_permission = &azure_platform_permissions[0];
181        let binding_spec = match binding_target {
182            BindingTarget::Stack => first_platform_permission
183                .binding
184                .stack
185                .as_ref()
186                .ok_or_else(|| {
187                    alien_error::AlienError::new(ErrorData::BindingTargetNotSupported {
188                        platform: "azure".to_string(),
189                        binding_target: "stack".to_string(),
190                        permission_set_id: permission_set.id.clone(),
191                    })
192                })?,
193            BindingTarget::Resource => first_platform_permission
194                .binding
195                .resource
196                .as_ref()
197                .ok_or_else(|| {
198                    alien_error::AlienError::new(ErrorData::BindingTargetNotSupported {
199                        platform: "azure".to_string(),
200                        binding_target: "resource".to_string(),
201                        permission_set_id: permission_set.id.clone(),
202                    })
203                })?,
204        };
205
206        // Interpolate variables in the scope
207        let interpolated_scope =
208            VariableInterpolator::interpolate_variables(&binding_spec.scope, context)?;
209
210        Ok(AzureRoleAssignment {
211            properties: AzureRoleAssignmentProperties {
212                role_definition_id,
213                principal_id,
214                scope: interpolated_scope,
215            },
216        })
217    }
218
219    /// Generate a human-readable role name
220    fn generate_role_name(&self, permission_set_id: &str) -> String {
221        permission_set_id
222            .split('/')
223            .map(|part| {
224                part.split('-')
225                    .map(|word| {
226                        let mut chars = word.chars();
227                        match chars.next() {
228                            None => String::new(),
229                            Some(first) => {
230                                first.to_uppercase().collect::<String>() + chars.as_str()
231                            }
232                        }
233                    })
234                    .collect::<Vec<String>>()
235                    .join(" ")
236            })
237            .collect::<Vec<String>>()
238            .join(" ")
239    }
240}
241
242impl Default for AzureRuntimePermissionsGenerator {
243    fn default() -> Self {
244        Self::new()
245    }
246}