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#[derive(Debug, Clone, Serialize, Deserialize)]
119#[serde(rename_all = "camelCase")]
120pub struct AccountACLUsers {
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub account_id: Option<i32>,
123
124    /// HATEOAS links
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub links: Option<Vec<HashMap<String, Value>>>,
127
128    /// Additional fields from the API
129    #[serde(flatten)]
130    pub extra: Value,
131}
132
133/// Redis list of ACL redis rules in current account
134#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(rename_all = "camelCase")]
136pub struct AccountACLRedisRules {
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub account_id: Option<i32>,
139
140    /// HATEOAS links
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub links: Option<Vec<HashMap<String, Value>>>,
143
144    /// Additional fields from the API
145    #[serde(flatten)]
146    pub extra: Value,
147}
148
149/// ACL redis rule create request
150#[derive(Debug, Clone, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct AclRedisRuleCreateRequest {
153    /// Redis ACL rule name.
154    pub name: String,
155
156    /// 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.
157    pub redis_rule: String,
158
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub command_type: Option<String>,
161
162    /// Additional fields from the API
163    #[serde(flatten)]
164    pub extra: Value,
165}
166
167/// Redis list of ACL roles in current account
168#[derive(Debug, Clone, Serialize, Deserialize)]
169#[serde(rename_all = "camelCase")]
170pub struct AccountACLRoles {
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub account_id: Option<i32>,
173
174    /// HATEOAS links
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub links: Option<Vec<HashMap<String, Value>>>,
177
178    /// Additional fields from the API
179    #[serde(flatten)]
180    pub extra: Value,
181}
182
183/// ACL redis rule update request
184#[derive(Debug, Clone, Serialize, Deserialize)]
185#[serde(rename_all = "camelCase")]
186pub struct AclRedisRuleUpdateRequest {
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub redis_rule_id: Option<i32>,
189
190    /// Optional. Changes the Redis ACL rule name.
191    pub name: String,
192
193    /// 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.
194    pub redis_rule: String,
195
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub command_type: Option<String>,
198
199    /// Additional fields from the API
200    #[serde(flatten)]
201    pub extra: Value,
202}
203
204/// A list of databases where the specified rule applies for this role.
205#[derive(Debug, Clone, Serialize, Deserialize)]
206#[serde(rename_all = "camelCase")]
207pub struct AclRoleDatabaseSpec {
208    /// Subscription ID for the database's subscription. Use 'GET /subscriptions' or 'GET /fixed/subscriptions' to get a list of available subscriptions and their IDs.
209    pub subscription_id: i32,
210
211    /// 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.
212    pub database_id: i32,
213
214    /// (Active-Active databases only) Optional. A list of regions where this rule applies for this role.
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub regions: Option<Vec<String>>,
217
218    /// Additional fields from the API
219    #[serde(flatten)]
220    pub extra: Value,
221}
222
223/// ACL user create request
224#[derive(Debug, Clone, Serialize, Deserialize)]
225#[serde(rename_all = "camelCase")]
226pub struct AclUserCreateRequest {
227    /// Access control user name.
228    pub name: String,
229
230    /// Name of the database access role to assign to this user. Use GET '/acl/roles' to get a list of database access roles.
231    pub role: String,
232
233    /// The database password for this user.
234    pub password: String,
235
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub command_type: Option<String>,
238
239    /// Additional fields from the API
240    #[serde(flatten)]
241    pub extra: Value,
242}
243
244/// Redis ACL user information
245#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct ACLUser {
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub id: Option<i32>,
249
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub name: Option<String>,
252
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub role: Option<String>,
255
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub status: Option<String>,
258
259    /// HATEOAS links
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub links: Option<Vec<HashMap<String, Value>>>,
262
263    /// Additional fields from the API
264    #[serde(flatten)]
265    pub extra: Value,
266}
267
268/// ACL role update request
269#[derive(Debug, Clone, Serialize, Deserialize)]
270#[serde(rename_all = "camelCase")]
271pub struct AclRoleUpdateRequest {
272    /// Optional. Changes the database access role name.
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub name: Option<String>,
275
276    /// Optional. Changes the Redis ACL rules to assign to this database access role.
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub redis_rules: Option<Vec<AclRoleRedisRuleSpec>>,
279
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub role_id: Option<i32>,
282
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub command_type: Option<String>,
285
286    /// Additional fields from the API
287    #[serde(flatten)]
288    pub extra: Value,
289}
290
291/// Optional. Changes the Redis ACL rules to assign to this database access role.
292#[derive(Debug, Clone, Serialize, Deserialize)]
293#[serde(rename_all = "camelCase")]
294pub struct AclRoleRedisRuleSpec {
295    /// 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.
296    pub rule_name: String,
297
298    /// A list of databases where the specified rule applies for this role.
299    pub databases: Vec<AclRoleDatabaseSpec>,
300
301    /// Additional fields from the API
302    #[serde(flatten)]
303    pub extra: Value,
304}
305
306/// TaskStateUpdate
307#[derive(Debug, Clone, Serialize, Deserialize)]
308#[serde(rename_all = "camelCase")]
309pub struct TaskStateUpdate {
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub task_id: Option<String>,
312
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub command_type: Option<String>,
315
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub status: Option<String>,
318
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub description: Option<String>,
321
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub timestamp: Option<String>,
324
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub response: Option<ProcessorResponse>,
327
328    /// HATEOAS links
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub links: Option<Vec<HashMap<String, Value>>>,
331
332    /// Additional fields from the API
333    #[serde(flatten)]
334    pub extra: Value,
335}
336
337// ============================================================================
338// Handler
339// ============================================================================
340
341/// Handler for Role-based Access Control (RBAC) operations
342///
343/// Manages ACLs for users, roles, Redis rules, and database-level access controls.
344/// Provides fine-grained permission management for Redis Cloud resources.
345pub struct AclHandler {
346    client: CloudClient,
347}
348
349impl AclHandler {
350    /// Create a new handler
351    pub fn new(client: CloudClient) -> Self {
352        Self { client }
353    }
354
355    /// Get Redis ACL rules
356    /// Gets a list of all Redis ACL rules for this account.
357    ///
358    /// GET /acl/redisRules
359    pub async fn get_all_redis_rules(&self) -> Result<AccountACLRedisRules> {
360        self.client.get("/acl/redisRules").await
361    }
362
363    /// Create Redis ACL rule
364    /// Creates a new Redis ACL rule.
365    ///
366    /// POST /acl/redisRules
367    pub async fn create_redis_rule(
368        &self,
369        request: &AclRedisRuleCreateRequest,
370    ) -> Result<TaskStateUpdate> {
371        self.client.post("/acl/redisRules", request).await
372    }
373
374    /// Delete Redis ACL rule
375    /// Deletes a Redis ACL rule.
376    ///
377    /// DELETE /acl/redisRules/{aclRedisRuleId}
378    pub async fn delete_redis_rule(&self, acl_redis_rule_id: i32) -> Result<TaskStateUpdate> {
379        let response = self
380            .client
381            .delete_raw(&format!("/acl/redisRules/{}", acl_redis_rule_id))
382            .await?;
383        serde_json::from_value(response).map_err(Into::into)
384    }
385
386    /// Update Redis ACL rule
387    /// Updates a Redis ACL rule.
388    ///
389    /// PUT /acl/redisRules/{aclRedisRuleId}
390    pub async fn update_redis_rule(
391        &self,
392        acl_redis_rule_id: i32,
393        request: &AclRedisRuleUpdateRequest,
394    ) -> Result<TaskStateUpdate> {
395        self.client
396            .put(&format!("/acl/redisRules/{}", acl_redis_rule_id), request)
397            .await
398    }
399
400    /// Get database access roles
401    /// Gets a list of all database access roles for this account.
402    ///
403    /// GET /acl/roles
404    pub async fn get_roles(&self) -> Result<AccountACLRoles> {
405        self.client.get("/acl/roles").await
406    }
407
408    /// Create database access role
409    /// Creates a new database access role with the assigned permissions and associates it with the provided databases.
410    ///
411    /// POST /acl/roles
412    pub async fn create_role(&self, request: &AclRoleCreateRequest) -> Result<TaskStateUpdate> {
413        self.client.post("/acl/roles", request).await
414    }
415
416    /// Delete database access role
417    /// Deletes a database access role.
418    ///
419    /// DELETE /acl/roles/{aclRoleId}
420    pub async fn delete_acl_role(&self, acl_role_id: i32) -> Result<TaskStateUpdate> {
421        let response = self
422            .client
423            .delete_raw(&format!("/acl/roles/{}", acl_role_id))
424            .await?;
425        serde_json::from_value(response).map_err(Into::into)
426    }
427
428    /// Update database access role
429    /// Updates a database access role with new assigned permissions or associated databases.
430    ///
431    /// PUT /acl/roles/{aclRoleId}
432    pub async fn update_role(
433        &self,
434        acl_role_id: i32,
435        request: &AclRoleUpdateRequest,
436    ) -> Result<TaskStateUpdate> {
437        self.client
438            .put(&format!("/acl/roles/{}", acl_role_id), request)
439            .await
440    }
441
442    /// Get access control users
443    /// Gets a list of all access control users for this account.
444    ///
445    /// GET /acl/users
446    pub async fn get_all_acl_users(&self) -> Result<AccountACLUsers> {
447        self.client.get("/acl/users").await
448    }
449
450    /// Create access control user
451    /// Creates a new access control user with the assigned database access role.
452    ///
453    /// POST /acl/users
454    pub async fn create_user(&self, request: &AclUserCreateRequest) -> Result<TaskStateUpdate> {
455        self.client.post("/acl/users", request).await
456    }
457
458    /// Delete access control user
459    /// Deletes a access control user.
460    ///
461    /// DELETE /acl/users/{aclUserId}
462    pub async fn delete_user(&self, acl_user_id: i32) -> Result<TaskStateUpdate> {
463        let response = self
464            .client
465            .delete_raw(&format!("/acl/users/{}", acl_user_id))
466            .await?;
467        serde_json::from_value(response).map_err(Into::into)
468    }
469
470    /// Get a single access control user
471    /// Gets details and settings for single access control user.
472    ///
473    /// GET /acl/users/{aclUserId}
474    pub async fn get_user_by_id(&self, acl_user_id: i32) -> Result<ACLUser> {
475        self.client
476            .get(&format!("/acl/users/{}", acl_user_id))
477            .await
478    }
479
480    /// Update access control user
481    /// Updates a access control user with a different role or database password.
482    ///
483    /// PUT /acl/users/{aclUserId}
484    pub async fn update_acl_user(
485        &self,
486        acl_user_id: i32,
487        request: &AclUserUpdateRequest,
488    ) -> Result<TaskStateUpdate> {
489        self.client
490            .put(&format!("/acl/users/{}", acl_user_id), request)
491            .await
492    }
493}