Skip to content

Commit 6706707

Browse files
authored
feat(AclFamily): add acl deluser (#1773)
* add acl deluser command * add tests
1 parent 9ca7dba commit 6706707

File tree

6 files changed

+88
-6
lines changed

6 files changed

+88
-6
lines changed

src/facade/conn_context.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ class ConnectionContext {
6464
rbuilder_->SendSimpleString(str);
6565
}
6666

67+
void SendOk() {
68+
rbuilder_->SendOk();
69+
}
70+
6771
// connection state / properties.
6872
bool conn_closing : 1;
6973
bool req_auth : 1;

src/server/acl/acl_family.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,33 @@ void AclFamily::SetUser(CmdArgList args, ConnectionContext* cntx) {
188188
std::visit(Overloaded{error_case, update_case}, std::move(req));
189189
}
190190

191+
void AclFamily::EvictOpenConnectionsOnAllProactors(std::string_view user) {
192+
auto close_cb = [user]([[maybe_unused]] size_t id, util::Connection* conn) {
193+
DCHECK(conn);
194+
auto connection = static_cast<facade::Connection*>(conn);
195+
auto ctx = static_cast<ConnectionContext*>(connection->cntx());
196+
if (ctx && ctx->authed_username == user) {
197+
connection->ShutdownSelf();
198+
}
199+
};
200+
201+
if (main_listener_) {
202+
main_listener_->TraverseConnections(close_cb);
203+
}
204+
}
205+
206+
void AclFamily::DelUser(CmdArgList args, ConnectionContext* cntx) {
207+
std::string_view username = facade::ToSV(args[0]);
208+
auto& registry = *ServerState::tlocal()->user_registry;
209+
if (!registry.RemoveUser(username)) {
210+
cntx->SendError(absl::StrCat("User ", username, " does not exist"));
211+
return;
212+
}
213+
214+
EvictOpenConnectionsOnAllProactors(username);
215+
cntx->SendOk();
216+
}
217+
191218
using CI = dfly::CommandId;
192219

193220
using MemberFunc = void (AclFamily::*)(CmdArgList args, ConnectionContext* cntx);
@@ -201,6 +228,7 @@ inline CommandId::Handler HandlerFunc(AclFamily* acl, MemberFunc f) {
201228
constexpr uint32_t kAcl = acl::CONNECTION;
202229
constexpr uint32_t kList = acl::ADMIN | acl::SLOW | acl::DANGEROUS;
203230
constexpr uint32_t kSetUser = acl::ADMIN | acl::SLOW | acl::DANGEROUS;
231+
constexpr uint32_t kDelUser = acl::ADMIN | acl::SLOW | acl::DANGEROUS;
204232

205233
// We can't implement the ACL commands and its respective subcommands LIST, CAT, etc
206234
// the usual way, (that is, one command called ACL which then dispatches to the subcommand
@@ -215,6 +243,8 @@ void AclFamily::Register(dfly::CommandRegistry* registry) {
215243
List);
216244
*registry << CI{"ACL SETUSER", CO::ADMIN | CO::NOSCRIPT | CO::LOADING, -2, 0, 0, 0, acl::kSetUser}
217245
.HFUNC(SetUser);
246+
*registry << CI{"ACL DELUSER", CO::ADMIN | CO::NOSCRIPT | CO::LOADING, 2, 0, 0, 0, acl::kDelUser}
247+
.HFUNC(DelUser);
218248
}
219249

220250
#undef HFUNC

src/server/acl/acl_family.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,15 @@ class AclFamily final {
3030
void Acl(CmdArgList args, ConnectionContext* cntx);
3131
void List(CmdArgList args, ConnectionContext* cntx);
3232
void SetUser(CmdArgList args, ConnectionContext* cntx);
33+
void DelUser(CmdArgList args, ConnectionContext* cntx);
3334

3435
// Helper function that updates all open connections and their
3536
// respective ACL fields on all the available proactor threads
3637
void StreamUpdatesToAllProactorConnections(std::string_view user, uint32_t update_cat);
3738

39+
// Helper function that closes all open connection from the deleted user
40+
void EvictOpenConnectionsOnAllProactors(std::string_view user);
41+
3842
facade::Listener* main_listener_{nullptr};
3943
};
4044

src/server/acl/user_registry.cc

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@ void UserRegistry::MaybeAddAndUpdate(std::string_view username, User::UpdateRequ
2121
user.Update(std::move(req));
2222
}
2323

24-
void UserRegistry::RemoveUser(std::string_view username) {
24+
bool UserRegistry::RemoveUser(std::string_view username) {
2525
std::unique_lock<util::SharedMutex> lock(mu_);
26-
registry_.erase(username);
27-
// TODO evict authed connections from user
26+
return registry_.erase(username);
2827
}
2928

3029
UserRegistry::UserCredentials UserRegistry::GetCredentials(std::string_view username) const {

src/server/acl/user_registry.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ class UserRegistry {
3535
// Acquires a write lock on mu_
3636
// Removes user from the store
3737
// kills already existing connections from the removed user
38-
// TODO change return time to communicate back results to acl commands
39-
void RemoveUser(std::string_view username);
38+
bool RemoveUser(std::string_view username);
4039

4140
struct UserCredentials {
4241
uint32_t acl_categories{0};

tests/dragonfly/acl_family_test.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from redis import asyncio as aioredis
44
from . import DflyInstanceFactory
55
from .utility import disconnect_clients
6-
import time
76
import asyncio
87

98

@@ -193,6 +192,33 @@ async def test_acl_categories_multi_exec_squash(df_local_factory):
193192
await client.close()
194193

195194

195+
@pytest.mark.asyncio
196+
async def test_acl_deluser(df_server):
197+
client = aioredis.Redis(port=df_server.port)
198+
199+
try:
200+
res = await client.execute_command("ACL DELUSER adi")
201+
except redis.exceptions.ResponseError as e:
202+
assert e.args[0] == "User adi does not exist"
203+
204+
res = await client.execute_command("ACL SETUSER adi ON >pass +@transaction +@string")
205+
assert res == b"OK"
206+
207+
res = await client.execute_command("AUTH adi pass")
208+
assert res == b"OK"
209+
210+
await client.execute_command("MULTI")
211+
await client.execute_command("SET key 44")
212+
213+
admin_client = aioredis.Redis(port=df_server.port)
214+
await admin_client.execute_command("ACL DELUSER adi")
215+
216+
with pytest.raises(redis.exceptions.ConnectionError):
217+
await client.execute_command("EXEC")
218+
219+
await admin_client.close()
220+
221+
196222
script = """
197223
for i = 1, 10000 do
198224
redis.call('SET', 'key', i)
@@ -203,6 +229,26 @@ async def test_acl_categories_multi_exec_squash(df_local_factory):
203229
"""
204230

205231

232+
@pytest.mark.asyncio
233+
async def test_acl_del_user_while_running_lua_script(df_server):
234+
client = aioredis.Redis(port=df_server.port)
235+
await client.execute_command("ACL SETUSER kostas ON >kk +@string +@scripting")
236+
await client.execute_command("AUTH kostas kk")
237+
admin_client = aioredis.Redis(port=df_server.port)
238+
239+
with pytest.raises(redis.exceptions.ConnectionError):
240+
await asyncio.gather(
241+
client.eval(script, 4, "key", "key1", "key2", "key3"),
242+
admin_client.execute_command("ACL DELUSER kostas"),
243+
)
244+
245+
for i in range(1, 4):
246+
res = await admin_client.get(f"key{i}")
247+
assert res == b"10000"
248+
249+
await admin_client.close()
250+
251+
206252
@pytest.mark.asyncio
207253
async def test_acl_with_long_running_script(df_server):
208254
client = aioredis.Redis(port=df_server.port)

0 commit comments

Comments
 (0)