Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add user-client mapping. #405

Open
wants to merge 2 commits into
base: unstable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,8 @@ user *ACLCreateUser(const char *name, size_t namelen) {
aclSelector *s = ACLCreateSelector(SELECTOR_FLAG_ROOT);
listAddNodeHead(u->selectors, s);

u->clients = listCreate();

raxInsert(Users,(unsigned char*)name,namelen,u,NULL);
return u;
}
Expand Down Expand Up @@ -487,6 +489,7 @@ void ACLFreeUser(user *u) {
}
listRelease(u->passwords);
listRelease(u->selectors);
listRelease(u->clients);
zfree(u);
}

Expand Down Expand Up @@ -3241,6 +3244,11 @@ void authCommand(client *c) {
robj *err = NULL;
int result = ACLAuthenticateUser(c, username, password, &err);
if (result == AUTH_OK) {
listNode *ln=listSearchKey(c->user->clients, c);
if(ln == NULL) {
listAddNodeTail(c->user->clients, c);
c->user_client_node = listLast(c->user->clients);
}
addReply(c, shared.ok);
} else if (result == AUTH_ERR) {
addAuthErrReply(c, err);
Expand Down
21 changes: 20 additions & 1 deletion src/commands.def
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,23 @@ struct COMMAND_ARG CLIENT_CACHING_Args[] = {
{MAKE_ARG("mode",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=CLIENT_CACHING_mode_Subargs},
};

/********** CLIENT COUNT ********************/

#ifndef SKIP_CMD_HISTORY_TABLE
/* CLIENT COUNT history */
#define CLIENT_COUNT_History NULL
#endif

#ifndef SKIP_CMD_TIPS_TABLE
/* CLIENT COUNT tips */
#define CLIENT_COUNT_Tips NULL
#endif

#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* CLIENT COUNT key specs */
#define CLIENT_COUNT_Keyspecs NULL
#endif

/********** CLIENT GETNAME ********************/

#ifndef SKIP_CMD_HISTORY_TABLE
Expand Down Expand Up @@ -1266,6 +1283,7 @@ struct COMMAND_ARG CLIENT_LIST_client_type_Subargs[] = {
struct COMMAND_ARG CLIENT_LIST_Args[] = {
{MAKE_ARG("client-type",ARG_TYPE_ONEOF,-1,"TYPE",NULL,"5.0.0",CMD_ARG_OPTIONAL,4,NULL),.subargs=CLIENT_LIST_client_type_Subargs},
{MAKE_ARG("client-id",ARG_TYPE_INTEGER,-1,"ID",NULL,"6.2.0",CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)},
{MAKE_ARG("user",ARG_TYPE_STRING,-1,NULL,NULL,"7.2.5",CMD_ARG_OPTIONAL,0,NULL)},
};

/********** CLIENT NO_EVICT ********************/
Expand Down Expand Up @@ -1540,13 +1558,14 @@ struct COMMAND_ARG CLIENT_UNBLOCK_Args[] = {
/* CLIENT command table */
struct COMMAND_STRUCT CLIENT_Subcommands[] = {
{MAKE_CMD("caching","Instructs the server whether to track the keys in the next request.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_CACHING_History,0,CLIENT_CACHING_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_CACHING_Keyspecs,0,NULL,1),.args=CLIENT_CACHING_Args},
{MAKE_CMD("count","Returns client count among the user.","O(1)","7.2.5",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_COUNT_History,0,CLIENT_COUNT_Tips,0,clientCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_COUNT_Keyspecs,0,NULL,0)},
{MAKE_CMD("getname","Returns the name of the connection.","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_GETNAME_History,0,CLIENT_GETNAME_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_GETNAME_Keyspecs,0,NULL,0)},
{MAKE_CMD("getredir","Returns the client ID to which the connection's tracking notifications are redirected.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_GETREDIR_History,0,CLIENT_GETREDIR_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_GETREDIR_Keyspecs,0,NULL,0)},
{MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_HELP_History,0,CLIENT_HELP_Tips,0,clientCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_HELP_Keyspecs,0,NULL,0)},
{MAKE_CMD("id","Returns the unique client ID of the connection.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_ID_History,0,CLIENT_ID_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_ID_Keyspecs,0,NULL,0)},
{MAKE_CMD("info","Returns information about the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_INFO_History,0,CLIENT_INFO_Tips,1,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_INFO_Keyspecs,0,NULL,0)},
{MAKE_CMD("kill","Terminates open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,6,CLIENT_KILL_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_KILL_Keyspecs,0,NULL,1),.args=CLIENT_KILL_Args},
{MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,6,CLIENT_LIST_Tips,1,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,2),.args=CLIENT_LIST_Args},
{MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,6,CLIENT_LIST_Tips,1,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,3),.args=CLIENT_LIST_Args},
{MAKE_CMD("no-evict","Sets the client eviction mode of the connection.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_EVICT_History,0,CLIENT_NO_EVICT_Tips,0,clientCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_NO_EVICT_Keyspecs,0,NULL,1),.args=CLIENT_NO_EVICT_Args},
{MAKE_CMD("no-touch","Controls whether commands sent by the client affect the LRU/LFU of accessed keys.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_TOUCH_History,0,CLIENT_NO_TOUCH_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_NO_TOUCH_Keyspecs,0,NULL,1),.args=CLIENT_NO_TOUCH_Args},
{MAKE_CMD("pause","Suspends commands processing.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_PAUSE_History,1,CLIENT_PAUSE_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_PAUSE_Keyspecs,0,NULL,2),.args=CLIENT_PAUSE_Args},
Expand Down
25 changes: 25 additions & 0 deletions src/commands/client-count.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"COUNT": {
"summary": "Returns client count among the user.",
"complexity": "O(1)",
"group": "connection",
"since": "7.2.5",
"arity": 3,
"container": "CLIENT",
"function": "clientCommand",
"command_flags": [
"ADMIN",
"NOSCRIPT",
"LOADING",
"STALE",
"SENTINEL"
],
"acl_categories": [
"CONNECTION"
],
"reply_schema": {
"type": "string",
"description": "client count among user"
}
}
}
7 changes: 7 additions & 0 deletions src/commands/client-list.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@
"optional": true,
"multiple": true,
"since": "6.2.0"
},
{
"name": "user",
"type": "string",
"optional": true,
"multiple":false,
"since": "7.2.5"
}
]
}
Expand Down
99 changes: 99 additions & 0 deletions src/networking.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ client *createClient(connection *conn) {
c->peerid = NULL;
c->sockname = NULL;
c->client_list_node = NULL;
c->user_client_node = NULL;
c->postponed_list_node = NULL;
c->pending_read_list_node = NULL;
c->client_tracking_redirection = 0;
Expand Down Expand Up @@ -1541,6 +1542,10 @@ void clearClientConnectionState(client *c) {
#else
c->resp = 2;
#endif
if(c->user && c->user != DefaultUser && c->user_client_node){
listDelNode(c->user->clients,c->user_client_node);
c->user_client_node = NULL;
}

clientSetDefaultAuth(c);
moduleNotifyUserChanged(c);
Expand Down Expand Up @@ -2902,6 +2907,20 @@ sds getAllClientsInfoString(int type) {
return o;
}

long long getAllClientsCount(int type){
listNode *ln;
listIter li;
client *client;
long long count = 0;
listRewind(server.clients, &li);
while ((ln = listNext(&li)) != NULL) {
client = listNodeValue(ln);
if (type != -1 && getClientType(client) != type) continue;
count++;
}
return count;
}

/* Check validity of an attribute that's gonna be shown in CLIENT LIST. */
int validateClientAttr(const char *val) {
/* Check if the charset is ok. We need to do this otherwise
Expand Down Expand Up @@ -3020,6 +3039,21 @@ void quitCommand(client *c) {
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
}

sds getUserClientsInfoString(user *user) {
listNode *ln;
listIter li;
client *client;
sds o = sdsnewlen(SDS_NOINIT, 200 * listLength(server.clients));
sdsclear(o);
listRewind(user->clients, &li);
while ((ln = listNext(&li)) != NULL) {
client = listNodeValue(ln);
o = catClientInfoString(o, client);
o = sdscatlen(o, "\n", 1);
}
return o;
}

void clientCommand(client *c) {
listNode *ln;
listIter li;
Expand Down Expand Up @@ -3058,6 +3092,16 @@ void clientCommand(client *c) {
" Return information about client connections. Options:",
" * TYPE (NORMAL|MASTER|REPLICA|PUBSUB)",
" Return clients of specified type.",
" * USER <username>",
" Return clients of specified user.",
"COUNT [options ...]",
" Return the count of clients connections. Options:",
" * TYPE (NORMAL|MASTER|REPLICA|PUBSUB)",
" Return count of clients of specified type.",
" * USER <username>",
" Return count of clients of specified user.",
" * ID <client-id>",
" Return count of clients of specified client id.",
"UNPAUSE",
" Stop the current client pause, resuming traffic.",
"PAUSE <timeout> [WRITE|ALL]",
Expand Down Expand Up @@ -3097,6 +3141,7 @@ NULL
/* CLIENT LIST */
int type = -1;
sds o = NULL;
user *user = NULL;
if (c->argc == 4 && !strcasecmp(c->argv[2]->ptr,"type")) {
type = getClientTypeByName(c->argv[3]->ptr);
if (type == -1) {
Expand All @@ -3120,6 +3165,16 @@ NULL
o = sdscatlen(o, "\n", 1);
}
}
} else if(c->argc == 4 && !strcasecmp(c->argv[2]->ptr, "user")) {
o = sdsempty();
user = ACLGetUserByName(c->argv[3]->ptr, sdslen(c->argv[3]->ptr));
if (user == NULL) {
addReplyErrorFormat(c, "No such user '%s'",
(char *) c->argv[3]->ptr);
sdsfree(o);
return;
}
o = getUserClientsInfoString(user);
} else if (c->argc != 2) {
addReplyErrorObject(c,shared.syntaxerr);
return;
Expand Down Expand Up @@ -3272,6 +3327,50 @@ NULL
/* If this client has to be closed, flag it as CLOSE_AFTER_REPLY
* only after we queued the reply to its output buffers. */
if (close_this_client) c->flags |= CLIENT_CLOSE_AFTER_REPLY;
} else if (!strcasecmp(c->argv[1]->ptr, "count")) {
int type = -1;
user *user = NULL;
long long count = 0;
if (c->argc == 4 && !strcasecmp(c->argv[2]->ptr,"type")) {
type = getClientTypeByName(c->argv[3]->ptr);
if (type == -1) {
addReplyErrorFormat(c, "Unknown client type '%s'",
(char *) c->argv[3]->ptr);
return;
}
count = getAllClientsCount(type);
addReplyLongLong(c,count);
return;
}else if (c->argc == 3) {
user = ACLGetUserByName(c->argv[2]->ptr,
sdslen(c->argv[2]->ptr));
if (user == NULL) {
addReplyErrorFormat(c, "No such user '%s'",
(char *) c->argv[2]->ptr);
return;
}
count = listLength(user->clients);
addReplyLongLong(c, count);
return;
}else if (c->argc > 3 && !strcasecmp(c->argv[2]->ptr, "id")) {
int j;
int arrLen = c->argc - 3;
addReplyArrayLen(c,arrLen);
for (j = 0; j < arrLen; j++) {
long long cid;
if (getLongLongFromObjectOrReply(c, c->argv[j+3],&cid,NULL) != C_OK) return;
client *cl = lookupClientByID(cid);
if (cl) {
count = listLength(cl->user->clients);
addReplyLongLong(c, count);
} else {
addReplyLongLong(c, 0);
}
}
} else {
addReplyErrorObject(c, shared.syntaxerr);
return;
}
} else if (!strcasecmp(c->argv[1]->ptr,"unblock") && (c->argc == 3 ||
c->argc == 4))
{
Expand Down
2 changes: 2 additions & 0 deletions src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,7 @@ typedef struct {
against. This list will always contain at least
one selector for backwards compatibility. */
robj *acl_string; /* cached string represent of ACLs */
list *clients; /* A list of clients associated with this user. */
} user;

/* With multiplexing we need to take per-client state.
Expand Down Expand Up @@ -1230,6 +1231,7 @@ typedef struct client {
sds peerid; /* Cached peer ID. */
sds sockname; /* Cached connection target address. */
listNode *client_list_node; /* list node in client list */
listNode *user_client_node; /* list node in user->client list */
listNode *postponed_list_node; /* list node within the postponed list */
listNode *pending_read_list_node; /* list node in clients pending read list */
void *module_blocked_client; /* Pointer to the ValkeyModuleBlockedClient associated with this
Expand Down
17 changes: 13 additions & 4 deletions tests/unit/acl.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,22 @@ start_server {tags {"acl external:skip"}} {

assert_equal {0} [r PUBLISH foo bar]
catch {r PUBLISH bar game} e

# Falling back to psuser for the below tests
r AUTH psuser pspass
r ACL deluser hpuser
set e
} {*NOPERM*channel*}

test {client list users} {
set rd [valkey_deferring_client]
$rd AUTH psuser pspass
$rd read
r AUTH hpuser pass
$rd client list
assert {[llength [$rd read]] >= 2}
$rd client list user psuser
assert {[llength [$rd read]] >= 1}
assert_equal 1 [r client count hpuser]
r AUTH psuser pspass
r ACL deluser hpuser
}
test {In transaction queue publish/subscribe/psubscribe to unauthorized channel will fail} {
r ACL setuser psuser +multi +discard
r MULTI
Expand Down