summaryrefslogtreecommitdiffstats
path: root/tests/modules/aclcheck.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/modules/aclcheck.c269
1 files changed, 269 insertions, 0 deletions
diff --git a/tests/modules/aclcheck.c b/tests/modules/aclcheck.c
new file mode 100644
index 0000000..09b525c
--- /dev/null
+++ b/tests/modules/aclcheck.c
@@ -0,0 +1,269 @@
+
+#include "redismodule.h"
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+#include <strings.h>
+
+/* A wrap for SET command with ACL check on the key. */
+int set_aclcheck_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ if (argc < 4) {
+ return RedisModule_WrongArity(ctx);
+ }
+
+ int permissions;
+ const char *flags = RedisModule_StringPtrLen(argv[1], NULL);
+
+ if (!strcasecmp(flags, "W")) {
+ permissions = REDISMODULE_CMD_KEY_UPDATE;
+ } else if (!strcasecmp(flags, "R")) {
+ permissions = REDISMODULE_CMD_KEY_ACCESS;
+ } else if (!strcasecmp(flags, "*")) {
+ permissions = REDISMODULE_CMD_KEY_UPDATE | REDISMODULE_CMD_KEY_ACCESS;
+ } else if (!strcasecmp(flags, "~")) {
+ permissions = 0; /* Requires either read or write */
+ } else {
+ RedisModule_ReplyWithError(ctx, "INVALID FLAGS");
+ return REDISMODULE_OK;
+ }
+
+ /* Check that the key can be accessed */
+ RedisModuleString *user_name = RedisModule_GetCurrentUserName(ctx);
+ RedisModuleUser *user = RedisModule_GetModuleUserFromUserName(user_name);
+ int ret = RedisModule_ACLCheckKeyPermissions(user, argv[2], permissions);
+ if (ret != 0) {
+ RedisModule_ReplyWithError(ctx, "DENIED KEY");
+ RedisModule_FreeModuleUser(user);
+ RedisModule_FreeString(ctx, user_name);
+ return REDISMODULE_OK;
+ }
+
+ RedisModuleCallReply *rep = RedisModule_Call(ctx, "SET", "v", argv + 2, argc - 2);
+ if (!rep) {
+ RedisModule_ReplyWithError(ctx, "NULL reply returned");
+ } else {
+ RedisModule_ReplyWithCallReply(ctx, rep);
+ RedisModule_FreeCallReply(rep);
+ }
+
+ RedisModule_FreeModuleUser(user);
+ RedisModule_FreeString(ctx, user_name);
+ return REDISMODULE_OK;
+}
+
+/* A wrap for PUBLISH command with ACL check on the channel. */
+int publish_aclcheck_channel(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ if (argc != 3) {
+ return RedisModule_WrongArity(ctx);
+ }
+
+ /* Check that the pubsub channel can be accessed */
+ RedisModuleString *user_name = RedisModule_GetCurrentUserName(ctx);
+ RedisModuleUser *user = RedisModule_GetModuleUserFromUserName(user_name);
+ int ret = RedisModule_ACLCheckChannelPermissions(user, argv[1], REDISMODULE_CMD_CHANNEL_SUBSCRIBE);
+ if (ret != 0) {
+ RedisModule_ReplyWithError(ctx, "DENIED CHANNEL");
+ RedisModule_FreeModuleUser(user);
+ RedisModule_FreeString(ctx, user_name);
+ return REDISMODULE_OK;
+ }
+
+ RedisModuleCallReply *rep = RedisModule_Call(ctx, "PUBLISH", "v", argv + 1, argc - 1);
+ if (!rep) {
+ RedisModule_ReplyWithError(ctx, "NULL reply returned");
+ } else {
+ RedisModule_ReplyWithCallReply(ctx, rep);
+ RedisModule_FreeCallReply(rep);
+ }
+
+ RedisModule_FreeModuleUser(user);
+ RedisModule_FreeString(ctx, user_name);
+ return REDISMODULE_OK;
+}
+
+/* A wrap for RM_Call that check first that the command can be executed */
+int rm_call_aclcheck_cmd(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleString **argv, int argc) {
+ if (argc < 2) {
+ return RedisModule_WrongArity(ctx);
+ }
+
+ /* Check that the command can be executed */
+ int ret = RedisModule_ACLCheckCommandPermissions(user, argv + 1, argc - 1);
+ if (ret != 0) {
+ RedisModule_ReplyWithError(ctx, "DENIED CMD");
+ /* Add entry to ACL log */
+ RedisModule_ACLAddLogEntry(ctx, user, argv[1], REDISMODULE_ACL_LOG_CMD);
+ return REDISMODULE_OK;
+ }
+
+ const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
+
+ RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "v", argv + 2, argc - 2);
+ if(!rep){
+ RedisModule_ReplyWithError(ctx, "NULL reply returned");
+ }else{
+ RedisModule_ReplyWithCallReply(ctx, rep);
+ RedisModule_FreeCallReply(rep);
+ }
+
+ return REDISMODULE_OK;
+}
+
+int rm_call_aclcheck_cmd_default_user(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ RedisModuleString *user_name = RedisModule_GetCurrentUserName(ctx);
+ RedisModuleUser *user = RedisModule_GetModuleUserFromUserName(user_name);
+
+ int res = rm_call_aclcheck_cmd(ctx, user, argv, argc);
+
+ RedisModule_FreeModuleUser(user);
+ RedisModule_FreeString(ctx, user_name);
+ return res;
+}
+
+int rm_call_aclcheck_cmd_module_user(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ /* Create a user and authenticate */
+ RedisModuleUser *user = RedisModule_CreateModuleUser("testuser1");
+ RedisModule_SetModuleUserACL(user, "allcommands");
+ RedisModule_SetModuleUserACL(user, "allkeys");
+ RedisModule_SetModuleUserACL(user, "on");
+ RedisModule_AuthenticateClientWithUser(ctx, user, NULL, NULL, NULL);
+
+ int res = rm_call_aclcheck_cmd(ctx, user, argv, argc);
+
+ /* authenticated back to "default" user (so once we free testuser1 we will not disconnected */
+ RedisModule_AuthenticateClientWithACLUser(ctx, "default", 7, NULL, NULL, NULL);
+ RedisModule_FreeModuleUser(user);
+ return res;
+}
+
+int rm_call_aclcheck_with_errors(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ if(argc < 2){
+ return RedisModule_WrongArity(ctx);
+ }
+
+ const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
+
+ RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "vEC", argv + 2, argc - 2);
+ RedisModule_ReplyWithCallReply(ctx, rep);
+ RedisModule_FreeCallReply(rep);
+ return REDISMODULE_OK;
+}
+
+/* A wrap for RM_Call that pass the 'C' flag to do ACL check on the command. */
+int rm_call_aclcheck(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ if(argc < 2){
+ return RedisModule_WrongArity(ctx);
+ }
+
+ const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
+
+ RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "vC", argv + 2, argc - 2);
+ if(!rep) {
+ char err[100];
+ switch (errno) {
+ case EACCES:
+ RedisModule_ReplyWithError(ctx, "ERR NOPERM");
+ break;
+ default:
+ snprintf(err, sizeof(err) - 1, "ERR errno=%d", errno);
+ RedisModule_ReplyWithError(ctx, err);
+ break;
+ }
+ } else {
+ RedisModule_ReplyWithCallReply(ctx, rep);
+ RedisModule_FreeCallReply(rep);
+ }
+
+ return REDISMODULE_OK;
+}
+
+int module_test_acl_category(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+ RedisModule_ReplyWithSimpleString(ctx, "OK");
+ return REDISMODULE_OK;
+}
+
+int commandBlockCheck(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+ int response_ok = 0;
+ int result = RedisModule_CreateCommand(ctx,"command.that.should.fail", module_test_acl_category, "", 0, 0, 0);
+ response_ok |= (result == REDISMODULE_OK);
+
+ RedisModuleCommand *parent = RedisModule_GetCommand(ctx,"block.commands.outside.onload");
+ result = RedisModule_SetCommandACLCategories(parent, "write");
+ response_ok |= (result == REDISMODULE_OK);
+
+ result = RedisModule_CreateSubcommand(parent,"subcommand.that.should.fail",module_test_acl_category,"",0,0,0);
+ response_ok |= (result == REDISMODULE_OK);
+
+ /* This validates that it's not possible to create commands outside OnLoad,
+ * thus returns an error if they succeed. */
+ if (response_ok) {
+ RedisModule_ReplyWithError(ctx, "UNEXPECTEDOK");
+ } else {
+ RedisModule_ReplyWithSimpleString(ctx, "OK");
+ }
+ return REDISMODULE_OK;
+}
+
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ if (RedisModule_Init(ctx,"aclcheck",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"aclcheck.set.check.key", set_aclcheck_key,"write",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"block.commands.outside.onload", commandBlockCheck,"write",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"aclcheck.module.command.aclcategories.write", module_test_acl_category,"write",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+ RedisModuleCommand *aclcategories_write = RedisModule_GetCommand(ctx,"aclcheck.module.command.aclcategories.write");
+
+ if (RedisModule_SetCommandACLCategories(aclcategories_write, "write") == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"aclcheck.module.command.aclcategories.write.function.read.category", module_test_acl_category,"write",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+ RedisModuleCommand *read_category = RedisModule_GetCommand(ctx,"aclcheck.module.command.aclcategories.write.function.read.category");
+
+ if (RedisModule_SetCommandACLCategories(read_category, "read") == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"aclcheck.module.command.aclcategories.read.only.category", module_test_acl_category,"",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+ RedisModuleCommand *read_only_category = RedisModule_GetCommand(ctx,"aclcheck.module.command.aclcategories.read.only.category");
+
+ if (RedisModule_SetCommandACLCategories(read_only_category, "read") == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"aclcheck.publish.check.channel", publish_aclcheck_channel,"",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"aclcheck.rm_call.check.cmd", rm_call_aclcheck_cmd_default_user,"",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"aclcheck.rm_call.check.cmd.module.user", rm_call_aclcheck_cmd_module_user,"",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"aclcheck.rm_call", rm_call_aclcheck,
+ "write",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"aclcheck.rm_call_with_errors", rm_call_aclcheck_with_errors,
+ "write",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ return REDISMODULE_OK;
+}