1use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
7#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
8pub enum AwsPermissionEffect {
9 #[default]
10 Allow,
11 Deny,
12}
13
14impl AwsPermissionEffect {
15 pub fn as_str(&self) -> &'static str {
16 match self {
17 Self::Allow => "Allow",
18 Self::Deny => "Deny",
19 }
20 }
21
22 pub fn is_allow(&self) -> bool {
23 matches!(self, Self::Allow)
24 }
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
29#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
30#[serde(rename_all = "camelCase", deny_unknown_fields)]
31pub struct PermissionGrant {
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub actions: Option<Vec<String>>,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub permissions: Option<Vec<String>>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub predefined_roles: Option<Vec<String>>,
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub residual_permissions: Option<Vec<String>>,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub data_actions: Option<Vec<String>>,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
52#[serde(rename_all = "camelCase", deny_unknown_fields)]
53pub struct AwsBindingSpec {
54 pub resources: Vec<String>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub condition: Option<IndexMap<String, IndexMap<String, String>>>,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
64#[serde(rename_all = "camelCase", deny_unknown_fields)]
65pub struct GcpBindingSpec {
66 pub scope: String,
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub condition: Option<GcpCondition>,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
76#[serde(rename_all = "camelCase", deny_unknown_fields)]
77pub struct AzureBindingSpec {
78 pub scope: String,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
85#[serde(rename_all = "camelCase", deny_unknown_fields)]
86pub struct BindingConfiguration<T> {
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub stack: Option<T>,
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub resource: Option<T>,
93}
94
95impl<T> BindingConfiguration<T> {
96 pub fn is_empty(&self) -> bool {
98 self.stack.is_none() && self.resource.is_none()
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
105#[serde(rename_all = "camelCase", deny_unknown_fields)]
106pub struct GcpCondition {
107 pub title: String,
108 pub expression: String,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
114#[serde(rename_all = "camelCase", deny_unknown_fields)]
115pub struct AwsPlatformPermission {
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub label: Option<String>,
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub description: Option<String>,
122 #[serde(default, skip_serializing_if = "AwsPermissionEffect::is_allow")]
124 pub effect: AwsPermissionEffect,
125 pub grant: PermissionGrant,
127 pub binding: BindingConfiguration<AwsBindingSpec>,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
134#[serde(rename_all = "camelCase", deny_unknown_fields)]
135pub struct GcpPlatformPermission {
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub label: Option<String>,
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub description: Option<String>,
142 pub grant: PermissionGrant,
144 pub binding: BindingConfiguration<GcpBindingSpec>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
150#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
151#[serde(rename_all = "camelCase", deny_unknown_fields)]
152pub struct AzurePlatformPermission {
153 #[serde(skip_serializing_if = "Option::is_none")]
155 pub label: Option<String>,
156 #[serde(skip_serializing_if = "Option::is_none")]
158 pub description: Option<String>,
159 pub grant: PermissionGrant,
161 pub binding: BindingConfiguration<AzureBindingSpec>,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
167#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
168#[serde(rename_all = "camelCase", deny_unknown_fields)]
169pub struct PlatformPermissions {
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub aws: Option<Vec<AwsPlatformPermission>>,
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub gcp: Option<Vec<GcpPlatformPermission>>,
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub azure: Option<Vec<AzurePlatformPermission>>,
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
183#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
184#[serde(rename_all = "camelCase", deny_unknown_fields)]
185pub struct PermissionSet {
186 pub id: String,
188 pub description: String,
190 pub platforms: PlatformPermissions,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
196#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
197#[serde(untagged)]
198pub enum PermissionSetReference {
199 Name(String),
201 Inline(PermissionSet),
203}
204
205impl PermissionSetReference {
206 pub fn id(&self) -> &str {
208 match self {
209 PermissionSetReference::Name(name) => name,
210 PermissionSetReference::Inline(permission_set) => &permission_set.id,
211 }
212 }
213
214 pub fn from_name(name: impl Into<String>) -> Self {
216 PermissionSetReference::Name(name.into())
217 }
218
219 pub fn from_inline(permission_set: PermissionSet) -> Self {
221 PermissionSetReference::Inline(permission_set)
222 }
223
224 pub fn resolve(
227 &self,
228 resolver: impl Fn(&str) -> Option<PermissionSet>,
229 ) -> Option<PermissionSet> {
230 match self {
231 PermissionSetReference::Name(name) => resolver(name),
232 PermissionSetReference::Inline(permission_set) => Some(permission_set.clone()),
233 }
234 }
235}
236
237#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
240#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
241#[serde(transparent)]
242pub struct PermissionProfile(pub IndexMap<String, Vec<PermissionSetReference>>);
243
244impl PermissionProfile {
245 pub fn new() -> Self {
247 Self(IndexMap::new())
248 }
249
250 pub fn global<I>(mut self, permission_sets: I) -> Self
252 where
253 I: IntoIterator,
254 I::Item: Into<PermissionSetReference>,
255 {
256 let permission_list: Vec<PermissionSetReference> =
257 permission_sets.into_iter().map(|s| s.into()).collect();
258 self.0.insert("*".to_string(), permission_list);
259 self
260 }
261
262 pub fn resource<I>(mut self, resource_name: impl Into<String>, permission_sets: I) -> Self
264 where
265 I: IntoIterator,
266 I::Item: Into<PermissionSetReference>,
267 {
268 let permission_list: Vec<PermissionSetReference> =
269 permission_sets.into_iter().map(|s| s.into()).collect();
270 self.0.insert(resource_name.into(), permission_list);
271 self
272 }
273}
274
275impl Default for PermissionProfile {
276 fn default() -> Self {
277 Self::new()
278 }
279}
280
281impl From<String> for PermissionSetReference {
282 fn from(name: String) -> Self {
283 PermissionSetReference::Name(name)
284 }
285}
286
287impl From<&str> for PermissionSetReference {
288 fn from(name: &str) -> Self {
289 PermissionSetReference::Name(name.to_string())
290 }
291}
292
293impl From<PermissionSet> for PermissionSetReference {
294 fn from(permission_set: PermissionSet) -> Self {
295 PermissionSetReference::Inline(permission_set)
296 }
297}
298
299#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
301#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
302#[serde(rename_all = "camelCase", deny_unknown_fields)]
303pub enum ManagementPermissions {
304 Auto,
309
310 Extend(PermissionProfile),
312
313 Override(PermissionProfile),
315}
316
317impl Default for ManagementPermissions {
318 fn default() -> Self {
319 ManagementPermissions::Auto
320 }
321}
322
323#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
325#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
326#[serde(rename_all = "camelCase", deny_unknown_fields)]
327pub struct PermissionsConfig {
328 pub profiles: IndexMap<String, PermissionProfile>,
331 #[serde(default)]
333 pub management: ManagementPermissions,
334}
335
336impl PermissionsConfig {
337 pub fn new() -> Self {
339 Self {
340 profiles: IndexMap::new(),
341 management: ManagementPermissions::Auto,
342 }
343 }
344
345 pub fn with_profile(mut self, name: impl Into<String>, profile: PermissionProfile) -> Self {
347 self.profiles.insert(name.into(), profile);
348 self
349 }
350
351 pub fn with_management(mut self, management: ManagementPermissions) -> Self {
353 self.management = management;
354 self
355 }
356}
357
358impl Default for PermissionsConfig {
359 fn default() -> Self {
360 Self::new()
361 }
362}
363
364impl ManagementPermissions {
365 pub fn auto() -> Self {
367 ManagementPermissions::Auto
368 }
369
370 pub fn extend(profile: PermissionProfile) -> Self {
372 ManagementPermissions::Extend(profile)
373 }
374
375 pub fn override_(profile: PermissionProfile) -> Self {
377 ManagementPermissions::Override(profile)
378 }
379
380 pub fn profile(&self) -> Option<&PermissionProfile> {
382 match self {
383 ManagementPermissions::Auto => None,
384 ManagementPermissions::Extend(profile) => Some(profile),
385 ManagementPermissions::Override(profile) => Some(profile),
386 }
387 }
388
389 pub fn is_auto(&self) -> bool {
391 matches!(self, ManagementPermissions::Auto)
392 }
393
394 pub fn is_extend(&self) -> bool {
396 matches!(self, ManagementPermissions::Extend(_))
397 }
398
399 pub fn is_override(&self) -> bool {
401 matches!(self, ManagementPermissions::Override(_))
402 }
403}