Skip to content

Commit f8a5a4f

Browse files
Custom authentication for Modules (#11659)
This change adds new module callbacks that can override the default password based authentication associated with ACLs. With this, Modules can register auth callbacks through which they can implement their own Authentication logic. When `AUTH` and `HELLO AUTH ...` commands are used, Module based authentication is attempted and then normal password based authentication is attempted if needed. The new Module APIs added in this PR are - `RM_RegisterCustomAuthCallback` and `RM_BlockClientOnAuth` and `RedisModule_ACLAddLogEntryByUserName `. Module based authentication will be attempted for all Redis users (created through the ACL SETUSER cmd or through Module APIs) even if the Redis user does not exist at the time of the command. This gives a chance for the Module to create the RedisModule user and then authenticate via the RedisModule API - from the custom auth callback. For the AUTH command, we will support both variations - `AUTH <username> <password>` and `AUTH <password>`. In case of the `AUTH <password>` variation, the custom auth callbacks are triggered with “default” as the username and password as what is provided. ### RedisModule_RegisterCustomAuthCallback ``` void RM_RegisterCustomAuthCallback(RedisModuleCtx *ctx, RedisModuleCustomAuthCallback cb) { ``` This API registers a callback to execute to prior to normal password based authentication. Multiple callbacks can be registered across different modules. These callbacks are responsible for either handling the authentication, each authenticating the user or explicitly denying, or deferring it to other authentication mechanisms. Callbacks are triggered in the order they were registered. When a Module is unloaded, all the auth callbacks registered by it are unregistered. The callbacks are attempted, in the order of most recently registered callbacks, when the AUTH/HELLO (with AUTH field is provided) commands are called. The callbacks will be called with a module context along with a username and a password, and are expected to take one of the following actions: (1) Authenticate - Use the RM_Authenticate* API successfully and return `REDISMODULE_AUTH_HANDLED`. This will immediately end the auth chain as successful and add the OK reply. (2) Block a client on authentication - Use the `RM_BlockClientOnAuth` API and return `REDISMODULE_AUTH_HANDLED`. Here, the client will be blocked until the `RM_UnblockClient `API is used which will trigger the auth reply callback (provided earlier through the `RM_BlockClientOnAuth`). In this reply callback, the Module should authenticate, deny or skip handling authentication. (3) Deny Authentication - Return `REDISMODULE_AUTH_HANDLED` without authenticating or blocking the client. Optionally, `err` can be set to a custom error message. This will immediately end the auth chain as unsuccessful and add the ERR reply. (4) Skip handling Authentication - Return `REDISMODULE_AUTH_NOT_HANDLED` without blocking the client. This will allow the engine to attempt the next custom auth callback. If none of the callbacks authenticate or deny auth, then password based auth is attempted and will authenticate or add failure logs and reply to the clients accordingly. ### RedisModule_BlockClientOnAuth ``` RedisModuleBlockedClient *RM_BlockClientOnAuth(RedisModuleCtx *ctx, RedisModuleCustomAuthCallback reply_callback, void (*free_privdata)(RedisModuleCtx*,void*)) ``` This API can only be used from a Module from the custom auth callback. If a client is not in the middle of custom module based authentication, ERROR is returned. Otherwise, the client is blocked and the `RedisModule_BlockedClient` is returned similar to the `RedisModule_BlockClient` API. ### RedisModule_ACLAddLogEntryByUserName ``` int RM_ACLAddLogEntryByUserName(RedisModuleCtx *ctx, RedisModuleString *username, RedisModuleString *object, RedisModuleACLLogEntryReason reason) ``` Adds a new entry in the ACL log with the `username` RedisModuleString provided. This simplifies the Module usage because now, developers do not need to create a Module User just to add an error ACL Log entry. Aside from accepting username (RedisModuleString) instead of a RedisModuleUser, it is the same as the existing `RedisModule_ACLAddLogEntry` API. ### Breaking changes - HELLO command - Clients can now only set the client name and RESP protocol from the `HELLO` command if they are authenticated. Also, we now finish command arg validation first and return early with a ERR reply if any arg is invalid. This is to avoid mutating the client name / RESP from a command that would have failed on invalid arguments. ### Notable behaviors - Module unblocking - Now, we will not allow Modules to block the client from inside the context of a reply callback (triggered from the Module unblock flow `moduleHandleBlockedClients`). --------- Co-authored-by: Madelyn Olson <[email protected]>
1 parent 58285a6 commit f8a5a4f

File tree

11 files changed

+1057
-103
lines changed

11 files changed

+1057
-103
lines changed

runtest-moduleapi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,5 @@ $TCLSH tests/test_helper.tcl \
5252
--single unit/moduleapi/publish \
5353
--single unit/moduleapi/usercall \
5454
--single unit/moduleapi/postnotifications \
55+
--single unit/moduleapi/moduleauth \
5556
"${@}"

src/acl.c

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,24 +1406,50 @@ int ACLCheckUserCredentials(robj *username, robj *password) {
14061406
return C_ERR;
14071407
}
14081408

1409+
/* If `err` is provided, this is added as an error reply to the client.
1410+
* Otherwise, the standard Auth error is added as a reply. */
1411+
void addAuthErrReply(client *c, robj *err) {
1412+
if (clientHasPendingReplies(c)) return;
1413+
if (!err) {
1414+
addReplyError(c, "-WRONGPASS invalid username-password pair or user is disabled.");
1415+
return;
1416+
}
1417+
addReplyError(c, err->ptr);
1418+
}
1419+
14091420
/* This is like ACLCheckUserCredentials(), however if the user/pass
14101421
* are correct, the connection is put in authenticated state and the
14111422
* connection user reference is populated.
14121423
*
1413-
* The return value is C_OK or C_ERR with the same meaning as
1414-
* ACLCheckUserCredentials(). */
1415-
int ACLAuthenticateUser(client *c, robj *username, robj *password) {
1424+
* The return value is AUTH_OK on success (valid username / password pair) & AUTH_ERR otherwise. */
1425+
int checkPasswordBasedAuth(client *c, robj *username, robj *password) {
14161426
if (ACLCheckUserCredentials(username,password) == C_OK) {
14171427
c->authenticated = 1;
14181428
c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr));
14191429
moduleNotifyUserChanged(c);
1420-
return C_OK;
1430+
return AUTH_OK;
14211431
} else {
14221432
addACLLogEntry(c,ACL_DENIED_AUTH,(c->flags & CLIENT_MULTI) ? ACL_LOG_CTX_MULTI : ACL_LOG_CTX_TOPLEVEL,0,username->ptr,NULL);
1423-
return C_ERR;
1433+
return AUTH_ERR;
14241434
}
14251435
}
14261436

1437+
/* Attempt authenticating the user - first through module based authentication,
1438+
* and then, if needed, with normal password based authentication.
1439+
* Returns one of the following codes:
1440+
* AUTH_OK - Indicates that authentication succeeded.
1441+
* AUTH_ERR - Indicates that authentication failed.
1442+
* AUTH_BLOCKED - Indicates module authentication is in progress through a blocking implementation.
1443+
*/
1444+
int ACLAuthenticateUser(client *c, robj *username, robj *password, robj **err) {
1445+
int result = checkModuleAuthentication(c, username, password, err);
1446+
/* If authentication was not handled by any Module, attempt normal password based auth. */
1447+
if (result == AUTH_NOT_HANDLED) {
1448+
result = checkPasswordBasedAuth(c, username, password);
1449+
}
1450+
return result;
1451+
}
1452+
14271453
/* For ACL purposes, every user has a bitmap with the commands that such
14281454
* user is allowed to execute. In order to populate the bitmap, every command
14291455
* should have an assigned ID (that is used to index the bitmap). This function
@@ -3046,11 +3072,14 @@ void authCommand(client *c) {
30463072
redactClientCommandArgument(c, 2);
30473073
}
30483074

3049-
if (ACLAuthenticateUser(c,username,password) == C_OK) {
3050-
addReply(c,shared.ok);
3051-
} else {
3052-
addReplyError(c,"-WRONGPASS invalid username-password pair or user is disabled.");
3075+
robj *err = NULL;
3076+
int result = ACLAuthenticateUser(c, username, password, &err);
3077+
if (result == AUTH_OK) {
3078+
addReply(c, shared.ok);
3079+
} else if (result == AUTH_ERR) {
3080+
addAuthErrReply(c, err);
30533081
}
3082+
if (err) decrRefCount(err);
30543083
}
30553084

30563085
/* Set the password for the "default" ACL user. This implements supports for

src/module.c

Lines changed: 297 additions & 71 deletions
Large diffs are not rendered by default.

src/networking.c

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ client *createClient(connection *conn) {
207207
c->client_tracking_prefixes = NULL;
208208
c->last_memory_usage = 0;
209209
c->last_memory_type = CLIENT_TYPE_NORMAL;
210+
c->module_blocked_client = NULL;
211+
c->module_auth_ctx = NULL;
210212
c->auth_callback = NULL;
211213
c->auth_callback_privdata = NULL;
212214
c->auth_module = NULL;
@@ -1534,6 +1536,9 @@ void freeClient(client *c) {
15341536
/* Notify module system that this client auth status changed. */
15351537
moduleNotifyUserChanged(c);
15361538

1539+
/* Free the RedisModuleBlockedClient held onto for reprocessing if not already freed. */
1540+
zfree(c->module_blocked_client);
1541+
15371542
/* If this client was scheduled for async freeing we need to remove it
15381543
* from the queue. Note that we need to do this here, because later
15391544
* we may call replicationCacheMaster() and the client should already
@@ -2809,27 +2814,39 @@ sds getAllClientsInfoString(int type) {
28092814
return o;
28102815
}
28112816

2812-
/* Returns C_OK if the name has been set or C_ERR if the name is invalid. */
2813-
int clientSetName(client *c, robj *name) {
2817+
/* Returns C_OK if the name is valid. Returns C_ERR & sets `err` (when provided) otherwise. */
2818+
int validateClientName(robj *name, const char **err) {
2819+
const char *err_msg = "Client names cannot contain spaces, newlines or special characters.";
28142820
int len = (name != NULL) ? sdslen(name->ptr) : 0;
2815-
2816-
/* Setting the client name to an empty string actually removes
2817-
* the current name. */
2818-
if (len == 0) {
2819-
if (c->name) decrRefCount(c->name);
2820-
c->name = NULL;
2821+
/* We allow setting the client name to an empty string. */
2822+
if (len == 0)
28212823
return C_OK;
2822-
}
2823-
28242824
/* Otherwise check if the charset is ok. We need to do this otherwise
28252825
* CLIENT LIST format will break. You should always be able to
28262826
* split by space to get the different fields. */
28272827
char *p = name->ptr;
28282828
for (int j = 0; j < len; j++) {
28292829
if (p[j] < '!' || p[j] > '~') { /* ASCII is assumed. */
2830+
if (err) *err = err_msg;
28302831
return C_ERR;
28312832
}
28322833
}
2834+
return C_OK;
2835+
}
2836+
2837+
/* Returns C_OK if the name has been set or C_ERR if the name is invalid. */
2838+
int clientSetName(client *c, robj *name, const char **err) {
2839+
if (validateClientName(name, err) == C_ERR) {
2840+
return C_ERR;
2841+
}
2842+
int len = (name != NULL) ? sdslen(name->ptr) : 0;
2843+
/* Setting the client name to an empty string actually removes
2844+
* the current name. */
2845+
if (len == 0) {
2846+
if (c->name) decrRefCount(c->name);
2847+
c->name = NULL;
2848+
return C_OK;
2849+
}
28332850
if (c->name) decrRefCount(c->name);
28342851
c->name = name;
28352852
incrRefCount(name);
@@ -2846,11 +2863,10 @@ int clientSetName(client *c, robj *name) {
28462863
*
28472864
* This function is also used to implement the HELLO SETNAME option. */
28482865
int clientSetNameOrReply(client *c, robj *name) {
2849-
int result = clientSetName(c, name);
2866+
const char *err = NULL;
2867+
int result = clientSetName(c, name, &err);
28502868
if (result == C_ERR) {
2851-
addReplyError(c,
2852-
"Client names cannot contain spaces, "
2853-
"newlines or special characters.");
2869+
addReplyError(c, err);
28542870
}
28552871
return result;
28562872
}
@@ -3434,26 +3450,46 @@ void helloCommand(client *c) {
34343450
}
34353451
}
34363452

3453+
robj *username = NULL;
3454+
robj *password = NULL;
3455+
robj *clientname = NULL;
34373456
for (int j = next_arg; j < c->argc; j++) {
34383457
int moreargs = (c->argc-1) - j;
34393458
const char *opt = c->argv[j]->ptr;
34403459
if (!strcasecmp(opt,"AUTH") && moreargs >= 2) {
34413460
redactClientCommandArgument(c, j+1);
34423461
redactClientCommandArgument(c, j+2);
3443-
if (ACLAuthenticateUser(c, c->argv[j+1], c->argv[j+2]) == C_ERR) {
3444-
addReplyError(c,"-WRONGPASS invalid username-password pair or user is disabled.");
3445-
return;
3446-
}
3462+
username = c->argv[j+1];
3463+
password = c->argv[j+2];
34473464
j += 2;
34483465
} else if (!strcasecmp(opt,"SETNAME") && moreargs) {
3449-
if (clientSetNameOrReply(c, c->argv[j+1]) == C_ERR) return;
3466+
clientname = c->argv[j+1];
3467+
const char *err = NULL;
3468+
if (validateClientName(clientname, &err) == C_ERR) {
3469+
addReplyError(c, err);
3470+
return;
3471+
}
34503472
j++;
34513473
} else {
34523474
addReplyErrorFormat(c,"Syntax error in HELLO option '%s'",opt);
34533475
return;
34543476
}
34553477
}
34563478

3479+
if (username && password) {
3480+
robj *err = NULL;
3481+
int auth_result = ACLAuthenticateUser(c, username, password, &err);
3482+
if (auth_result == AUTH_ERR) {
3483+
addAuthErrReply(c, err);
3484+
}
3485+
if (err) decrRefCount(err);
3486+
/* In case of auth errors, return early since we already replied with an ERR.
3487+
* In case of blocking module auth, we reply to the client/setname later upon unblocking. */
3488+
if (auth_result == AUTH_ERR || auth_result == AUTH_BLOCKED) {
3489+
return;
3490+
}
3491+
}
3492+
34573493
/* At this point we need to be authenticated to continue. */
34583494
if (!c->authenticated) {
34593495
addReplyError(c,"-NOAUTH HELLO must be called with the client already "
@@ -3463,6 +3499,9 @@ void helloCommand(client *c) {
34633499
return;
34643500
}
34653501

3502+
/* Now that we're authenticated, set the client name. */
3503+
if (clientname) clientSetName(c, clientname, NULL);
3504+
34663505
/* Let's switch to the specified RESP mode. */
34673506
if (ver) c->resp = ver;
34683507
addReplyMapLen(c,6 + !server.sentinel_mode);

src/redismodule.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ typedef long long ustime_t;
3434
#define REDISMODULE_OK 0
3535
#define REDISMODULE_ERR 1
3636

37+
/* Module Based Authentication status return values. */
38+
#define REDISMODULE_AUTH_HANDLED 0
39+
#define REDISMODULE_AUTH_NOT_HANDLED 1
40+
3741
/* API versions. */
3842
#define REDISMODULE_APIVER_1 1
3943

@@ -912,6 +916,7 @@ typedef int (*RedisModuleConfigSetNumericFunc)(const char *name, long long val,
912916
typedef int (*RedisModuleConfigSetBoolFunc)(const char *name, int val, void *privdata, RedisModuleString **err);
913917
typedef int (*RedisModuleConfigSetEnumFunc)(const char *name, int val, void *privdata, RedisModuleString **err);
914918
typedef int (*RedisModuleConfigApplyFunc)(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err);
919+
typedef int (*RedisModuleAuthCallback)(RedisModuleCtx *ctx, RedisModuleString *username, RedisModuleString *password, RedisModuleString **err);
915920

916921
typedef struct RedisModuleTypeMethods {
917922
uint64_t version;
@@ -1164,6 +1169,7 @@ REDISMODULE_API RedisModuleString * (*RedisModule_DictPrev)(RedisModuleCtx *ctx,
11641169
REDISMODULE_API int (*RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) REDISMODULE_ATTR;
11651170
REDISMODULE_API int (*RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key) REDISMODULE_ATTR;
11661171
REDISMODULE_API int (*RedisModule_RegisterInfoFunc)(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) REDISMODULE_ATTR;
1172+
REDISMODULE_API void (*RedisModule_RegisterAuthCallback)(RedisModuleCtx *ctx, RedisModuleAuthCallback cb) REDISMODULE_ATTR;
11671173
REDISMODULE_API int (*RedisModule_InfoAddSection)(RedisModuleInfoCtx *ctx, const char *name) REDISMODULE_ATTR;
11681174
REDISMODULE_API int (*RedisModule_InfoBeginDictField)(RedisModuleInfoCtx *ctx, const char *name) REDISMODULE_ATTR;
11691175
REDISMODULE_API int (*RedisModule_InfoEndDictField)(RedisModuleInfoCtx *ctx) REDISMODULE_ATTR;
@@ -1201,6 +1207,7 @@ REDISMODULE_API int (*RedisModule_GetServerVersion)() REDISMODULE_ATTR;
12011207
REDISMODULE_API int (*RedisModule_GetTypeMethodVersion)() REDISMODULE_ATTR;
12021208
REDISMODULE_API void (*RedisModule_Yield)(RedisModuleCtx *ctx, int flags, const char *busy_reply) REDISMODULE_ATTR;
12031209
REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) REDISMODULE_ATTR;
1210+
REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_BlockClientOnAuth)(RedisModuleCtx *ctx, RedisModuleAuthCallback reply_callback, void (*free_privdata)(RedisModuleCtx*,void*)) REDISMODULE_ATTR;
12041211
REDISMODULE_API int (*RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata) REDISMODULE_ATTR;
12051212
REDISMODULE_API int (*RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
12061213
REDISMODULE_API int (*RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
@@ -1264,6 +1271,7 @@ REDISMODULE_API int (*RedisModule_ACLCheckCommandPermissions)(RedisModuleUser *u
12641271
REDISMODULE_API int (*RedisModule_ACLCheckKeyPermissions)(RedisModuleUser *user, RedisModuleString *key, int flags) REDISMODULE_ATTR;
12651272
REDISMODULE_API int (*RedisModule_ACLCheckChannelPermissions)(RedisModuleUser *user, RedisModuleString *ch, int literal) REDISMODULE_ATTR;
12661273
REDISMODULE_API void (*RedisModule_ACLAddLogEntry)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleString *object, RedisModuleACLLogEntryReason reason) REDISMODULE_ATTR;
1274+
REDISMODULE_API void (*RedisModule_ACLAddLogEntryByUserName)(RedisModuleCtx *ctx, RedisModuleString *user, RedisModuleString *object, RedisModuleACLLogEntryReason reason) REDISMODULE_ATTR;
12671275
REDISMODULE_API int (*RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR;
12681276
REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR;
12691277
REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR;
@@ -1506,6 +1514,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
15061514
REDISMODULE_GET_API(DictCompare);
15071515
REDISMODULE_GET_API(DictCompareC);
15081516
REDISMODULE_GET_API(RegisterInfoFunc);
1517+
REDISMODULE_GET_API(RegisterAuthCallback);
15091518
REDISMODULE_GET_API(InfoAddSection);
15101519
REDISMODULE_GET_API(InfoBeginDictField);
15111520
REDISMODULE_GET_API(InfoEndDictField);
@@ -1554,6 +1563,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
15541563
REDISMODULE_GET_API(ThreadSafeContextTryLock);
15551564
REDISMODULE_GET_API(ThreadSafeContextUnlock);
15561565
REDISMODULE_GET_API(BlockClient);
1566+
REDISMODULE_GET_API(BlockClientOnAuth);
15571567
REDISMODULE_GET_API(UnblockClient);
15581568
REDISMODULE_GET_API(IsBlockedReplyRequest);
15591569
REDISMODULE_GET_API(IsBlockedTimeoutRequest);
@@ -1611,6 +1621,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
16111621
REDISMODULE_GET_API(ACLCheckKeyPermissions);
16121622
REDISMODULE_GET_API(ACLCheckChannelPermissions);
16131623
REDISMODULE_GET_API(ACLAddLogEntry);
1624+
REDISMODULE_GET_API(ACLAddLogEntryByUserName);
16141625
REDISMODULE_GET_API(DeauthenticateAndCloseClient);
16151626
REDISMODULE_GET_API(AuthenticateClientWithACLUser);
16161627
REDISMODULE_GET_API(AuthenticateClientWithUser);

src/server.h

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,8 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
392392
scripts even when in OOM */
393393
#define CLIENT_NO_TOUCH (1ULL<<45) /* This client will not touch LFU/LRU stats. */
394394
#define CLIENT_PUSHING (1ULL<<46) /* This client is pushing notifications. */
395+
#define CLIENT_MODULE_AUTH_HAS_RESULT (1ULL<<47) /* Indicates a client in the middle of module based
396+
auth had been authenticated from the Module. */
395397

396398
/* Client block type (btype field in client structure)
397399
* if CLIENT_BLOCKED flag is set. */
@@ -740,6 +742,7 @@ typedef void (*moduleTypeFreeFunc2)(struct RedisModuleKeyOptCtx *ctx, void *valu
740742
typedef size_t (*moduleTypeFreeEffortFunc2)(struct RedisModuleKeyOptCtx *ctx, const void *value);
741743
typedef void (*moduleTypeUnlinkFunc2)(struct RedisModuleKeyOptCtx *ctx, void *value);
742744
typedef void *(*moduleTypeCopyFunc2)(struct RedisModuleKeyOptCtx *ctx, const void *value);
745+
typedef int (*moduleTypeAuthCallback)(struct RedisModuleCtx *ctx, void *username, void *password, const char **err);
743746

744747

745748
/* The module type, which is referenced in each value of a given type, defines
@@ -856,6 +859,9 @@ struct RedisModuleDigest {
856859
memset(mdvar.x,0,sizeof(mdvar.x)); \
857860
} while(0)
858861

862+
/* Macro to check if the client is in the middle of module based authentication. */
863+
#define clientHasModuleAuthInProgress(c) ((c)->module_auth_ctx != NULL)
864+
859865
/* Objects encoding. Some kind of objects like Strings and Hashes can be
860866
* internally represented in multiple ways. The 'encoding' field of the object
861867
* is set to one of this fields for this object. */
@@ -1200,6 +1206,12 @@ typedef struct client {
12001206
listNode *client_list_node; /* list node in client list */
12011207
listNode *postponed_list_node; /* list node within the postponed list */
12021208
listNode *pending_read_list_node; /* list node in clients pending read list */
1209+
void *module_blocked_client; /* Pointer to the RedisModuleBlockedClient associated with this
1210+
* client. This is set in case of module authentication before the
1211+
* unblocked client is reprocessed to handle reply callbacks. */
1212+
void *module_auth_ctx; /* Ongoing / attempted module based auth callback's ctx.
1213+
* This is only tracked within the context of the command attempting
1214+
* authentication. If not NULL, it means module auth is in progress. */
12031215
RedisModuleUserChangedFunc auth_callback; /* Module callback to execute
12041216
* when the authenticated user
12051217
* changes. */
@@ -2467,7 +2479,7 @@ void moduleInitModulesSystem(void);
24672479
void moduleInitModulesSystemLast(void);
24682480
void modulesCron(void);
24692481
int moduleLoad(const char *path, void **argv, int argc, int is_loadex);
2470-
int moduleUnload(sds name);
2482+
int moduleUnload(sds name, const char **errmsg);
24712483
void moduleLoadFromQueue(void);
24722484
int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
24732485
int moduleGetCommandChannelsViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
@@ -2598,7 +2610,7 @@ char *getClientPeerId(client *client);
25982610
char *getClientSockName(client *client);
25992611
sds catClientInfoString(sds s, client *client);
26002612
sds getAllClientsInfoString(int type);
2601-
int clientSetName(client *c, robj *name);
2613+
int clientSetName(client *c, robj *name, const char **err);
26022614
void rewriteClientCommandVector(client *c, int argc, ...);
26032615
void rewriteClientCommandArgument(client *c, int i, robj *newval);
26042616
void replaceClientCommandVector(client *c, int argc, robj **argv);
@@ -2895,8 +2907,18 @@ void ACLInit(void);
28952907
#define ACL_WRITE_PERMISSION (1<<1)
28962908
#define ACL_ALL_PERMISSION (ACL_READ_PERMISSION|ACL_WRITE_PERMISSION)
28972909

2910+
/* Return codes for Authentication functions to indicate the result. */
2911+
typedef enum {
2912+
AUTH_OK = 0,
2913+
AUTH_ERR,
2914+
AUTH_NOT_HANDLED,
2915+
AUTH_BLOCKED
2916+
} AuthResult;
2917+
28982918
int ACLCheckUserCredentials(robj *username, robj *password);
2899-
int ACLAuthenticateUser(client *c, robj *username, robj *password);
2919+
int ACLAuthenticateUser(client *c, robj *username, robj *password, robj **err);
2920+
int checkModuleAuthentication(client *c, robj *username, robj *password, robj **err);
2921+
void addAuthErrReply(client *c, robj *err);
29002922
unsigned long ACLGetCommandID(sds cmdname);
29012923
void ACLClearCommandID(void);
29022924
user *ACLGetUserByName(const char *name, size_t namelen);

tests/modules/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ TEST_MODULES = \
6060
moduleconfigstwo.so \
6161
publish.so \
6262
usercall.so \
63-
postnotifications.so
63+
postnotifications.so \
64+
moduleauthtwo.so
6465

6566
.PHONY: all
6667

0 commit comments

Comments
 (0)