Skip to content

Commit 7141716

Browse files
committed
Add capability to create module users
1 parent d1f567f commit 7141716

File tree

6 files changed

+152
-16
lines changed

6 files changed

+152
-16
lines changed

examples/acl.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
use std::sync::Mutex;
2+
3+
use lazy_static::{lazy_static, __Deref};
14
use redis_module::{
25
redis_module, AclPermissions, Context, NextArg, RedisError, RedisResult, RedisString,
3-
RedisValue,
6+
RedisValue, RedisUser, Status,
47
};
58

9+
lazy_static! {
10+
static ref USER: Mutex<RedisUser> = Mutex::new(RedisUser::new("acl"));
11+
}
12+
613
fn verify_key_access_for_user(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
714
let mut args = args.into_iter().skip(1);
815
let user = args.next_arg()?;
@@ -18,14 +25,30 @@ fn get_current_user(ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
1825
Ok(RedisValue::BulkRedisString(ctx.get_current_user()))
1926
}
2027

28+
fn authenticate_with_user(ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
29+
let user = USER.lock()?;
30+
ctx.authenticate_client_with_user(user.deref())?;
31+
Ok(RedisValue::SimpleStringStatic("OK"))
32+
}
33+
34+
fn init(_ctx: &Context, _args: &[RedisString]) -> Status {
35+
// Set the user ACL
36+
let _ = USER.lock().unwrap().set_acl("on allcommands allkeys");
37+
38+
// Module initialized
39+
Status::Ok
40+
}
41+
2142
//////////////////////////////////////////////////////
2243

2344
redis_module! {
2445
name: "acl",
2546
version: 1,
2647
allocator: (redis_module::alloc::RedisAlloc, redis_module::alloc::RedisAlloc),
2748
data_types: [],
49+
init: init,
2850
commands: [
51+
["authenticate_with_user", authenticate_with_user, "", 0, 0, 0],
2952
["verify_key_access_for_user", verify_key_access_for_user, "", 0, 0, 0],
3053
["get_current_user", get_current_user, "", 0, 0, 0],
3154
],

src/context/mod.rs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ use std::os::raw::{c_char, c_int, c_long, c_longlong};
66
use std::ptr::{self, NonNull};
77
use std::sync::atomic::{AtomicPtr, Ordering};
88

9-
use crate::add_info_section;
109
use crate::key::{RedisKey, RedisKeyWritable};
1110
use crate::logging::RedisLogLevel;
1211
use crate::raw::{ModuleOptions, Version};
1312
use crate::redisvalue::RedisValueKey;
1413
use crate::{add_info_field_long_long, add_info_field_str, raw, utils, Status};
14+
use crate::{add_info_section, RedisUser};
1515
use crate::{RedisError, RedisResult, RedisString, RedisValue};
1616
use std::ops::Deref;
1717

@@ -731,6 +731,16 @@ impl Context {
731731
RedisString::from_redis_module_string(ptr::null_mut(), user)
732732
}
733733

734+
/// Return the current user as a [RedisUser] object
735+
pub fn get_module_user(&self, user_name: &RedisString) -> Option<RedisUser> {
736+
let user = unsafe { raw::RedisModule_GetModuleUserFromUserName.unwrap()(user_name.inner) };
737+
if user.is_null() {
738+
return None;
739+
}
740+
741+
Some(RedisUser::from_redis_module_user(user))
742+
}
743+
734744
/// Attach the given user to the current context so each operation performed from
735745
/// now on using this context will be validated againts this new user.
736746
/// Return [ContextUserScope] which make sure to unset the user when freed and
@@ -747,6 +757,25 @@ impl Context {
747757
Ok(ContextUserScope::new(self, user))
748758
}
749759

760+
/// Authenticate the current context's user with the provided [RedisUser].
761+
pub fn authenticate_client_with_user(&self, user: &RedisUser) -> Result<(), RedisError> {
762+
let result = unsafe {
763+
raw::RedisModule_AuthenticateClientWithUser.unwrap()(
764+
self.ctx,
765+
user.user,
766+
None,
767+
std::ptr::null_mut(),
768+
std::ptr::null_mut(),
769+
)
770+
};
771+
772+
if result != raw::REDISMODULE_OK as i32 {
773+
return Err(RedisError::Str("Error authenticating user client"));
774+
}
775+
776+
Ok(())
777+
}
778+
750779
fn deautenticate_user(&self) {
751780
unsafe { raw::RedisModule_SetContextUser.unwrap()(self.ctx, ptr::null_mut()) };
752781
}
@@ -760,21 +789,10 @@ impl Context {
760789
key_name: &RedisString,
761790
permissions: &AclPermissions,
762791
) -> Result<(), RedisError> {
763-
let user = unsafe { raw::RedisModule_GetModuleUserFromUserName.unwrap()(user_name.inner) };
764-
if user.is_null() {
765-
return Err(RedisError::Str("User does not exists or disabled"));
766-
}
767-
let acl_permission_result: raw::Status = unsafe {
768-
raw::RedisModule_ACLCheckKeyPermissions.unwrap()(
769-
user,
770-
key_name.inner,
771-
permissions.bits(),
772-
)
792+
match self.get_module_user(user_name) {
793+
Some(user) => user.acl_check_key_permission(key_name, permissions),
794+
None => Err(RedisError::Str("User does not exists or disabled")),
773795
}
774-
.into();
775-
unsafe { raw::RedisModule_FreeModuleUser.unwrap()(user) };
776-
let acl_permission_result: Result<(), &str> = acl_permission_result.into();
777-
acl_permission_result.map_err(|_e| RedisError::Str("User does not have permissions on key"))
778796
}
779797

780798
api!(

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod raw;
99
pub mod rediserror;
1010
mod redismodule;
1111
pub mod redisraw;
12+
pub mod redisuser;
1213
pub mod redisvalue;
1314
pub mod stream;
1415

src/redismodule.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use serde::de::{Error, SeqAccess};
1616
pub use crate::raw;
1717
pub use crate::rediserror::RedisError;
1818
pub use crate::redisvalue::RedisValue;
19+
pub use crate::redisuser::RedisUser;
1920
use crate::Context;
2021

2122
pub type RedisResult = Result<RedisValue, RedisError>;

src/redisuser.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use std::{ffi::CString, os::raw::c_char};
2+
3+
use crate::{raw, AclPermissions, RedisError, RedisString};
4+
5+
pub struct RedisUser {
6+
pub(super) user: *mut raw::RedisModuleUser,
7+
}
8+
9+
impl RedisUser {
10+
pub fn new(username: &str) -> RedisUser {
11+
let username = CString::new(username).unwrap();
12+
let module_user = unsafe { raw::RedisModule_CreateModuleUser.unwrap()(username.as_ptr()) };
13+
14+
RedisUser { user: module_user }
15+
}
16+
17+
pub(super) fn from_redis_module_user(user: *mut raw::RedisModuleUser) -> RedisUser {
18+
RedisUser { user }
19+
}
20+
21+
pub fn set_acl(&self, acl: &str) -> Result<(), RedisError> {
22+
let acl = CString::new(acl).unwrap();
23+
let mut error: *mut raw::RedisModuleString = std::ptr::null_mut();
24+
let error_ptr: *mut *mut raw::RedisModuleString = &mut error;
25+
26+
let result = unsafe {
27+
raw::RedisModule_SetModuleUserACLString.unwrap()(
28+
std::ptr::null_mut(),
29+
self.user,
30+
acl.as_ptr().cast::<c_char>(),
31+
error_ptr,
32+
)
33+
};
34+
35+
// If the result is an error, parse the error string
36+
if result != raw::REDISMODULE_OK as i32 {
37+
let error = RedisString::from_redis_module_string(std::ptr::null_mut(), error);
38+
return Err(RedisError::String(error.to_string_lossy()));
39+
}
40+
41+
Ok(())
42+
}
43+
44+
pub fn acl(&self) -> RedisString {
45+
let acl = unsafe { raw::RedisModule_GetModuleUserACLString.unwrap()(self.user) };
46+
RedisString::from_redis_module_string(std::ptr::null_mut(), acl)
47+
}
48+
49+
/// Verify the the given user has the give ACL permission on the given key.
50+
/// Return Ok(()) if the user has the permissions or error (with relevant error message)
51+
/// if the validation failed.
52+
pub fn acl_check_key_permission(
53+
&self,
54+
key_name: &RedisString,
55+
permissions: &AclPermissions,
56+
) -> Result<(), RedisError> {
57+
let acl_permission_result: raw::Status = unsafe {
58+
raw::RedisModule_ACLCheckKeyPermissions.unwrap()(
59+
self.user,
60+
key_name.inner,
61+
permissions.bits(),
62+
)
63+
}
64+
.into();
65+
let acl_permission_result: Result<(), &str> = acl_permission_result.into();
66+
acl_permission_result.map_err(|_e| RedisError::Str("User does not have permissions on key"))
67+
}
68+
}
69+
70+
impl Drop for RedisUser {
71+
fn drop(&mut self) {
72+
unsafe { raw::RedisModule_FreeModuleUser.unwrap()(self.user) };
73+
}
74+
}
75+
76+
unsafe impl Send for RedisUser {}

tests/integration.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,23 @@ fn test_get_current_user() -> Result<()> {
282282
Ok(())
283283
}
284284

285+
#[test]
286+
fn test_authenticate_client_with_user() -> Result<()> {
287+
let port: u16 = 6490;
288+
let _guards = vec![start_redis_server_with_module("acl", port)
289+
.with_context(|| "failed to start redis server")?];
290+
let mut con =
291+
get_redis_connection(port).with_context(|| "failed to connect to redis server")?;
292+
293+
let res: String = redis::cmd("authenticate_with_user").query(&mut con)?;
294+
assert_eq!(&res, "OK");
295+
296+
let res: String = redis::cmd("get_current_user").query(&mut con)?;
297+
assert_eq!(&res, "acl");
298+
299+
Ok(())
300+
}
301+
285302
#[test]
286303
fn test_verify_acl_on_user() -> Result<()> {
287304
let port: u16 = 6491;

0 commit comments

Comments
 (0)