mod redis_acl;
#[cfg(feature = "postgres")]
mod postgres_acl;
pub use redis_acl::RedisAclManager;
#[cfg(feature = "postgres")]
pub use postgres_acl::PostgresAclManager;
use crate::error::Result;
use async_trait::async_trait;
use redis::acl::Rule;
#[derive(Debug, Clone, Default)]
pub struct NodeConfig {
pub addr: String,
pub username: String,
pub password: String,
pub db: i32,
}
impl NodeConfig {
pub fn new(addr: &str, username: &str, password: &str, db: i32) -> Self {
Self {
addr: addr.to_string(),
username: username.to_string(),
password: password.to_string(),
db,
}
}
pub fn acl_prefix(&self) -> &str {
&self.username
}
pub fn acl_prefix_with_colon(&self) -> String {
format!("{}:", self.username)
}
pub fn asynq_key_pattern(&self) -> Rule {
Rule::Pattern(format!("asynq:{{{}:*", self.username))
}
}
impl std::fmt::Display for NodeConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:", self.username)
}
}
#[derive(Debug, Clone, Default)]
pub struct AclConfig {
pub enabled: bool,
pub node_config: NodeConfig,
pub write_only_keys: Vec<String>,
}
impl AclConfig {
pub fn new(node_config: NodeConfig) -> Self {
Self {
enabled: true,
node_config,
write_only_keys: Vec::new(),
}
}
pub fn enable(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn add_write_only_key<S: Into<String>>(mut self, key: S) -> Self {
self.write_only_keys.push(key.into());
self
}
pub fn write_only_keys(mut self, keys: Vec<String>) -> Self {
self.write_only_keys = keys;
self
}
pub fn default_key_patterns(tenant: &str) -> Vec<Rule> {
vec![
Rule::Pattern(crate::base::keys::ALL_QUEUES.to_string()),
Rule::Pattern(format!(
"{}{{{}:*",
crate::base::keys::SERVERS_PREFIX,
tenant
)),
Rule::Pattern(crate::base::keys::ALL_SERVERS.to_string()),
Rule::Pattern(crate::base::keys::ALL_WORKERS.to_string()),
Rule::Pattern(format!(
"{}{{{}:*",
crate::base::keys::WORKERS_PREFIX,
tenant
)),
Rule::Pattern(crate::base::keys::ALL_SCHEDULERS.to_string()),
Rule::Pattern(format!(
"{}{{{}:*",
crate::base::keys::SCHEDULED_PREFIX,
tenant
)),
Rule::Channel(crate::base::keys::CANCEL_CHANNEL.to_string()),
]
}
}
#[derive(Debug, Clone)]
pub struct AclCommand {
pub args: Vec<String>,
}
impl AclCommand {
pub fn to_command_string(&self) -> String {
self.args.join(" ")
}
pub fn get_args(&self) -> &[String] {
&self.args
}
}
#[async_trait]
pub trait AclManager: Send + Sync {
async fn create_tenant_user(&self, config: &AclConfig) -> Result<()>;
async fn delete_tenant_user(&self, username: &str) -> Result<()>;
async fn list_tenant_users(&self) -> Result<Vec<String>>;
async fn tenant_user_exists(&self, username: &str) -> Result<bool>;
async fn update_tenant_user(&self, config: &AclConfig) -> Result<()>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_config_creation() {
let config = NodeConfig::new("localhost:6379", "tenant1", "pass123", 0);
assert_eq!(config.addr, "localhost:6379");
assert_eq!(config.username, "tenant1");
assert_eq!(config.password, "pass123");
assert_eq!(config.db, 0);
}
#[test]
fn test_node_config_acl_prefix() {
let config = NodeConfig::new("localhost:6379", "tenant1", "pass123", 0);
assert_eq!(config.acl_prefix(), "tenant1");
}
#[test]
fn test_node_config_acl_prefix_with_colon() {
let config = NodeConfig::new("localhost:6379", "tenant1", "pass123", 0);
assert_eq!(config.acl_prefix_with_colon(), "tenant1:");
}
#[test]
fn test_node_config_asynq_key_pattern() {
let config = NodeConfig::new("localhost:6379", "tenant1", "pass123", 0);
assert_eq!(
config.asynq_key_pattern(),
Rule::Pattern("asynq:{tenant1:*".to_string())
);
}
#[test]
fn test_node_config_display() {
let config = NodeConfig::new("localhost:6379", "tenant1", "pass123", 0);
assert_eq!(config.to_string(), "tenant1:");
}
#[test]
fn test_acl_config_creation() {
let node_config = NodeConfig::new("localhost:6379", "tenant1", "pass123", 0);
let acl_config = AclConfig::new(node_config);
assert!(acl_config.enabled);
assert!(acl_config.write_only_keys.is_empty());
}
#[test]
fn test_acl_config_builder() {
let node_config = NodeConfig::new("localhost:6379", "tenant1", "pass123", 0);
let acl_config = AclConfig::new(node_config)
.enable(true)
.add_write_only_key("test:result:*");
assert!(acl_config.enabled);
assert_eq!(acl_config.write_only_keys.len(), 1);
assert_eq!(acl_config.write_only_keys[0], "test:result:*");
}
#[test]
fn test_acl_config_disable() {
let node_config = NodeConfig::new("localhost:6379", "tenant1", "pass123", 0);
let acl_config = AclConfig::new(node_config).enable(false);
assert!(!acl_config.enabled);
}
#[test]
fn test_acl_command_to_string() {
let cmd = AclCommand {
args: vec![
"ACL".to_string(),
"SETUSER".to_string(),
"user1".to_string(),
"on".to_string(),
],
};
assert_eq!(cmd.to_command_string(), "ACL SETUSER user1 on");
}
}