diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:40:54 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:40:54 +0000 |
commit | 317c0644ccf108aa23ef3fd8358bd66c2840bfc0 (patch) | |
tree | c417b3d25c86b775989cb5ac042f37611b626c8a /tests/modules/auth.c | |
parent | Initial commit. (diff) | |
download | redis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.tar.xz redis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.zip |
Adding upstream version 5:7.2.4.upstream/5%7.2.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/modules/auth.c')
-rw-r--r-- | tests/modules/auth.c | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/tests/modules/auth.c b/tests/modules/auth.c new file mode 100644 index 0000000..19be95a --- /dev/null +++ b/tests/modules/auth.c @@ -0,0 +1,270 @@ +/* define macros for having usleep */ +#define _BSD_SOURCE +#define _DEFAULT_SOURCE + +#include "redismodule.h" + +#include <string.h> +#include <unistd.h> +#include <pthread.h> + +#define UNUSED(V) ((void) V) + +// A simple global user +static RedisModuleUser *global = NULL; +static long long client_change_delta = 0; + +void UserChangedCallback(uint64_t client_id, void *privdata) { + REDISMODULE_NOT_USED(privdata); + REDISMODULE_NOT_USED(client_id); + client_change_delta++; +} + +int Auth_CreateModuleUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + if (global) { + RedisModule_FreeModuleUser(global); + } + + global = RedisModule_CreateModuleUser("global"); + RedisModule_SetModuleUserACL(global, "allcommands"); + RedisModule_SetModuleUserACL(global, "allkeys"); + RedisModule_SetModuleUserACL(global, "on"); + + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + +int Auth_AuthModuleUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + uint64_t client_id; + RedisModule_AuthenticateClientWithUser(ctx, global, UserChangedCallback, NULL, &client_id); + + return RedisModule_ReplyWithLongLong(ctx, (uint64_t) client_id); +} + +int Auth_AuthRealUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + + size_t length; + uint64_t client_id; + + RedisModuleString *user_string = argv[1]; + const char *name = RedisModule_StringPtrLen(user_string, &length); + + if (RedisModule_AuthenticateClientWithACLUser(ctx, name, length, + UserChangedCallback, NULL, &client_id) == REDISMODULE_ERR) { + return RedisModule_ReplyWithError(ctx, "Invalid user"); + } + + return RedisModule_ReplyWithLongLong(ctx, (uint64_t) client_id); +} + +/* This command redacts every other arguments and returns OK */ +int Auth_RedactedAPI(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + for(int i = argc - 1; i > 0; i -= 2) { + int result = RedisModule_RedactClientCommandArgument(ctx, i); + RedisModule_Assert(result == REDISMODULE_OK); + } + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + +int Auth_ChangeCount(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + long long result = client_change_delta; + client_change_delta = 0; + return RedisModule_ReplyWithLongLong(ctx, result); +} + +/* The Module functionality below validates that module authentication callbacks can be registered + * to support both non-blocking and blocking module based authentication. */ + +/* Non Blocking Module Auth callback / implementation. */ +int auth_cb(RedisModuleCtx *ctx, RedisModuleString *username, RedisModuleString *password, RedisModuleString **err) { + const char *user = RedisModule_StringPtrLen(username, NULL); + const char *pwd = RedisModule_StringPtrLen(password, NULL); + if (!strcmp(user,"foo") && !strcmp(pwd,"allow")) { + RedisModule_AuthenticateClientWithACLUser(ctx, "foo", 3, NULL, NULL, NULL); + return REDISMODULE_AUTH_HANDLED; + } + else if (!strcmp(user,"foo") && !strcmp(pwd,"deny")) { + RedisModuleString *log = RedisModule_CreateString(ctx, "Module Auth", 11); + RedisModule_ACLAddLogEntryByUserName(ctx, username, log, REDISMODULE_ACL_LOG_AUTH); + RedisModule_FreeString(ctx, log); + const char *err_msg = "Auth denied by Misc Module."; + *err = RedisModule_CreateString(ctx, err_msg, strlen(err_msg)); + return REDISMODULE_AUTH_HANDLED; + } + return REDISMODULE_AUTH_NOT_HANDLED; +} + +int test_rm_register_auth_cb(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + RedisModule_RegisterAuthCallback(ctx, auth_cb); + RedisModule_ReplyWithSimpleString(ctx, "OK"); + return REDISMODULE_OK; +} + +/* + * The thread entry point that actually executes the blocking part of the AUTH command. + * This function sleeps for 0.5 seconds and then unblocks the client which will later call + * `AuthBlock_Reply`. + * `arg` is expected to contain the RedisModuleBlockedClient, username, and password. + */ +void *AuthBlock_ThreadMain(void *arg) { + usleep(500000); + void **targ = arg; + RedisModuleBlockedClient *bc = targ[0]; + int result = 2; + const char *user = RedisModule_StringPtrLen(targ[1], NULL); + const char *pwd = RedisModule_StringPtrLen(targ[2], NULL); + if (!strcmp(user,"foo") && !strcmp(pwd,"block_allow")) { + result = 1; + } + else if (!strcmp(user,"foo") && !strcmp(pwd,"block_deny")) { + result = 0; + } + else if (!strcmp(user,"foo") && !strcmp(pwd,"block_abort")) { + RedisModule_BlockedClientMeasureTimeEnd(bc); + RedisModule_AbortBlock(bc); + goto cleanup; + } + /* Provide the result to the blocking reply cb. */ + void **replyarg = RedisModule_Alloc(sizeof(void*)); + replyarg[0] = (void *) (uintptr_t) result; + RedisModule_BlockedClientMeasureTimeEnd(bc); + RedisModule_UnblockClient(bc, replyarg); +cleanup: + /* Free the username and password and thread / arg data. */ + RedisModule_FreeString(NULL, targ[1]); + RedisModule_FreeString(NULL, targ[2]); + RedisModule_Free(targ); + return NULL; +} + +/* + * Reply callback for a blocking AUTH command. This is called when the client is unblocked. + */ +int AuthBlock_Reply(RedisModuleCtx *ctx, RedisModuleString *username, RedisModuleString *password, RedisModuleString **err) { + REDISMODULE_NOT_USED(password); + void **targ = RedisModule_GetBlockedClientPrivateData(ctx); + int result = (uintptr_t) targ[0]; + size_t userlen = 0; + const char *user = RedisModule_StringPtrLen(username, &userlen); + /* Handle the success case by authenticating. */ + if (result == 1) { + RedisModule_AuthenticateClientWithACLUser(ctx, user, userlen, NULL, NULL, NULL); + return REDISMODULE_AUTH_HANDLED; + } + /* Handle the Error case by denying auth */ + else if (result == 0) { + RedisModuleString *log = RedisModule_CreateString(ctx, "Module Auth", 11); + RedisModule_ACLAddLogEntryByUserName(ctx, username, log, REDISMODULE_ACL_LOG_AUTH); + RedisModule_FreeString(ctx, log); + const char *err_msg = "Auth denied by Misc Module."; + *err = RedisModule_CreateString(ctx, err_msg, strlen(err_msg)); + return REDISMODULE_AUTH_HANDLED; + } + /* "Skip" Authentication */ + return REDISMODULE_AUTH_NOT_HANDLED; +} + +/* Private data freeing callback for Module Auth. */ +void AuthBlock_FreeData(RedisModuleCtx *ctx, void *privdata) { + REDISMODULE_NOT_USED(ctx); + RedisModule_Free(privdata); +} + +/* Callback triggered when the engine attempts module auth + * Return code here is one of the following: Auth succeeded, Auth denied, + * Auth not handled, Auth blocked. + * The Module can have auth succeed / denied here itself, but this is an example + * of blocking module auth. + */ +int blocking_auth_cb(RedisModuleCtx *ctx, RedisModuleString *username, RedisModuleString *password, RedisModuleString **err) { + REDISMODULE_NOT_USED(username); + REDISMODULE_NOT_USED(password); + REDISMODULE_NOT_USED(err); + /* Block the client from the Module. */ + RedisModuleBlockedClient *bc = RedisModule_BlockClientOnAuth(ctx, AuthBlock_Reply, AuthBlock_FreeData); + int ctx_flags = RedisModule_GetContextFlags(ctx); + if (ctx_flags & REDISMODULE_CTX_FLAGS_MULTI || ctx_flags & REDISMODULE_CTX_FLAGS_LUA) { + /* Clean up by using RedisModule_UnblockClient since we attempted blocking the client. */ + RedisModule_UnblockClient(bc, NULL); + return REDISMODULE_AUTH_HANDLED; + } + RedisModule_BlockedClientMeasureTimeStart(bc); + pthread_t tid; + /* Allocate memory for information needed. */ + void **targ = RedisModule_Alloc(sizeof(void*)*3); + targ[0] = bc; + targ[1] = RedisModule_CreateStringFromString(NULL, username); + targ[2] = RedisModule_CreateStringFromString(NULL, password); + /* Create bg thread and pass the blockedclient, username and password to it. */ + if (pthread_create(&tid, NULL, AuthBlock_ThreadMain, targ) != 0) { + RedisModule_AbortBlock(bc); + } + return REDISMODULE_AUTH_HANDLED; +} + +int test_rm_register_blocking_auth_cb(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + RedisModule_RegisterAuthCallback(ctx, blocking_auth_cb); + RedisModule_ReplyWithSimpleString(ctx, "OK"); + return REDISMODULE_OK; +} + +/* This function must be present on each Redis module. It is used in order to + * register the commands into the Redis server. */ +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + if (RedisModule_Init(ctx,"testacl",1,REDISMODULE_APIVER_1) + == REDISMODULE_ERR) return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"auth.authrealuser", + Auth_AuthRealUser,"no-auth",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"auth.createmoduleuser", + Auth_CreateModuleUser,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"auth.authmoduleuser", + Auth_AuthModuleUser,"no-auth",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"auth.changecount", + Auth_ChangeCount,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"auth.redact", + Auth_RedactedAPI,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"testmoduleone.rm_register_auth_cb", + test_rm_register_auth_cb,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"testmoduleone.rm_register_blocking_auth_cb", + test_rm_register_blocking_auth_cb,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + return REDISMODULE_OK; +} + +int RedisModule_OnUnload(RedisModuleCtx *ctx) { + UNUSED(ctx); + + if (global) + RedisModule_FreeModuleUser(global); + + return REDISMODULE_OK; +} |