redict/tests/modules/aclcheck.c
Drew DeVault cdd60793d5 tests/modules: fix remaining redis => redict cases
Non-API impacting renames.

Signed-off-by: Drew DeVault <sir@cmpwn.com>
2024-03-25 12:45:47 +01:00

322 lines
13 KiB
C

// SPDX-FileCopyrightText: 2024 Redict Contributors
// SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo <antirez at gmail dot com>
//
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-License-Identifier: LGPL-3.0-only
#include "redictmodule.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(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
if (argc < 4) {
return RedictModule_WrongArity(ctx);
}
int permissions;
const char *flags = RedictModule_StringPtrLen(argv[1], NULL);
if (!strcasecmp(flags, "W")) {
permissions = REDICTMODULE_CMD_KEY_UPDATE;
} else if (!strcasecmp(flags, "R")) {
permissions = REDICTMODULE_CMD_KEY_ACCESS;
} else if (!strcasecmp(flags, "*")) {
permissions = REDICTMODULE_CMD_KEY_UPDATE | REDICTMODULE_CMD_KEY_ACCESS;
} else if (!strcasecmp(flags, "~")) {
permissions = 0; /* Requires either read or write */
} else {
RedictModule_ReplyWithError(ctx, "INVALID FLAGS");
return REDICTMODULE_OK;
}
/* Check that the key can be accessed */
RedictModuleString *user_name = RedictModule_GetCurrentUserName(ctx);
RedictModuleUser *user = RedictModule_GetModuleUserFromUserName(user_name);
int ret = RedictModule_ACLCheckKeyPermissions(user, argv[2], permissions);
if (ret != 0) {
RedictModule_ReplyWithError(ctx, "DENIED KEY");
RedictModule_FreeModuleUser(user);
RedictModule_FreeString(ctx, user_name);
return REDICTMODULE_OK;
}
RedictModuleCallReply *rep = RedictModule_Call(ctx, "SET", "v", argv + 2, argc - 2);
if (!rep) {
RedictModule_ReplyWithError(ctx, "NULL reply returned");
} else {
RedictModule_ReplyWithCallReply(ctx, rep);
RedictModule_FreeCallReply(rep);
}
RedictModule_FreeModuleUser(user);
RedictModule_FreeString(ctx, user_name);
return REDICTMODULE_OK;
}
/* A wrap for PUBLISH command with ACL check on the channel. */
int publish_aclcheck_channel(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
if (argc != 3) {
return RedictModule_WrongArity(ctx);
}
/* Check that the pubsub channel can be accessed */
RedictModuleString *user_name = RedictModule_GetCurrentUserName(ctx);
RedictModuleUser *user = RedictModule_GetModuleUserFromUserName(user_name);
int ret = RedictModule_ACLCheckChannelPermissions(user, argv[1], REDICTMODULE_CMD_CHANNEL_SUBSCRIBE);
if (ret != 0) {
RedictModule_ReplyWithError(ctx, "DENIED CHANNEL");
RedictModule_FreeModuleUser(user);
RedictModule_FreeString(ctx, user_name);
return REDICTMODULE_OK;
}
RedictModuleCallReply *rep = RedictModule_Call(ctx, "PUBLISH", "v", argv + 1, argc - 1);
if (!rep) {
RedictModule_ReplyWithError(ctx, "NULL reply returned");
} else {
RedictModule_ReplyWithCallReply(ctx, rep);
RedictModule_FreeCallReply(rep);
}
RedictModule_FreeModuleUser(user);
RedictModule_FreeString(ctx, user_name);
return REDICTMODULE_OK;
}
/* A wrap for RM_Call that check first that the command can be executed */
int rm_call_aclcheck_cmd(RedictModuleCtx *ctx, RedictModuleUser *user, RedictModuleString **argv, int argc) {
if (argc < 2) {
return RedictModule_WrongArity(ctx);
}
/* Check that the command can be executed */
int ret = RedictModule_ACLCheckCommandPermissions(user, argv + 1, argc - 1);
if (ret != 0) {
RedictModule_ReplyWithError(ctx, "DENIED CMD");
/* Add entry to ACL log */
RedictModule_ACLAddLogEntry(ctx, user, argv[1], REDICTMODULE_ACL_LOG_CMD);
return REDICTMODULE_OK;
}
const char* cmd = RedictModule_StringPtrLen(argv[1], NULL);
RedictModuleCallReply* rep = RedictModule_Call(ctx, cmd, "v", argv + 2, argc - 2);
if(!rep){
RedictModule_ReplyWithError(ctx, "NULL reply returned");
}else{
RedictModule_ReplyWithCallReply(ctx, rep);
RedictModule_FreeCallReply(rep);
}
return REDICTMODULE_OK;
}
int rm_call_aclcheck_cmd_default_user(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
RedictModuleString *user_name = RedictModule_GetCurrentUserName(ctx);
RedictModuleUser *user = RedictModule_GetModuleUserFromUserName(user_name);
int res = rm_call_aclcheck_cmd(ctx, user, argv, argc);
RedictModule_FreeModuleUser(user);
RedictModule_FreeString(ctx, user_name);
return res;
}
int rm_call_aclcheck_cmd_module_user(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
/* Create a user and authenticate */
RedictModuleUser *user = RedictModule_CreateModuleUser("testuser1");
RedictModule_SetModuleUserACL(user, "allcommands");
RedictModule_SetModuleUserACL(user, "allkeys");
RedictModule_SetModuleUserACL(user, "on");
RedictModule_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 */
RedictModule_AuthenticateClientWithACLUser(ctx, "default", 7, NULL, NULL, NULL);
RedictModule_FreeModuleUser(user);
return res;
}
int rm_call_aclcheck_with_errors(RedictModuleCtx *ctx, RedictModuleString **argv, int argc){
REDICTMODULE_NOT_USED(argv);
REDICTMODULE_NOT_USED(argc);
if(argc < 2){
return RedictModule_WrongArity(ctx);
}
const char* cmd = RedictModule_StringPtrLen(argv[1], NULL);
RedictModuleCallReply* rep = RedictModule_Call(ctx, cmd, "vEC", argv + 2, argc - 2);
RedictModule_ReplyWithCallReply(ctx, rep);
RedictModule_FreeCallReply(rep);
return REDICTMODULE_OK;
}
/* A wrap for RM_Call that pass the 'C' flag to do ACL check on the command. */
int rm_call_aclcheck(RedictModuleCtx *ctx, RedictModuleString **argv, int argc){
REDICTMODULE_NOT_USED(argv);
REDICTMODULE_NOT_USED(argc);
if(argc < 2){
return RedictModule_WrongArity(ctx);
}
const char* cmd = RedictModule_StringPtrLen(argv[1], NULL);
RedictModuleCallReply* rep = RedictModule_Call(ctx, cmd, "vC", argv + 2, argc - 2);
if(!rep) {
char err[100];
switch (errno) {
case EACCES:
RedictModule_ReplyWithError(ctx, "ERR NOPERM");
break;
default:
snprintf(err, sizeof(err) - 1, "ERR errno=%d", errno);
RedictModule_ReplyWithError(ctx, err);
break;
}
} else {
RedictModule_ReplyWithCallReply(ctx, rep);
RedictModule_FreeCallReply(rep);
}
return REDICTMODULE_OK;
}
int module_test_acl_category(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
REDICTMODULE_NOT_USED(argv);
REDICTMODULE_NOT_USED(argc);
RedictModule_ReplyWithSimpleString(ctx, "OK");
return REDICTMODULE_OK;
}
int commandBlockCheck(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
REDICTMODULE_NOT_USED(argv);
REDICTMODULE_NOT_USED(argc);
int response_ok = 0;
int result = RedictModule_CreateCommand(ctx,"command.that.should.fail", module_test_acl_category, "", 0, 0, 0);
response_ok |= (result == REDICTMODULE_OK);
result = RedictModule_AddACLCategory(ctx,"blockedcategory");
response_ok |= (result == REDICTMODULE_OK);
RedictModuleCommand *parent = RedictModule_GetCommand(ctx,"block.commands.outside.onload");
result = RedictModule_SetCommandACLCategories(parent, "write");
response_ok |= (result == REDICTMODULE_OK);
result = RedictModule_CreateSubcommand(parent,"subcommand.that.should.fail",module_test_acl_category,"",0,0,0);
response_ok |= (result == REDICTMODULE_OK);
/* This validates that it's not possible to create commands or add
* a new ACL Category outside OnLoad function.
* thus returns an error if they succeed. */
if (response_ok) {
RedictModule_ReplyWithError(ctx, "UNEXPECTEDOK");
} else {
RedictModule_ReplyWithSimpleString(ctx, "OK");
}
return REDICTMODULE_OK;
}
int RedictModule_OnLoad(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
if (RedictModule_Init(ctx,"aclcheck",1,REDICTMODULE_APIVER_1)== REDICTMODULE_ERR)
return REDICTMODULE_ERR;
if (argc > 1) return RedictModule_WrongArity(ctx);
/* When that flag is passed, we try to create too many categories,
* and the test expects this to fail. In this case redict returns REDICTMODULE_ERR
* and set errno to ENOMEM*/
if (argc == 1) {
long long fail_flag = 0;
RedictModule_StringToLongLong(argv[0], &fail_flag);
if (fail_flag) {
for (size_t j = 0; j < 45; j++) {
RedictModuleString* name = RedictModule_CreateStringPrintf(ctx, "customcategory%zu", j);
if (RedictModule_AddACLCategory(ctx, RedictModule_StringPtrLen(name, NULL)) == REDICTMODULE_ERR) {
RedictModule_Assert(errno == ENOMEM);
RedictModule_FreeString(ctx, name);
return REDICTMODULE_ERR;
}
RedictModule_FreeString(ctx, name);
}
}
}
if (RedictModule_CreateCommand(ctx,"aclcheck.set.check.key", set_aclcheck_key,"write",0,0,0) == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
if (RedictModule_CreateCommand(ctx,"block.commands.outside.onload", commandBlockCheck,"write",0,0,0) == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
if (RedictModule_CreateCommand(ctx,"aclcheck.module.command.aclcategories.write", module_test_acl_category,"write",0,0,0) == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
RedictModuleCommand *aclcategories_write = RedictModule_GetCommand(ctx,"aclcheck.module.command.aclcategories.write");
if (RedictModule_SetCommandACLCategories(aclcategories_write, "write") == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
if (RedictModule_CreateCommand(ctx,"aclcheck.module.command.aclcategories.write.function.read.category", module_test_acl_category,"write",0,0,0) == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
RedictModuleCommand *read_category = RedictModule_GetCommand(ctx,"aclcheck.module.command.aclcategories.write.function.read.category");
if (RedictModule_SetCommandACLCategories(read_category, "read") == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
if (RedictModule_CreateCommand(ctx,"aclcheck.module.command.aclcategories.read.only.category", module_test_acl_category,"",0,0,0) == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
RedictModuleCommand *read_only_category = RedictModule_GetCommand(ctx,"aclcheck.module.command.aclcategories.read.only.category");
if (RedictModule_SetCommandACLCategories(read_only_category, "read") == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
if (RedictModule_CreateCommand(ctx,"aclcheck.publish.check.channel", publish_aclcheck_channel,"",0,0,0) == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
if (RedictModule_CreateCommand(ctx,"aclcheck.rm_call.check.cmd", rm_call_aclcheck_cmd_default_user,"",0,0,0) == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
if (RedictModule_CreateCommand(ctx,"aclcheck.rm_call.check.cmd.module.user", rm_call_aclcheck_cmd_module_user,"",0,0,0) == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
if (RedictModule_CreateCommand(ctx,"aclcheck.rm_call", rm_call_aclcheck,
"write",0,0,0) == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
if (RedictModule_CreateCommand(ctx,"aclcheck.rm_call_with_errors", rm_call_aclcheck_with_errors,
"write",0,0,0) == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
/* This validates that, when module tries to add a category with invalid characters,
* redict returns REDICTMODULE_ERR and set errno to `EINVAL` */
if (RedictModule_AddACLCategory(ctx,"!nval!dch@r@cter$") == REDICTMODULE_ERR)
RedictModule_Assert(errno == EINVAL);
else
return REDICTMODULE_ERR;
/* This validates that, when module tries to add a category that already exists,
* redict returns REDICTMODULE_ERR and set errno to `EBUSY` */
if (RedictModule_AddACLCategory(ctx,"write") == REDICTMODULE_ERR)
RedictModule_Assert(errno == EBUSY);
else
return REDICTMODULE_ERR;
if (RedictModule_AddACLCategory(ctx,"foocategory") == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
if (RedictModule_CreateCommand(ctx,"aclcheck.module.command.test.add.new.aclcategories", module_test_acl_category,"",0,0,0) == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
RedictModuleCommand *test_add_new_aclcategories = RedictModule_GetCommand(ctx,"aclcheck.module.command.test.add.new.aclcategories");
if (RedictModule_SetCommandACLCategories(test_add_new_aclcategories, "foocategory") == REDICTMODULE_ERR)
return REDICTMODULE_ERR;
return REDICTMODULE_OK;
}