redis_cloud/
acl.rs

1//! Role-based Access Control (RBAC) operations and models
2//!
3//! This module provides comprehensive access control management for Redis Cloud,
4//! including ACL management for users, roles, Redis rules, and database-level
5//! access controls.
6//!
7//! # Overview
8//!
9//! The ACL module implements Redis Cloud's role-based access control system, allowing
10//! fine-grained control over who can access what resources and perform which operations.
11//! It supports both user-level and database-level access controls.
12//!
13//! # Key Features
14//!
15//! - **User ACLs**: Manage user access control lists and permissions
16//! - **Role Management**: Create and manage roles with specific permissions
17//! - **Redis Rules**: Define Redis command-level access rules
18//! - **Database ACLs**: Control access at the database level
19//! - **Rule Association**: Link users and roles to specific databases
20//!
21//! # Example Usage
22//!
23//! ```no_run
24//! use redis_cloud::{CloudClient, AclHandler};
25//!
26//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
27//! let client = CloudClient::builder()
28//!     .api_key("your-api-key")
29//!     .api_secret("your-api-secret")
30//!     .build()?;
31//!
32//! let handler = AclHandler::new(client);
33//!
34//! // List all ACL users
35//! let users = handler.get_all_acl_users().await?;
36//!
37//! // Get all Redis rules
38//! let rules = handler.get_all_redis_rules().await?;
39//! # Ok(())
40//! # }
41//! ```
42
43use crate::{CloudClient, Result};
44use serde::{Deserialize, Serialize};
45use serde_json::Value;
46use std::collections::HashMap;
47
48// ============================================================================
49// Models
50// ============================================================================
51
52/// ACL role create request
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct AclRoleCreateRequest {
56    /// Database access role name.
57    pub name: String,
58
59    /// A list of Redis ACL rules to assign to this database access role.
60    pub redis_rules: Vec<AclRoleRedisRuleSpec>,
61
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub command_type: Option<String>,
64
65    /// Additional fields from the API
66    #[serde(flatten)]
67    pub extra: Value,
68}
69
70/// ProcessorResponse
71#[derive(Debug, Clone, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase")]
73pub struct ProcessorResponse {
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub resource_id: Option<i32>,
76
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub additional_resource_id: Option<i32>,
79
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub resource: Option<HashMap<String, Value>>,
82
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub error: Option<String>,
85
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub additional_info: Option<String>,
88
89    /// Additional fields from the API
90    #[serde(flatten)]
91    pub extra: Value,
92}
93
94/// ACL user update request
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct AclUserUpdateRequest {
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub user_id: Option<i32>,
100
101    /// Optional. Changes the ACL role assigned to the user. Use GET '/acl/roles' to get a list of database access roles.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub role: Option<String>,
104
105    /// Optional. Changes the user's database password.
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub password: Option<String>,
108
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub command_type: Option<String>,
111
112    /// Additional fields from the API
113    #[serde(flatten)]
114    pub extra: Value,
115}
116
117/// Redis list of ACL users in current account
118///
119/// Response from GET /acl/users
120#[derive(Debug, Clone, Serialize, Deserialize)]
121#[serde(rename_all = "camelCase")]
122pub struct AccountACLUsers {
123    /// Account ID
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub account_id: Option<i32>,
126
127    /// List of ACL users (typically in extra as 'users' array)
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub users: Option<Vec<ACLUser>>,
130
131    /// HATEOAS links for API navigation
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub links: Option<Vec<HashMap<String, Value>>>,
134
135    /// Only for truly unknown/future API fields
136    #[serde(flatten)]
137    pub extra: Value,
138}
139
140/// Redis list of ACL redis rules in current account
141///
142/// Response from GET /acl/redisRules
143#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct AccountACLRedisRules {
146    /// Account ID
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub account_id: Option<i32>,
149
150    /// List of Redis ACL rules (typically in extra as 'redisRules' array)
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub redis_rules: Option<Vec<Value>>,
153
154    /// HATEOAS links for API navigation
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub links: Option<Vec<HashMap<String, Value>>>,
157
158    /// Only for truly unknown/future API fields
159    #[serde(flatten)]
160    pub extra: Value,
161}
162
163/// ACL redis rule create request
164#[derive(Debug, Clone, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct AclRedisRuleCreateRequest {
167    /// Redis ACL rule name.
168    pub name: String,
169
170    /// Redis ACL rule pattern. See [ACL syntax](https://redis.io/docs/latest/operate/rc/security/access-control/data-access-control/configure-acls/#define-permissions-with-acl-syntax) to learn how to define rules.
171    pub redis_rule: String,
172
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub command_type: Option<String>,
175
176    /// Additional fields from the API
177    #[serde(flatten)]
178    pub extra: Value,
179}
180
181/// Redis list of ACL roles in current account
182///
183/// Response from GET /acl/roles
184#[derive(Debug, Clone, Serialize, Deserialize)]
185#[serde(rename_all = "camelCase")]
186pub struct AccountACLRoles {
187    /// Account ID
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub account_id: Option<i32>,
190
191    /// List of ACL roles (typically in extra as 'roles' array)
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub roles: Option<Vec<Value>>,
194
195    /// HATEOAS links for API navigation
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub links: Option<Vec<HashMap<String, Value>>>,
198
199    /// Only for truly unknown/future API fields
200    #[serde(flatten)]
201    pub extra: Value,
202}
203
204/// ACL redis rule update request
205#[derive(Debug, Clone, Serialize, Deserialize)]
206#[serde(rename_all = "camelCase")]
207pub struct AclRedisRuleUpdateRequest {
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub redis_rule_id: Option<i32>,
210
211    /// Optional. Changes the Redis ACL rule name.
212    pub name: String,
213
214    /// Optional. Changes the Redis ACL rule pattern. See [ACL syntax](https://redis.io/docs/latest/operate/rc/security/access-control/data-access-control/configure-acls/#define-permissions-with-acl-syntax) to learn how to define rules.
215    pub redis_rule: String,
216
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub command_type: Option<String>,
219
220    /// Additional fields from the API
221    #[serde(flatten)]
222    pub extra: Value,
223}
224
225/// A list of databases where the specified rule applies for this role.
226#[derive(Debug, Clone, Serialize, Deserialize)]
227#[serde(rename_all = "camelCase")]
228pub struct AclRoleDatabaseSpec {
229    /// Subscription ID for the database's subscription. Use 'GET /subscriptions' or 'GET /fixed/subscriptions' to get a list of available subscriptions and their IDs.
230    pub subscription_id: i32,
231
232    /// The database's ID. Use 'GET /subscriptions/{subscriptionId}/databases' or 'GET /fixed/subscriptions/{subscriptionId}/databases' to get a list of databases in a subscription and their IDs.
233    pub database_id: i32,
234
235    /// (Active-Active databases only) Optional. A list of regions where this rule applies for this role.
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub regions: Option<Vec<String>>,
238
239    /// Additional fields from the API
240    #[serde(flatten)]
241    pub extra: Value,
242}
243
244/// ACL user create request
245#[derive(Debug, Clone, Serialize, Deserialize)]
246#[serde(rename_all = "camelCase")]
247pub struct AclUserCreateRequest {
248    /// Access control user name.
249    pub name: String,
250
251    /// Name of the database access role to assign to this user. Use GET '/acl/roles' to get a list of database access roles.
252    pub role: String,
253
254    /// The database password for this user.
255    pub password: String,
256
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub command_type: Option<String>,
259
260    /// Additional fields from the API
261    #[serde(flatten)]
262    pub extra: Value,
263}
264
265/// Redis ACL user information
266#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct ACLUser {
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub id: Option<i32>,
270
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub name: Option<String>,
273
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub role: Option<String>,
276
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub status: Option<String>,
279
280    /// HATEOAS links
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub links: Option<Vec<HashMap<String, Value>>>,
283
284    /// Additional fields from the API
285    #[serde(flatten)]
286    pub extra: Value,
287}
288
289/// ACL role update request
290#[derive(Debug, Clone, Serialize, Deserialize)]
291#[serde(rename_all = "camelCase")]
292pub struct AclRoleUpdateRequest {
293    /// Optional. Changes the database access role name.
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub name: Option<String>,
296
297    /// Optional. Changes the Redis ACL rules to assign to this database access role.
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub redis_rules: Option<Vec<AclRoleRedisRuleSpec>>,
300
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub role_id: Option<i32>,
303
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub command_type: Option<String>,
306
307    /// Additional fields from the API
308    #[serde(flatten)]
309    pub extra: Value,
310}
311
312/// Optional. Changes the Redis ACL rules to assign to this database access role.
313#[derive(Debug, Clone, Serialize, Deserialize)]
314#[serde(rename_all = "camelCase")]
315pub struct AclRoleRedisRuleSpec {
316    /// The name of a Redis ACL rule to assign to the role. Use 'GET /acl/redisRules' to get a list of available rules for your account.
317    pub rule_name: String,
318
319    /// A list of databases where the specified rule applies for this role.
320    pub databases: Vec<AclRoleDatabaseSpec>,
321
322    /// Additional fields from the API
323    #[serde(flatten)]
324    pub extra: Value,
325}
326
327/// TaskStateUpdate
328#[derive(Debug, Clone, Serialize, Deserialize)]
329#[serde(rename_all = "camelCase")]
330pub struct TaskStateUpdate {
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub task_id: Option<String>,
333
334    #[serde(skip_serializing_if = "Option::is_none")]
335    pub command_type: Option<String>,
336
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub status: Option<String>,
339
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub description: Option<String>,
342
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub timestamp: Option<String>,
345
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub response: Option<ProcessorResponse>,
348
349    /// HATEOAS links
350    #[serde(skip_serializing_if = "Option::is_none")]
351    pub links: Option<Vec<HashMap<String, Value>>>,
352
353    /// Additional fields from the API
354    #[serde(flatten)]
355    pub extra: Value,
356}
357
358// ============================================================================
359// Handler
360// ============================================================================
361
362/// Handler for Role-based Access Control (RBAC) operations
363///
364/// Manages ACLs for users, roles, Redis rules, and database-level access controls.
365/// Provides fine-grained permission management for Redis Cloud resources.
366pub struct AclHandler {
367    client: CloudClient,
368}
369
370impl AclHandler {
371    /// Create a new handler
372    pub fn new(client: CloudClient) -> Self {
373        Self { client }
374    }
375
376    /// Get Redis ACL rules
377    /// Gets a list of all Redis ACL rules for this account.
378    ///
379    /// GET /acl/redisRules
380    pub async fn get_all_redis_rules(&self) -> Result<AccountACLRedisRules> {
381        self.client.get("/acl/redisRules").await
382    }
383
384    /// Create Redis ACL rule
385    /// Creates a new Redis ACL rule.
386    ///
387    /// POST /acl/redisRules
388    pub async fn create_redis_rule(
389        &self,
390        request: &AclRedisRuleCreateRequest,
391    ) -> Result<TaskStateUpdate> {
392        self.client.post("/acl/redisRules", request).await
393    }
394
395    /// Delete Redis ACL rule
396    /// Deletes a Redis ACL rule.
397    ///
398    /// DELETE /acl/redisRules/{aclRedisRuleId}
399    pub async fn delete_redis_rule(&self, acl_redis_rule_id: i32) -> Result<TaskStateUpdate> {
400        let response = self
401            .client
402            .delete_raw(&format!("/acl/redisRules/{}", acl_redis_rule_id))
403            .await?;
404        serde_json::from_value(response).map_err(Into::into)
405    }
406
407    /// Update Redis ACL rule
408    /// Updates a Redis ACL rule.
409    ///
410    /// PUT /acl/redisRules/{aclRedisRuleId}
411    pub async fn update_redis_rule(
412        &self,
413        acl_redis_rule_id: i32,
414        request: &AclRedisRuleUpdateRequest,
415    ) -> Result<TaskStateUpdate> {
416        self.client
417            .put(&format!("/acl/redisRules/{}", acl_redis_rule_id), request)
418            .await
419    }
420
421    /// Get database access roles
422    /// Gets a list of all database access roles for this account.
423    ///
424    /// GET /acl/roles
425    pub async fn get_roles(&self) -> Result<AccountACLRoles> {
426        self.client.get("/acl/roles").await
427    }
428
429    /// Create database access role
430    /// Creates a new database access role with the assigned permissions and associates it with the provided databases.
431    ///
432    /// POST /acl/roles
433    pub async fn create_role(&self, request: &AclRoleCreateRequest) -> Result<TaskStateUpdate> {
434        self.client.post("/acl/roles", request).await
435    }
436
437    /// Delete database access role
438    /// Deletes a database access role.
439    ///
440    /// DELETE /acl/roles/{aclRoleId}
441    pub async fn delete_acl_role(&self, acl_role_id: i32) -> Result<TaskStateUpdate> {
442        let response = self
443            .client
444            .delete_raw(&format!("/acl/roles/{}", acl_role_id))
445            .await?;
446        serde_json::from_value(response).map_err(Into::into)
447    }
448
449    /// Update database access role
450    /// Updates a database access role with new assigned permissions or associated databases.
451    ///
452    /// PUT /acl/roles/{aclRoleId}
453    pub async fn update_role(
454        &self,
455        acl_role_id: i32,
456        request: &AclRoleUpdateRequest,
457    ) -> Result<TaskStateUpdate> {
458        self.client
459            .put(&format!("/acl/roles/{}", acl_role_id), request)
460            .await
461    }
462
463    /// Get access control users
464    /// Gets a list of all access control users for this account.
465    ///
466    /// GET /acl/users
467    pub async fn get_all_acl_users(&self) -> Result<AccountACLUsers> {
468        self.client.get("/acl/users").await
469    }
470
471    /// Create access control user
472    /// Creates a new access control user with the assigned database access role.
473    ///
474    /// POST /acl/users
475    pub async fn create_user(&self, request: &AclUserCreateRequest) -> Result<TaskStateUpdate> {
476        self.client.post("/acl/users", request).await
477    }
478
479    /// Delete access control user
480    /// Deletes a access control user.
481    ///
482    /// DELETE /acl/users/{aclUserId}
483    pub async fn delete_user(&self, acl_user_id: i32) -> Result<TaskStateUpdate> {
484        let response = self
485            .client
486            .delete_raw(&format!("/acl/users/{}", acl_user_id))
487            .await?;
488        serde_json::from_value(response).map_err(Into::into)
489    }
490
491    /// Get a single access control user
492    /// Gets details and settings for single access control user.
493    ///
494    /// GET /acl/users/{aclUserId}
495    pub async fn get_user_by_id(&self, acl_user_id: i32) -> Result<ACLUser> {
496        self.client
497            .get(&format!("/acl/users/{}", acl_user_id))
498            .await
499    }
500
501    /// Update access control user
502    /// Updates a access control user with a different role or database password.
503    ///
504    /// PUT /acl/users/{aclUserId}
505    pub async fn update_acl_user(
506        &self,
507        acl_user_id: i32,
508        request: &AclUserUpdateRequest,
509    ) -> Result<TaskStateUpdate> {
510        self.client
511            .put(&format!("/acl/users/{}", acl_user_id), request)
512            .await
513    }
514}