summaryrefslogtreecommitdiffstats
path: root/tests/modules/auth.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:40:54 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:40:54 +0000
commit317c0644ccf108aa23ef3fd8358bd66c2840bfc0 (patch)
treec417b3d25c86b775989cb5ac042f37611b626c8a /tests/modules/auth.c
parentInitial commit. (diff)
downloadredis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.tar.xz
redis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.zip
Adding upstream version 5:7.2.4.upstream/5%7.2.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/modules/auth.c')
-rw-r--r--tests/modules/auth.c270
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;
+}