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}