use std::sync::Arc;
use super::{
cmd_strings as cs,
cmd_strings::{
abort_with_error_message, abort_with_wrong_number_of_arguments, write_error_raw,
write_map_len_resp2, write_raw,
},
parser::resp_ext::{RespSliceExt, RespVecExt},
resp_server_session::RespServerSession,
};
use crate::{
acl::{AccessControlList, AclError, AclParser, User, UserHandle},
auth::{
GarnetAclAuthenticator, settings::acl_authentication_settings::AclAuthenticationSettings,
},
};
const RESP_ERR_ACL_AUTH_DISABLED: &str = "ERR ACL Authenticator is disabled.";
const RESP_ERR_ACL_AUTH_FILE_DISABLED: &str = "ERR This Garnet instance is not configured to use an ACL file. Please restart server with --acl-file option.";
const RESP_ERR_ACL_GENPASS_BITS_RANGE: &str = "ERR ACL GENPASS argument must be the number of bits for the output password, a positive number up to 4096";
const GENPASS_DEFAULT_LENGTH: usize = 64;
const GENPASS_MAX_BITS: i64 = 4096;
pub struct AclCtx<'a> {
pub authenticator: Option<&'a GarnetAclAuthenticator>,
pub acl_settings: Option<&'a AclAuthenticationSettings>,
pub is_custom_command_registered: Option<&'a (dyn Fn(&str) -> bool + Sync)>,
}
impl RespServerSession {
fn validate_acl_authenticator(ctx: &AclCtx, output: &mut Vec<u8>) -> bool {
if ctx.authenticator.is_none() {
write_error_raw(output, RESP_ERR_ACL_AUTH_DISABLED);
return false;
}
true
}
fn validate_acl_file_use(ctx: &AclCtx, output: &mut Vec<u8>) -> bool {
let Some(settings) = ctx.acl_settings else {
write_error_raw(output, RESP_ERR_ACL_AUTH_DISABLED);
return false;
};
if settings.acl_configuration_file.is_none() {
write_error_raw(output, RESP_ERR_ACL_AUTH_FILE_DISABLED);
return false;
}
true
}
pub fn network_acl_list(
&mut self,
ctx: &AclCtx,
parse_state: &[&[u8]],
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if !parse_state.is_empty() {
abort_with_wrong_number_of_arguments(output, "acl|list");
return Ok(true);
}
if !Self::validate_acl_authenticator(ctx, output) {
return Ok(true);
}
let user_handles = ctx
.authenticator
.map(|a| a.get_access_control_list().get_user_handles())
.unwrap_or_default();
output.write_resp_array_len(user_handles.len());
for (_, user_handle) in user_handles {
let described = user_handle.user().describe_user();
output.write_resp_bulk_string(described.as_bytes());
}
Ok(true)
}
pub fn network_acl_users(
&mut self,
ctx: &AclCtx,
parse_state: &[&[u8]],
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if !parse_state.is_empty() {
abort_with_wrong_number_of_arguments(output, "acl|users");
return Ok(true);
}
if !Self::validate_acl_authenticator(ctx, output) {
return Ok(true);
}
let user_handles = ctx
.authenticator
.map(|a| a.get_access_control_list().get_user_handles())
.unwrap_or_default();
output.write_resp_array_len(user_handles.len());
for (username, _) in user_handles {
output.write_resp_bulk_string(username.as_bytes());
}
Ok(true)
}
pub fn network_acl_cat(
&mut self,
ctx: &AclCtx,
parse_state: &[&[u8]],
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if !parse_state.is_empty() {
abort_with_error_message(
output,
"ERR Unknown subcommand or wrong number of arguments for ACL CAT.",
);
return Ok(true);
}
if !Self::validate_acl_authenticator(ctx, output) {
return Ok(true);
}
let categories = AclParser::list_categories();
output.write_resp_array_len(categories.len());
for category in categories {
output.write_resp_bulk_string(category.as_bytes());
}
Ok(true)
}
pub fn network_acl_set_user(
&mut self,
ctx: &AclCtx,
parse_state: &[&[u8]],
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.is_empty() {
abort_with_wrong_number_of_arguments(output, "acl|setuser");
return Ok(true);
}
if !Self::validate_acl_authenticator(ctx, output) {
return Ok(true);
}
let Some(acl) = ctx.authenticator.map(|a| a.get_access_control_list()) else {
return Ok(true);
};
let username = parse_state[0].as_str_safe();
let result = Self::apply_set_user(acl, username, &parse_state[1..], ctx);
match result {
Ok(()) => {
write_raw(output, cs::RESP_OK);
}
Err(exception) => {
write_error_raw(output, &format!("ERR {exception}"));
}
}
Ok(true)
}
fn apply_set_user(
acl: &Arc<AccessControlList>,
username: &str,
ops: &[&[u8]],
ctx: &AclCtx<'_>,
) -> Result<(), AclError> {
let mut user_handle = acl.get_user_handle(username);
if user_handle.is_none() {
let handle = Arc::new(UserHandle::new(Arc::new(User::new(username.to_string()))));
match acl.add_user_handle(Arc::clone(&handle)) {
Ok(()) => user_handle = Some(handle),
Err(AclError::UserAlreadyExists(_)) => user_handle = acl.get_user_handle(username),
Err(e) => return Err(e),
}
}
let user_handle =
user_handle.ok_or_else(|| AclError::Acl("user handle missing after add".into()))?;
loop {
let current_user = user_handle.user();
let new_user = User::from_user(¤t_user);
let pre_allowed = current_user.custom_commands_allowed();
let pre_denied = current_user.custom_commands_denied();
for op in ops {
AclParser::apply_acl_op_to_user(&new_user, op.as_str_safe())?;
}
if let Some(is_registered) = ctx.is_custom_command_registered {
for name in new_user.custom_commands_allowed() {
if !pre_allowed.contains(&name) && !pre_denied.contains(&name) && !is_registered(&name) {
return Err(AclError::Acl(format!(
"Unknown custom command '{name}' (not registered with any loaded module)"
)));
}
}
for name in new_user.custom_commands_denied() {
if !pre_allowed.contains(&name) && !pre_denied.contains(&name) && !is_registered(&name) {
return Err(AclError::Acl(format!(
"Unknown custom command '{name}' (not registered with any loaded module)"
)));
}
}
}
if user_handle.try_set_user(Arc::new(new_user), ¤t_user) {
break;
}
}
Ok(())
}
pub fn network_acl_del_user(
&mut self,
ctx: &AclCtx,
parse_state: &[&[u8]],
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.is_empty() {
abort_with_wrong_number_of_arguments(output, "acl|deluser");
return Ok(true);
}
if !Self::validate_acl_authenticator(ctx, output) {
return Ok(true);
}
let Some(acl) = ctx.authenticator.map(|a| a.get_access_control_list()) else {
return Ok(true);
};
let mut successful_deletes: i64 = 0;
let mut error: Option<AclError> = None;
for username in parse_state {
match acl.delete_user_handle(username.as_str_safe()) {
Ok(true) => successful_deletes += 1,
Ok(false) => {}
Err(e) => {
error = Some(e);
break;
}
}
}
if let Some(exception) = error {
log::debug!("ACLException: {exception}");
write_error_raw(output, &format!("ERR {exception}"));
return Ok(true);
}
output.write_resp_int(successful_deletes);
Ok(true)
}
pub fn network_acl_who_am_i(
&mut self,
ctx: &AclCtx,
parse_state: &[&[u8]],
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if !parse_state.is_empty() {
abort_with_wrong_number_of_arguments(output, "acl|whoami");
return Ok(true);
}
if !Self::validate_acl_authenticator(ctx, output) {
return Ok(true);
}
let name = ctx
.authenticator
.and_then(|a| a.get_user_handle())
.map(|handle| handle.user().name.clone())
.unwrap_or_default();
output.write_resp_bulk_string(name.as_bytes());
Ok(true)
}
pub fn network_acl_load(
&mut self,
ctx: &AclCtx,
parse_state: &[&[u8]],
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if !parse_state.is_empty() {
abort_with_wrong_number_of_arguments(output, "acl|load");
return Ok(true);
}
if !Self::validate_acl_authenticator(ctx, output) || !Self::validate_acl_file_use(ctx, output) {
return Ok(true);
}
let Some(settings) = ctx.acl_settings else {
return Ok(true);
};
let Some(acl) = ctx.authenticator.map(|a| a.get_access_control_list()) else {
return Ok(true);
};
let file = settings.acl_configuration_file.clone().unwrap_or_default();
log::info!("Reading updated ACL configuration file '{file}'");
match acl.load(&settings.default_password, &file) {
Ok(()) => {
write_raw(output, cs::RESP_OK);
}
Err(exception) => {
write_error_raw(output, &format!("ERR {exception}"));
}
}
Ok(true)
}
pub fn network_acl_save(
&mut self,
ctx: &AclCtx,
parse_state: &[&[u8]],
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if !parse_state.is_empty() {
abort_with_wrong_number_of_arguments(output, "acl|save");
return Ok(true);
}
if !Self::validate_acl_authenticator(ctx, output) || !Self::validate_acl_file_use(ctx, output) {
return Ok(true);
}
let Some(settings) = ctx.acl_settings else {
return Ok(true);
};
let Some(acl) = ctx.authenticator.map(|a| a.get_access_control_list()) else {
return Ok(true);
};
let file = settings.acl_configuration_file.clone().unwrap_or_default();
match acl.save(&file) {
Ok(()) => {
log::info!("ACL configuration file '{file}' saved!");
write_raw(output, cs::RESP_OK);
}
Err(exception) => {
log::error!("ACL SAVE faulted: {exception}");
write_error_raw(output, &format!("ERR {exception}"));
}
}
Ok(true)
}
pub fn network_acl_gen_pass(
&mut self,
_ctx: &AclCtx,
parse_state: &[&[u8]],
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() > 1 {
abort_with_wrong_number_of_arguments(output, "acl|genpass");
return Ok(true);
}
let mut length = GENPASS_DEFAULT_LENGTH;
if let Some(&bits_arg) = parse_state.first() {
let Some(bits) = bits_arg.try_parse_i64() else {
abort_with_error_message(output, cs::RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER);
return Ok(true);
};
if bits <= 0 || bits > GENPASS_MAX_BITS {
abort_with_error_message(output, RESP_ERR_ACL_GENPASS_BITS_RANGE);
return Ok(true);
}
length = (bits / 4 + (bits % 4 != 0) as i64) as usize;
}
let mut password = Vec::with_capacity(length);
for _ in 0..length {
let nibble = fastrand::u8(0..16);
password.push(if nibble < 10 {
b'0' + nibble
} else {
b'a' + nibble - 10
});
}
output.write_resp_bulk_string(&password);
Ok(true)
}
pub fn network_acl_get_user(
&mut self,
ctx: &AclCtx,
parse_state: &[&[u8]],
output: &mut Vec<u8>,
) -> wresp::Result<bool> {
if parse_state.len() != 1 {
abort_with_wrong_number_of_arguments(output, "acl|getuser");
return Ok(true);
}
if !Self::validate_acl_authenticator(ctx, output) {
return Ok(true);
}
let user = ctx
.authenticator
.map(|a| a.get_access_control_list())
.and_then(|acl| acl.get_user_handle(parse_state[0].as_str_safe()))
.map(|handle| User::from_user(&handle.user()));
match user {
None => {
output.write_resp_null();
}
Some(user) => {
write_map_len_resp2(output, 3);
output.write_resp_bulk_string(b"flags");
output.write_resp_array_len(1);
output.write_resp_bulk_string(if user.is_enabled() { b"on" } else { b"off" });
output.write_resp_bulk_string(b"passwords");
let passwords = user.copy_password_hashes();
output.write_resp_array_len(passwords.len());
for password in passwords {
let hash = format!("#{password}");
output.write_resp_bulk_string(hash.as_bytes());
}
output.write_resp_bulk_string(b"commands");
let commands = user.get_enabled_commands_description();
output.write_resp_bulk_string(commands.as_bytes());
}
}
Ok(true)
}
}
#[cfg(test)]
mod tests {
use std::{fs, sync::Arc};
use super::*;
use crate::{
acl::{AccessControlList, AclParser},
auth::{
GarnetAclWithPasswordAuthenticator, i_garnet_authenticator::IGarnetAuthenticator,
settings::acl_authentication_settings::AclAuthenticationSettings,
},
types::RespCommand,
};
fn acl_settings(acl_file: Option<String>) -> AclAuthenticationSettings {
AclAuthenticationSettings::new(acl_file, String::new())
}
fn ctx_for<'a>(
auth: &'a GarnetAclWithPasswordAuthenticator,
settings: &'a AclAuthenticationSettings,
registered: Option<&'a (dyn Fn(&str) -> bool + Sync)>,
) -> AclCtx<'a> {
AclCtx {
authenticator: Some(&auth.base),
acl_settings: Some(settings),
is_custom_command_registered: registered,
}
}
fn bare_ctx() -> AclCtx<'static> {
AclCtx {
authenticator: None,
acl_settings: None,
is_custom_command_registered: None,
}
}
#[test]
fn rejects_without_acl_authenticator() {
let mut session = RespServerSession::default();
let mut out = Vec::new();
let _ = session
.network_acl_list(&bare_ctx(), &[], &mut out)
.unwrap();
assert_eq!(out, b"-ERR ACL Authenticator is disabled.\r\n");
let mut out = Vec::new();
let _ = session
.network_acl_users(&bare_ctx(), &[], &mut out)
.unwrap();
assert_eq!(out, b"-ERR ACL Authenticator is disabled.\r\n");
}
#[test]
fn list_and_users_frames() {
let acl = Arc::new(AccessControlList::new("", None).unwrap());
let auth = GarnetAclWithPasswordAuthenticator::new(Arc::clone(&acl));
let settings = acl_settings(None);
let ctx = ctx_for(&auth, &settings, None);
let mut session = RespServerSession::default();
let mut out = Vec::new();
let _ = session
.network_acl_set_user(&ctx, &[b"testuser"], &mut out)
.unwrap();
assert_eq!(out, b"+OK\r\n");
let mut out = Vec::new();
let _ = session.network_acl_list(&ctx, &[], &mut out).unwrap();
let frame = String::from_utf8(out).unwrap();
assert!(frame.starts_with("*2\r\n"));
assert!(frame.contains("user default on nopass +@all"));
assert!(frame.contains("user testuser off"));
let mut out = Vec::new();
let _ = session.network_acl_users(&ctx, &[], &mut out).unwrap();
let frame = String::from_utf8(out).unwrap();
assert!(frame.contains("$8\r\ntestuser\r\n"));
assert!(frame.contains("$7\r\ndefault\r\n"));
let mut out = Vec::new();
let _ = session
.network_acl_list(&ctx, &[b"extra"], &mut out)
.unwrap();
assert_eq!(
out,
b"-ERR wrong number of arguments for 'acl|list' command\r\n"
);
}
#[test]
fn cat_lists_categories() {
let acl = Arc::new(AccessControlList::new("", None).unwrap());
let auth = GarnetAclWithPasswordAuthenticator::new(acl);
let settings = acl_settings(None);
let ctx = ctx_for(&auth, &settings, None);
let mut session = RespServerSession::default();
let mut out = Vec::new();
let _ = session.network_acl_cat(&ctx, &[], &mut out).unwrap();
let frame = String::from_utf8(out).unwrap();
assert!(frame.starts_with("*25\r\n"));
assert!(frame.contains("$8\r\nkeyspace\r\n"));
let mut out = Vec::new();
let _ = session.network_acl_cat(&ctx, &[b"x"], &mut out).unwrap();
assert_eq!(
out,
b"-ERR Unknown subcommand or wrong number of arguments for ACL CAT.\r\n"
);
}
#[test]
fn whoami_returns_authenticated_user() {
let acl = Arc::new(AccessControlList::new("", None).unwrap());
let mut auth = GarnetAclWithPasswordAuthenticator::new(Arc::clone(&acl));
let mut session = RespServerSession::default();
assert!(auth.authenticate(b"x", b""));
let settings = acl_settings(None);
let ctx = ctx_for(&auth, &settings, None);
let mut out = Vec::new();
let _ = session.network_acl_who_am_i(&ctx, &[], &mut out).unwrap();
assert_eq!(out, b"$7\r\ndefault\r\n");
AclParser::parse_acl_rule("user testuser on nopass", Some(&acl)).unwrap();
assert!(auth.authenticate(b"x", b"testuser"));
let settings = acl_settings(None);
let ctx = ctx_for(&auth, &settings, None);
let mut out = Vec::new();
let _ = session.network_acl_who_am_i(&ctx, &[], &mut out).unwrap();
assert_eq!(out, b"$8\r\ntestuser\r\n");
let mut out = Vec::new();
let _ = session
.network_acl_who_am_i(&ctx, &[b"x"], &mut out)
.unwrap();
assert_eq!(
out,
b"-ERR wrong number of arguments for 'acl|whoami' command\r\n"
);
}
#[test]
fn genpass_lengths_and_validation() {
let acl = Arc::new(AccessControlList::new("", None).unwrap());
let auth = GarnetAclWithPasswordAuthenticator::new(Arc::clone(&acl));
let settings = acl_settings(None);
let ctx = ctx_for(&auth, &settings, None);
let mut session = RespServerSession::default();
let mut out = Vec::new();
let _ = session.network_acl_gen_pass(&ctx, &[], &mut out).unwrap();
let frame = String::from_utf8(out).unwrap();
let (_, tail) = frame.split_once("\r\n").unwrap();
let hex: &str = &tail[..64];
assert!(
hex
.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
);
for bits in [b"5".as_slice(), b"8".as_slice()] {
let mut out = Vec::new();
let _ = session
.network_acl_gen_pass(&ctx, &[bits], &mut out)
.unwrap();
assert!(out.starts_with(b"$2\r\n"));
}
let mut out = Vec::new();
let _ = session
.network_acl_gen_pass(&ctx, &[b"abcd"], &mut out)
.unwrap();
assert_eq!(out, b"-ERR value is not an integer or out of range.\r\n");
for bad in [b"0".as_slice(), b"4097".as_slice()] {
let mut out = Vec::new();
let _ = session
.network_acl_gen_pass(&ctx, &[bad], &mut out)
.unwrap();
assert_eq!(
out,
&b"-ERR ACL GENPASS argument must be the number of bits for the output password, a positive number up to 4096\r\n"[..]
);
}
let mut out = Vec::new();
let _ = session
.network_acl_gen_pass(&ctx, &[b"8", b"8"], &mut out)
.unwrap();
assert_eq!(
out,
b"-ERR wrong number of arguments for 'acl|genpass' command\r\n"
);
}
#[test]
fn getuser_frames() {
let acl = Arc::new(AccessControlList::new("", None).unwrap());
AclParser::parse_acl_rule("user alice on >passw0rd +@admin", Some(&acl)).unwrap();
let auth = GarnetAclWithPasswordAuthenticator::new(Arc::clone(&acl));
let settings = acl_settings(None);
let ctx = ctx_for(&auth, &settings, None);
let mut session = RespServerSession::default();
let mut out = Vec::new();
let _ = session
.network_acl_get_user(&ctx, &[b"alice"], &mut out)
.unwrap();
let frame = String::from_utf8(out).unwrap();
assert!(frame.starts_with("*6\r\n$5\r\nflags\r\n*1\r\n$2\r\non\r\n"));
assert!(frame.contains("$9\r\npasswords\r\n*1\r\n$65\r\n#8f0e2f76e22b43e2855189877e7dc1e1e7d98c226c95db247cd1d547928334a9\r\n"));
assert!(frame.contains("$8\r\ncommands\r\n$7\r\n+@admin\r\n"));
let mut out = Vec::new();
let _ = session
.network_acl_get_user(&ctx, &[b"default"], &mut out)
.unwrap();
let frame = String::from_utf8(out).unwrap();
assert!(frame.contains("*0\r\n$8\r\ncommands\r\n$5\r\n+@all\r\n"));
let mut out = Vec::new();
let _ = session
.network_acl_get_user(&ctx, &[b"missing"], &mut out)
.unwrap();
assert_eq!(out, b"$-1\r\n");
let mut out = Vec::new();
let _ = session.network_acl_get_user(&ctx, &[], &mut out).unwrap();
assert_eq!(
out,
b"-ERR wrong number of arguments for 'acl|getuser' command\r\n"
);
}
#[test]
fn setuser_and_deluser() {
let acl = Arc::new(AccessControlList::new("", None).unwrap());
let auth = GarnetAclWithPasswordAuthenticator::new(Arc::clone(&acl));
let mut session = RespServerSession::default();
let settings = acl_settings(None);
let ctx = ctx_for(&auth, &settings, None);
let mut out = Vec::new();
let _ = session
.network_acl_set_user(&ctx, &[b"testuser", b"on", b"nopass", b"+@admin"], &mut out)
.unwrap();
assert_eq!(out, b"+OK\r\n");
let testuser = acl.get_user_handle("testuser").unwrap().user();
assert!(testuser.is_enabled());
assert!(testuser.can_access_command(RespCommand::AclDeluser));
let mut out = Vec::new();
let _ = session
.network_acl_set_user(&ctx, &[b"baduser", b"+@nosuchcat"], &mut out)
.unwrap();
assert_eq!(out, b"-ERR ACL Category 'nosuchcat' does not exist\r\n");
let baduser = acl.get_user_handle("baduser").unwrap().user();
assert!(!baduser.is_enabled());
assert!(!baduser.can_access_command(RespCommand::Get));
let mut out = Vec::new();
let _ = session.network_acl_set_user(&ctx, &[], &mut out).unwrap();
assert_eq!(
out,
b"-ERR wrong number of arguments for 'acl|setuser' command\r\n"
);
let mut out = Vec::new();
let _ = session
.network_acl_del_user(&ctx, &[b"testuser"], &mut out)
.unwrap();
assert_eq!(out, b":1\r\n");
assert!(acl.get_user_handle("testuser").is_none());
let mut out = Vec::new();
let _ = session
.network_acl_del_user(&ctx, &[b"default"], &mut out)
.unwrap();
assert_eq!(
out,
b"-ERR The special 'default' user cannot be removed from the system\r\n"
);
let mut out = Vec::new();
let _ = session
.network_acl_del_user(&ctx, &[b"ghost"], &mut out)
.unwrap();
assert_eq!(out, b":0\r\n");
}
#[test]
fn setuser_custom_command_validation() {
let acl = Arc::new(AccessControlList::new("", None).unwrap());
fn registered(name: &str) -> bool {
name.eq_ignore_ascii_case("json.set")
}
let auth = GarnetAclWithPasswordAuthenticator::new(Arc::clone(&acl));
let mut session = RespServerSession::default();
let settings = acl_settings(None);
let ctx = ctx_for(&auth, &settings, Some(®istered));
let mut out = Vec::new();
let _ = session
.network_acl_set_user(&ctx, &[b"u1", b"+json.set"], &mut out)
.unwrap();
assert_eq!(out, b"+OK\r\n");
let mut out = Vec::new();
let _ = session
.network_acl_set_user(&ctx, &[b"u2", b"+nothere"], &mut out)
.unwrap();
assert_eq!(
out,
&b"-ERR Unknown custom command 'NOTHERE' (not registered with any loaded module)\r\n"[..]
);
let settings = acl_settings(None);
let ctx = ctx_for(&auth, &settings, None);
let mut out = Vec::new();
let _ = session
.network_acl_set_user(&ctx, &[b"u3", b"+anything"], &mut out)
.unwrap();
assert_eq!(out, b"+OK\r\n");
}
#[test]
fn load_and_save() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("users.acl");
fs::write(&file, "user default on nopass +@all\n").unwrap();
let acl = Arc::new(AccessControlList::new("", None).unwrap());
let auth = GarnetAclWithPasswordAuthenticator::new(Arc::clone(&acl));
let mut session = RespServerSession::default();
let settings = acl_settings(Some(file.display().to_string()));
let ctx = ctx_for(&auth, &settings, None);
let mut out = Vec::new();
let _ = session.network_acl_load(&ctx, &[], &mut out).unwrap();
assert_eq!(out, b"+OK\r\n");
AclParser::parse_acl_rule("user saved on >pw +get", Some(&acl)).unwrap();
let mut out = Vec::new();
let _ = session.network_acl_save(&ctx, &[], &mut out).unwrap();
assert_eq!(out, b"+OK\r\n");
let content = fs::read_to_string(&file).unwrap();
assert!(content.contains("user saved on"));
assert!(content.contains("user default on nopass +@all"));
let mut out = Vec::new();
let _ = session.network_acl_load(&ctx, &[], &mut out).unwrap();
assert_eq!(out, b"+OK\r\n");
assert_eq!(acl.len(), 2);
fs::remove_file(&file).unwrap();
let mut out = Vec::new();
let _ = session.network_acl_load(&ctx, &[], &mut out).unwrap();
assert!(out.starts_with(b"-ERR Cannot find ACL configuration file"));
}
#[test]
fn load_save_require_acl_file_config() {
let acl = Arc::new(AccessControlList::new("", None).unwrap());
let mut session = RespServerSession::default();
let mut out = Vec::new();
let _ = session
.network_acl_load(&bare_ctx(), &[], &mut out)
.unwrap();
assert_eq!(out, b"-ERR ACL Authenticator is disabled.\r\n");
let auth = GarnetAclWithPasswordAuthenticator::new(Arc::clone(&acl));
let settings = acl_settings(None);
let ctx = ctx_for(&auth, &settings, None);
let mut out = Vec::new();
let _ = session.network_acl_load(&ctx, &[], &mut out).unwrap();
assert_eq!(
out,
&b"-ERR This Garnet instance is not configured to use an ACL file. Please restart server with --acl-file option.\r\n"[..]
);
let mut out = Vec::new();
let _ = session.network_acl_save(&ctx, &[], &mut out).unwrap();
assert_eq!(
out,
&b"-ERR This Garnet instance is not configured to use an ACL file. Please restart server with --acl-file option.\r\n"[..]
);
let mut out = Vec::new();
let _ = session.network_acl_load(&ctx, &[b"x"], &mut out).unwrap();
assert_eq!(
out,
b"-ERR wrong number of arguments for 'acl|load' command\r\n"
);
}
}