Skip to main content

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::types::Link;
44pub use crate::types::TaskStateUpdate;
45use crate::{CloudClient, Result};
46use serde::{Deserialize, Serialize};
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    /// Read-only on the response; populated by the server with the
63    /// operation type.
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub command_type: Option<String>,
66}
67
68/// ACL user update request
69#[derive(Debug, Clone, Serialize, Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub struct AclUserUpdateRequest {
72    /// ACL user ID being updated. Server-populated from the path.
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub user_id: Option<i32>,
75
76    /// Optional. Changes the ACL role assigned to the user. Use GET '/acl/roles' to get a list of database access roles.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub role: Option<String>,
79
80    /// Optional. Changes the user's database password.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub password: Option<String>,
83
84    /// Read-only on the response; populated by the server with the
85    /// operation type.
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub command_type: Option<String>,
88}
89
90/// ACL users response
91///
92/// Response from GET /acl/users
93#[derive(Debug, Clone, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct AccountACLUsers {
96    /// Account ID
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub account_id: Option<i32>,
99
100    /// List of ACL users
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub users: Option<Vec<ACLUser>>,
103
104    /// HATEOAS links for API navigation
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub links: Option<Vec<Link>>,
107}
108
109/// ACL Redis rules response
110///
111/// Response from GET /acl/redisRules
112#[derive(Debug, Clone, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct AccountACLRedisRules {
115    /// Account ID
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub account_id: Option<i32>,
118
119    /// List of Redis ACL rules
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub redis_rules: Option<Vec<ACLRedisRule>>,
122
123    /// HATEOAS links for API navigation
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub links: Option<Vec<Link>>,
126}
127
128/// ACL Redis rule
129#[derive(Debug, Clone, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct ACLRedisRule {
132    /// Rule ID
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub id: Option<i32>,
135
136    /// Rule name
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub name: Option<String>,
139
140    /// ACL pattern (e.g., "+@all ~lcm:*")
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub acl: Option<String>,
143
144    /// Whether this is a default rule
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub is_default: Option<bool>,
147
148    /// Rule status (e.g., "active")
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub status: Option<String>,
151}
152
153/// ACL Redis rule create request
154#[derive(Debug, Clone, Serialize, Deserialize)]
155#[serde(rename_all = "camelCase")]
156pub struct AclRedisRuleCreateRequest {
157    /// Redis ACL rule name.
158    pub name: String,
159
160    /// 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.
161    pub redis_rule: String,
162
163    /// Read-only on the response; populated by the server with the
164    /// operation type.
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub command_type: Option<String>,
167}
168
169/// ACL roles response
170///
171/// Response from GET /acl/roles
172#[derive(Debug, Clone, Serialize, Deserialize)]
173#[serde(rename_all = "camelCase")]
174pub struct AccountACLRoles {
175    /// Account ID
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub account_id: Option<i32>,
178
179    /// List of ACL roles
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub roles: Option<Vec<ACLRole>>,
182
183    /// HATEOAS links for API navigation
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub links: Option<Vec<Link>>,
186}
187
188/// ACL role
189#[derive(Debug, Clone, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct ACLRole {
192    /// Role ID
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub id: Option<i32>,
195
196    /// Role name
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub name: Option<String>,
199
200    /// Redis rules associated with this role
201    ///
202    /// Note: These use different field names (ruleId, ruleName) than standalone redis rules
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub redis_rules: Option<Vec<ACLRoleRedisRule>>,
205
206    /// Users assigned to this role
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub users: Option<Vec<ACLRoleUser>>,
209
210    /// Role status (e.g., "active")
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub status: Option<String>,
213}
214
215/// User reference in an ACL role
216#[derive(Debug, Clone, Serialize, Deserialize)]
217#[serde(rename_all = "camelCase")]
218pub struct ACLRoleUser {
219    /// User ID
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub id: Option<i32>,
222
223    /// User name
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub name: Option<String>,
226}
227
228/// Redis rule as embedded in an ACL role response
229///
230/// This has different field names than `ACLRedisRule` because the API
231/// uses different JSON keys when rules are embedded in role responses.
232#[derive(Debug, Clone, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234pub struct ACLRoleRedisRule {
235    /// Rule ID
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub rule_id: Option<i32>,
238
239    /// Rule name
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub rule_name: Option<String>,
242
243    /// Databases this rule applies to within the role
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub databases: Option<Vec<ACLRoleDatabase>>,
246}
247
248/// Database reference in an ACL role's redis rule
249#[derive(Debug, Clone, Serialize, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct ACLRoleDatabase {
252    /// Subscription ID
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub subscription_id: Option<i32>,
255
256    /// Database ID
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub database_id: Option<i32>,
259
260    /// Database name
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub database_name: Option<String>,
263
264    /// Regions (for Active-Active databases)
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub regions: Option<Vec<String>>,
267}
268
269/// ACL Redis rule update request
270#[derive(Debug, Clone, Serialize, Deserialize)]
271#[serde(rename_all = "camelCase")]
272pub struct AclRedisRuleUpdateRequest {
273    /// Redis ACL rule ID being updated. Server-populated from the path.
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub redis_rule_id: Option<i32>,
276
277    /// Optional. Changes the Redis ACL rule name.
278    pub name: String,
279
280    /// 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.
281    pub redis_rule: String,
282
283    /// Read-only on the response; populated by the server with the
284    /// operation type.
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub command_type: Option<String>,
287}
288
289/// Database specification for ACL role assignment
290#[derive(Debug, Clone, Serialize, Deserialize)]
291#[serde(rename_all = "camelCase")]
292pub struct AclRoleDatabaseSpec {
293    /// Subscription ID for the database's subscription. Use 'GET /subscriptions' or 'GET /fixed/subscriptions' to get a list of available subscriptions and their IDs.
294    pub subscription_id: i32,
295
296    /// 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.
297    pub database_id: i32,
298
299    /// (Active-Active databases only) Optional. A list of regions where this rule applies for this role.
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub regions: Option<Vec<String>>,
302}
303
304/// ACL user create request
305#[derive(Debug, Clone, Serialize, Deserialize)]
306#[serde(rename_all = "camelCase")]
307pub struct AclUserCreateRequest {
308    /// Access control user name.
309    pub name: String,
310
311    /// Name of the database access role to assign to this user. Use GET '/acl/roles' to get a list of database access roles.
312    pub role: String,
313
314    /// The database password for this user.
315    pub password: String,
316
317    /// Read-only on the response; populated by the server with the
318    /// operation type.
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub command_type: Option<String>,
321}
322
323/// ACL user information
324#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct ACLUser {
326    /// User ID
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub id: Option<i32>,
329
330    /// User name
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub name: Option<String>,
333
334    /// Assigned role name
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub role: Option<String>,
337
338    /// User status (e.g., "active", "error")
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub status: Option<String>,
341
342    /// HATEOAS links
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub links: Option<Vec<Link>>,
345}
346
347/// ACL role update request
348#[derive(Debug, Clone, Serialize, Deserialize)]
349#[serde(rename_all = "camelCase")]
350pub struct AclRoleUpdateRequest {
351    /// Optional. Changes the database access role name.
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub name: Option<String>,
354
355    /// Optional. Changes the Redis ACL rules to assign to this database access role.
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub redis_rules: Option<Vec<AclRoleRedisRuleSpec>>,
358
359    /// ACL role ID being updated. Server-populated from the path.
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub role_id: Option<i32>,
362
363    /// Read-only on the response; populated by the server with the
364    /// operation type.
365    #[serde(skip_serializing_if = "Option::is_none")]
366    pub command_type: Option<String>,
367}
368
369/// Redis rule specification for role assignment
370#[derive(Debug, Clone, Serialize, Deserialize)]
371#[serde(rename_all = "camelCase")]
372pub struct AclRoleRedisRuleSpec {
373    /// 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.
374    pub rule_name: String,
375
376    /// A list of databases where the specified rule applies for this role.
377    pub databases: Vec<AclRoleDatabaseSpec>,
378}
379
380// ============================================================================
381// Handler
382// ============================================================================
383
384/// Handler for Role-based Access Control (RBAC) operations
385///
386/// Manages ACLs for users, roles, Redis rules, and database-level access controls.
387/// Provides fine-grained permission management for Redis Cloud resources.
388pub struct AclHandler {
389    client: CloudClient,
390}
391
392impl AclHandler {
393    /// Create a new handler
394    #[must_use]
395    pub fn new(client: CloudClient) -> Self {
396        Self { client }
397    }
398
399    /// Get Redis ACL rules
400    /// Gets a list of all Redis ACL rules for this account.
401    ///
402    /// GET /acl/redisRules
403    ///
404    /// # Example
405    ///
406    /// ```no_run
407    /// use redis_cloud::CloudClient;
408    ///
409    /// # async fn example() -> redis_cloud::Result<()> {
410    /// let client = CloudClient::builder()
411    ///     .api_key("your-api-key")
412    ///     .api_secret("your-api-secret")
413    ///     .build()?;
414    ///
415    /// let rules = client.acl().get_all_redis_rules().await?;
416    /// # Ok(())
417    /// # }
418    /// ```
419    pub async fn get_all_redis_rules(&self) -> Result<AccountACLRedisRules> {
420        self.client.get("/acl/redisRules").await
421    }
422
423    /// Create Redis ACL rule
424    /// Creates a new Redis ACL rule.
425    ///
426    /// POST /acl/redisRules
427    pub async fn create_redis_rule(
428        &self,
429        request: &AclRedisRuleCreateRequest,
430    ) -> Result<TaskStateUpdate> {
431        self.client.post("/acl/redisRules", request).await
432    }
433
434    /// Delete Redis ACL rule
435    /// Deletes a Redis ACL rule.
436    ///
437    /// DELETE /acl/redisRules/{aclRedisRuleId}
438    pub async fn delete_redis_rule(&self, acl_redis_rule_id: i32) -> Result<TaskStateUpdate> {
439        let response = self
440            .client
441            .delete_raw(&format!("/acl/redisRules/{acl_redis_rule_id}"))
442            .await?;
443        serde_json::from_value(response).map_err(Into::into)
444    }
445
446    /// Update Redis ACL rule
447    /// Updates a Redis ACL rule.
448    ///
449    /// PUT /acl/redisRules/{aclRedisRuleId}
450    pub async fn update_redis_rule(
451        &self,
452        acl_redis_rule_id: i32,
453        request: &AclRedisRuleUpdateRequest,
454    ) -> Result<TaskStateUpdate> {
455        self.client
456            .put(&format!("/acl/redisRules/{acl_redis_rule_id}"), request)
457            .await
458    }
459
460    /// Get database access roles
461    /// Gets a list of all database access roles for this account.
462    ///
463    /// GET /acl/roles
464    ///
465    /// # Example
466    ///
467    /// ```no_run
468    /// use redis_cloud::CloudClient;
469    ///
470    /// # async fn example() -> redis_cloud::Result<()> {
471    /// let client = CloudClient::builder()
472    ///     .api_key("your-api-key")
473    ///     .api_secret("your-api-secret")
474    ///     .build()?;
475    ///
476    /// let roles = client.acl().get_roles().await?;
477    /// # Ok(())
478    /// # }
479    /// ```
480    pub async fn get_roles(&self) -> Result<AccountACLRoles> {
481        self.client.get("/acl/roles").await
482    }
483
484    /// Create database access role
485    /// Creates a new database access role with the assigned permissions and associates it with the provided databases.
486    ///
487    /// POST /acl/roles
488    pub async fn create_role(&self, request: &AclRoleCreateRequest) -> Result<TaskStateUpdate> {
489        self.client.post("/acl/roles", request).await
490    }
491
492    /// Delete database access role
493    /// Deletes a database access role.
494    ///
495    /// DELETE /acl/roles/{aclRoleId}
496    pub async fn delete_acl_role(&self, acl_role_id: i32) -> Result<TaskStateUpdate> {
497        let response = self
498            .client
499            .delete_raw(&format!("/acl/roles/{acl_role_id}"))
500            .await?;
501        serde_json::from_value(response).map_err(Into::into)
502    }
503
504    /// Update database access role
505    /// Updates a database access role with new assigned permissions or associated databases.
506    ///
507    /// PUT /acl/roles/{aclRoleId}
508    pub async fn update_role(
509        &self,
510        acl_role_id: i32,
511        request: &AclRoleUpdateRequest,
512    ) -> Result<TaskStateUpdate> {
513        self.client
514            .put(&format!("/acl/roles/{acl_role_id}"), request)
515            .await
516    }
517
518    /// Get access control users
519    /// Gets a list of all access control users for this account.
520    ///
521    /// GET /acl/users
522    pub async fn get_all_acl_users(&self) -> Result<AccountACLUsers> {
523        self.client.get("/acl/users").await
524    }
525
526    /// Create access control user
527    /// Creates a new access control user with the assigned database access role.
528    ///
529    /// POST /acl/users
530    pub async fn create_user(&self, request: &AclUserCreateRequest) -> Result<TaskStateUpdate> {
531        self.client.post("/acl/users", request).await
532    }
533
534    /// Delete access control user
535    /// Deletes a access control user.
536    ///
537    /// DELETE /acl/users/{aclUserId}
538    pub async fn delete_user(&self, acl_user_id: i32) -> Result<TaskStateUpdate> {
539        let response = self
540            .client
541            .delete_raw(&format!("/acl/users/{acl_user_id}"))
542            .await?;
543        serde_json::from_value(response).map_err(Into::into)
544    }
545
546    /// Get a single access control user
547    /// Gets details and settings for single access control user.
548    ///
549    /// GET /acl/users/{aclUserId}
550    pub async fn get_user_by_id(&self, acl_user_id: i32) -> Result<ACLUser> {
551        self.client.get(&format!("/acl/users/{acl_user_id}")).await
552    }
553
554    /// Update access control user
555    /// Updates a access control user with a different role or database password.
556    ///
557    /// PUT /acl/users/{aclUserId}
558    pub async fn update_acl_user(
559        &self,
560        acl_user_id: i32,
561        request: &AclUserUpdateRequest,
562    ) -> Result<TaskStateUpdate> {
563        self.client
564            .put(&format!("/acl/users/{acl_user_id}"), request)
565            .await
566    }
567
568    // ============================================================================
569    // Simplified aliases
570    // ============================================================================
571
572    /// List Redis ACL rules (simplified)
573    ///
574    /// Alias for [`get_all_redis_rules`](Self::get_all_redis_rules).
575    ///
576    /// # Example
577    ///
578    /// ```no_run
579    /// use redis_cloud::CloudClient;
580    ///
581    /// # async fn example() -> redis_cloud::Result<()> {
582    /// let client = CloudClient::builder()
583    ///     .api_key("your-api-key")
584    ///     .api_secret("your-api-secret")
585    ///     .build()?;
586    ///
587    /// let rules = client.acl().list_redis_rules().await?;
588    /// # Ok(())
589    /// # }
590    /// ```
591    pub async fn list_redis_rules(&self) -> Result<AccountACLRedisRules> {
592        self.get_all_redis_rules().await
593    }
594
595    /// List database access roles (simplified)
596    ///
597    /// Alias for [`get_roles`](Self::get_roles).
598    ///
599    /// # Example
600    ///
601    /// ```no_run
602    /// use redis_cloud::CloudClient;
603    ///
604    /// # async fn example() -> redis_cloud::Result<()> {
605    /// let client = CloudClient::builder()
606    ///     .api_key("your-api-key")
607    ///     .api_secret("your-api-secret")
608    ///     .build()?;
609    ///
610    /// let roles = client.acl().list_roles().await?;
611    /// # Ok(())
612    /// # }
613    /// ```
614    pub async fn list_roles(&self) -> Result<AccountACLRoles> {
615        self.get_roles().await
616    }
617
618    /// List access control users (simplified)
619    ///
620    /// Alias for [`get_all_acl_users`](Self::get_all_acl_users).
621    ///
622    /// # Example
623    ///
624    /// ```no_run
625    /// use redis_cloud::CloudClient;
626    ///
627    /// # async fn example() -> redis_cloud::Result<()> {
628    /// let client = CloudClient::builder()
629    ///     .api_key("your-api-key")
630    ///     .api_secret("your-api-secret")
631    ///     .build()?;
632    ///
633    /// let users = client.acl().list_acl_users().await?;
634    /// # Ok(())
635    /// # }
636    /// ```
637    pub async fn list_acl_users(&self) -> Result<AccountACLUsers> {
638        self.get_all_acl_users().await
639    }
640
641    /// Get a single access control user (simplified)
642    ///
643    /// Alias for [`get_user_by_id`](Self::get_user_by_id).
644    ///
645    /// # Arguments
646    ///
647    /// * `acl_user_id` - The access control user ID
648    ///
649    /// # Example
650    ///
651    /// ```no_run
652    /// use redis_cloud::CloudClient;
653    ///
654    /// # async fn example() -> redis_cloud::Result<()> {
655    /// let client = CloudClient::builder()
656    ///     .api_key("your-api-key")
657    ///     .api_secret("your-api-secret")
658    ///     .build()?;
659    ///
660    /// let user = client.acl().get_acl_user(123).await?;
661    /// # Ok(())
662    /// # }
663    /// ```
664    pub async fn get_acl_user(&self, acl_user_id: i32) -> Result<ACLUser> {
665        self.get_user_by_id(acl_user_id).await
666    }
667}