summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/acl.c39
-rw-r--r--src/module.c92
-rw-r--r--src/redismodule.h2
-rw-r--r--src/server.h3
-rw-r--r--tests/modules/aclcheck.c55
-rw-r--r--tests/modules/datatype.c29
-rw-r--r--tests/modules/moduleconfigs.c35
-rw-r--r--tests/unit/moduleapi/aclcheck.tcl39
-rw-r--r--tests/unit/moduleapi/datatype.tcl4
-rw-r--r--tests/unit/moduleapi/moduleconfigs.tcl4
10 files changed, 297 insertions, 5 deletions
diff --git a/src/acl.c b/src/acl.c
index e225b2a39..8b571d86e 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -629,7 +629,6 @@ void ACLSetSelectorCommandBitsForCategory(dict *commands, aclSelector *selector,
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
- if (cmd->flags & CMD_MODULE) continue; /* Ignore modules commands. */
if (cmd->acl_categories & cflag) {
ACLChangeSelectorPerm(selector,cmd,value);
}
@@ -640,6 +639,44 @@ void ACLSetSelectorCommandBitsForCategory(dict *commands, aclSelector *selector,
dictReleaseIterator(di);
}
+/* This function is responsible for recomputing the command bits for all selectors of the existing users.
+ * It uses the 'command_rules', a string representation of the ordered categories and commands,
+ * to recompute the command bits. */
+void ACLRecomputeCommandBitsFromCommandRulesAllUsers() {
+ raxIterator ri;
+ raxStart(&ri,Users);
+ raxSeek(&ri,"^",NULL,0);
+ while(raxNext(&ri)) {
+ user *u = ri.data;
+ listIter li;
+ listNode *ln;
+ listRewind(u->selectors,&li);
+ while((ln = listNext(&li))) {
+ aclSelector *selector = (aclSelector *) listNodeValue(ln);
+ int argc = 0;
+ sds *argv = sdssplitargs(selector->command_rules, &argc);
+ serverAssert(argv != NULL);
+ /* Checking selector's permissions for all commands to start with a clean state. */
+ if (ACLSelectorCanExecuteFutureCommands(selector)) {
+ int res = ACLSetSelector(selector,"+@all",-1);
+ serverAssert(res == C_OK);
+ } else {
+ int res = ACLSetSelector(selector,"-@all",-1);
+ serverAssert(res == C_OK);
+ }
+
+ /* Apply all of the commands and categories to this selector. */
+ for(int i = 0; i < argc; i++) {
+ int res = ACLSetSelector(selector, argv[i], sdslen(argv[i]));
+ serverAssert(res == C_OK);
+ }
+ sdsfreesplitres(argv, argc);
+ }
+ }
+ raxStop(&ri);
+
+}
+
int ACLSetSelectorCategory(aclSelector *selector, const char *category, int allow) {
uint64_t cflag = ACLGetCommandCategoryFlagByName(category + 1);
if (!cflag) return C_ERR;
diff --git a/src/module.c b/src/module.c
index cf980a38a..a8df08bbb 100644
--- a/src/module.c
+++ b/src/module.c
@@ -1152,6 +1152,7 @@ RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds dec
* convention.
*
* The function returns REDISMODULE_ERR in these cases:
+ * - If creation of module command is called outside the RedisModule_OnLoad.
* - The specified command is already busy.
* - The command name contains some chars that are not allowed.
* - A set of invalid flags were passed.
@@ -1240,8 +1241,11 @@ RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds dec
* NOTE: The scheme described above serves a limited purpose and can
* only be used to find keys that exist at constant indices.
* For non-trivial key arguments, you may pass 0,0,0 and use
- * RedisModule_SetCommandInfo to set key specs using a more advanced scheme. */
+ * RedisModule_SetCommandInfo to set key specs using a more advanced scheme and use
+ * RedisModule_SetCommandACLCategories to set Redis ACL categories of the commands. */
int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) {
+ if (!ctx->module->onload)
+ return REDISMODULE_ERR;
int64_t flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
if (flags == -1) return REDISMODULE_ERR;
if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled)
@@ -1361,8 +1365,11 @@ RedisModuleCommand *RM_GetCommand(RedisModuleCtx *ctx, const char *name) {
* * `parent` is already a subcommand (we do not allow more than one level of command nesting)
* * `parent` is a command with an implementation (RedisModuleCmdFunc) (A parent command should be a pure container of subcommands)
* * `parent` already has a subcommand called `name`
+ * * Creating a subcommand is called outside of RedisModule_OnLoad.
*/
int RM_CreateSubcommand(RedisModuleCommand *parent, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) {
+ if (!parent->module->onload)
+ return REDISMODULE_ERR;
int64_t flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
if (flags == -1) return REDISMODULE_ERR;
if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled)
@@ -1417,6 +1424,63 @@ moduleCmdArgAt(const RedisModuleCommandInfoVersion *version,
return (RedisModuleCommandArg *)((char *)(args) + offset);
}
+/* Helper for categoryFlagsFromString(). Attempts to find an acl flag representing the provided flag string
+ * and adds that flag to acl_categories_flags if a match is found.
+ *
+ * Returns '1' if acl category flag is recognized or
+ * returns '0' if not recognized */
+int matchAclCategoryFlag(char *flag, int64_t *acl_categories_flags) {
+ uint64_t this_flag = ACLGetCommandCategoryFlagByName(flag);
+ if (this_flag) {
+ *acl_categories_flags |= (int64_t) this_flag;
+ return 1;
+ }
+ return 0; /* Unrecognized */
+}
+
+/* Helper for RM_SetCommandACLCategories(). Turns a string representing acl category
+ * flags into the acl category flags used by Redis ACL which allows users to access
+ * the module commands by acl categories.
+ *
+ * It returns the set of acl flags, or -1 if unknown flags are found. */
+int64_t categoryFlagsFromString(char *aclflags) {
+ int count, j;
+ int64_t acl_categories_flags = 0;
+ sds *tokens = sdssplitlen(aclflags,strlen(aclflags)," ",1,&count);
+ for (j = 0; j < count; j++) {
+ char *t = tokens[j];
+ if (!matchAclCategoryFlag(t, &acl_categories_flags)) {
+ serverLog(LL_WARNING,"Unrecognized categories flag %s on module load", t);
+ break;
+ }
+ }
+ sdsfreesplitres(tokens,count);
+ if (j != count) return -1; /* Some token not processed correctly. */
+ return acl_categories_flags;
+}
+
+/* RedisModule_SetCommandACLCategories can be used to set ACL categories to module
+ * commands and subcommands. The set of ACL categories should be passed as
+ * a space separated C string 'aclflags'.
+ *
+ * Example, the acl flags 'write slow' marks the command as part of the write and
+ * slow ACL categories.
+ *
+ * On success REDISMODULE_OK is returned. On error REDISMODULE_ERR is returned.
+ *
+ * This function can only be called during the RedisModule_OnLoad function. If called
+ * outside of this function, an error is returned.
+ */
+int RM_SetCommandACLCategories(RedisModuleCommand *command, const char *aclflags) {
+ if (!command || !command->module || !command->module->onload) return REDISMODULE_ERR;
+ int64_t categories_flags = aclflags ? categoryFlagsFromString((char*)aclflags) : 0;
+ if (categories_flags == -1) return REDISMODULE_ERR;
+ struct redisCommand *rcmd = command->rediscmd;
+ rcmd->acl_categories = categories_flags; /* ACL categories flags for module command */
+ command->module->num_commands_with_acl_categories++;
+ return REDISMODULE_OK;
+}
+
/* Set additional command information.
*
* Affects the output of `COMMAND`, `COMMAND INFO` and `COMMAND DOCS`, Cluster,
@@ -2168,6 +2232,8 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
module->info_cb = 0;
module->defrag_cb = 0;
module->loadmod = NULL;
+ module->num_commands_with_acl_categories = 0;
+ module->onload = 1;
ctx->module = module;
}
@@ -6682,8 +6748,9 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj
* Note: the module name "AAAAAAAAA" is reserved and produces an error, it
* happens to be pretty lame as well.
*
- * If there is already a module registering a type with the same name,
- * and if the module name or encver is invalid, NULL is returned.
+ * If RedisModule_CreateDataType() is called outside of RedisModule_OnLoad() function,
+ * there is already a module registering a type with the same name,
+ * or if the module name or encver is invalid, NULL is returned.
* Otherwise the new type is registered into Redis, and a reference of
* type RedisModuleType is returned: the caller of the function should store
* this reference into a global variable to make future use of it in the
@@ -6698,6 +6765,8 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj
* }
*/
moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver, void *typemethods_ptr) {
+ if (!ctx->module->onload)
+ return NULL;
uint64_t id = moduleTypeEncodeId(name,encver);
if (id == 0) return NULL;
if (moduleTypeLookupModuleByName(name) != NULL) return NULL;
@@ -12013,7 +12082,13 @@ int moduleLoad(const char *path, void **module_argv, int module_argc, int is_loa
incrRefCount(ctx.module->loadmod->argv[i]);
}
+ /* If module commands have ACL categories, recompute command bits
+ * for all existing users once the modules has been registered. */
+ if (ctx.module->num_commands_with_acl_categories) {
+ ACLRecomputeCommandBitsFromCommandRulesAllUsers();
+ }
serverLog(LL_NOTICE,"Module '%s' loaded from %s",ctx.module->name,path);
+ ctx.module->onload = 0;
int post_load_err = 0;
if (listLength(ctx.module->module_configs) && !ctx.module->configs_initialized) {
@@ -12117,6 +12192,8 @@ int moduleUnload(sds name, const char **errmsg) {
module->name = NULL; /* The name was already freed by dictDelete(). */
moduleFreeModuleStructure(module);
+ /* Recompute command bits for all users once the modules has been completely unloaded. */
+ ACLRecomputeCommandBitsFromCommandRulesAllUsers();
return C_OK;
}
@@ -12424,6 +12501,10 @@ ModuleConfig *createModuleConfig(sds name, RedisModuleConfigApplyFunc apply_fn,
}
int moduleConfigValidityCheck(RedisModule *module, sds name, unsigned int flags, configType type) {
+ if (!module->onload) {
+ errno = EBUSY;
+ return REDISMODULE_ERR;
+ }
if (moduleVerifyConfigFlags(flags, type) || moduleVerifyConfigName(name)) {
errno = EINVAL;
return REDISMODULE_ERR;
@@ -12530,6 +12611,7 @@ unsigned int maskModuleEnumConfigFlags(unsigned int flags) {
*
* If the registration fails, REDISMODULE_ERR is returned and one of the following
* errno is set:
+ * * EBUSY: Registering the Config outside of RedisModule_OnLoad.
* * EINVAL: The provided flags are invalid for the registration or the name of the config contains invalid characters.
* * EALREADY: The provided configuration name is already used. */
int RM_RegisterStringConfig(RedisModuleCtx *ctx, const char *name, const char *default_val, unsigned int flags, RedisModuleConfigGetStringFunc getfn, RedisModuleConfigSetStringFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) {
@@ -12646,10 +12728,11 @@ int RM_RegisterNumericConfig(RedisModuleCtx *ctx, const char *name, long long de
/* Applies all pending configurations on the module load. This should be called
* after all of the configurations have been registered for the module inside of RedisModule_OnLoad.
+ * This will return REDISMODULE_ERR if it is called outside RedisModule_OnLoad.
* This API needs to be called when configurations are provided in either `MODULE LOADEX`
* or provided as startup arguments. */
int RM_LoadConfigs(RedisModuleCtx *ctx) {
- if (!ctx || !ctx->module) {
+ if (!ctx || !ctx->module || !ctx->module->onload) {
return REDISMODULE_ERR;
}
RedisModule *module = ctx->module;
@@ -13198,6 +13281,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(GetCommand);
REGISTER_API(CreateSubcommand);
REGISTER_API(SetCommandInfo);
+ REGISTER_API(SetCommandACLCategories);
REGISTER_API(SetModuleAttribs);
REGISTER_API(IsModuleNameBusy);
REGISTER_API(WrongArity);
diff --git a/src/redismodule.h b/src/redismodule.h
index e6edf6e7a..13ebc3829 100644
--- a/src/redismodule.h
+++ b/src/redismodule.h
@@ -966,6 +966,7 @@ REDISMODULE_API int (*RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char
REDISMODULE_API RedisModuleCommand *(*RedisModule_GetCommand)(RedisModuleCtx *ctx, const char *name) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_CreateSubcommand)(RedisModuleCommand *parent, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetCommandInfo)(RedisModuleCommand *command, const RedisModuleCommandInfo *info) REDISMODULE_ATTR;
+REDISMODULE_API int (*RedisModule_SetCommandACLCategories)(RedisModuleCommand *command, const char *ctgrsflags) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_IsModuleNameBusy)(const char *name) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_WrongArity)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
@@ -1320,6 +1321,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(GetCommand);
REDISMODULE_GET_API(CreateSubcommand);
REDISMODULE_GET_API(SetCommandInfo);
+ REDISMODULE_GET_API(SetCommandACLCategories);
REDISMODULE_GET_API(SetModuleAttribs);
REDISMODULE_GET_API(IsModuleNameBusy);
REDISMODULE_GET_API(WrongArity);
diff --git a/src/server.h b/src/server.h
index ddc2808e4..0cc15f3e3 100644
--- a/src/server.h
+++ b/src/server.h
@@ -813,6 +813,8 @@ struct RedisModule {
RedisModuleInfoFunc info_cb; /* Callback for module to add INFO fields. */
RedisModuleDefragFunc defrag_cb; /* Callback for global data defrag. */
struct moduleLoadQueueEntry *loadmod; /* Module load arguments for config rewrite. */
+ int num_commands_with_acl_categories; /* Number of commands in this module included in acl categories */
+ int onload; /* Flag to identify if the call is being made from Onload (0 or 1) */
};
typedef struct RedisModule RedisModule;
@@ -2950,6 +2952,7 @@ void addACLLogEntry(client *c, int reason, int context, int argpos, sds username
sds getAclErrorMessage(int acl_res, user *user, struct redisCommand *cmd, sds errored_val, int verbose);
void ACLUpdateDefaultUserPassword(sds password);
sds genRedisInfoStringACLStats(sds info);
+void ACLRecomputeCommandBitsFromCommandRulesAllUsers();
/* Sorted sets data type */
diff --git a/tests/modules/aclcheck.c b/tests/modules/aclcheck.c
index 9f4564d27..09b525cc5 100644
--- a/tests/modules/aclcheck.c
+++ b/tests/modules/aclcheck.c
@@ -183,6 +183,37 @@ int rm_call_aclcheck(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
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);
@@ -193,6 +224,30 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
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;
diff --git a/tests/modules/datatype.c b/tests/modules/datatype.c
index 45a356e52..408d1a526 100644
--- a/tests/modules/datatype.c
+++ b/tests/modules/datatype.c
@@ -234,6 +234,31 @@ static int datatype_is_in_slow_loading(RedisModuleCtx *ctx, RedisModuleString **
return REDISMODULE_OK;
}
+int createDataTypeBlockCheck(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+ static RedisModuleType *datatype_outside_onload = NULL;
+
+ RedisModuleTypeMethods datatype_methods = {
+ .version = REDISMODULE_TYPE_METHOD_VERSION,
+ .rdb_load = datatype_load,
+ .rdb_save = datatype_save,
+ .free = datatype_free,
+ .copy = datatype_copy
+ };
+
+ datatype_outside_onload = RedisModule_CreateDataType(ctx, "test_dt_outside_onload", 1, &datatype_methods);
+
+ /* This validates that it's not possible to create datatype outside OnLoad,
+ * thus returns an error if it succeeds. */
+ if (datatype_outside_onload == NULL) {
+ RedisModule_ReplyWithSimpleString(ctx, "OK");
+ } else {
+ RedisModule_ReplyWithError(ctx, "UNEXPECTEDOK");
+ }
+ return REDISMODULE_OK;
+}
+
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
@@ -241,6 +266,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_Init(ctx,"datatype",DATATYPE_ENC_VER,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
+ /* Creates a command which creates a datatype outside OnLoad() function. */
+ if (RedisModule_CreateCommand(ctx,"block.create.datatype.outside.onload", createDataTypeBlockCheck, "write", 0, 0, 0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
RedisModule_SetModuleOptions(ctx, REDISMODULE_OPTIONS_HANDLE_IO_ERRORS);
RedisModuleTypeMethods datatype_methods = {
diff --git a/tests/modules/moduleconfigs.c b/tests/modules/moduleconfigs.c
index b48133372..2c1737df7 100644
--- a/tests/modules/moduleconfigs.c
+++ b/tests/modules/moduleconfigs.c
@@ -103,6 +103,37 @@ int longlongApplyFunc(RedisModuleCtx *ctx, void *privdata, RedisModuleString **e
return REDISMODULE_OK;
}
+int registerBlockCheck(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+ int response_ok = 0;
+ int result = RedisModule_RegisterBoolConfig(ctx, "mutable_bool", 1, REDISMODULE_CONFIG_DEFAULT, getBoolConfigCommand, setBoolConfigCommand, boolApplyFunc, &mutable_bool_val);
+ response_ok |= (result == REDISMODULE_OK);
+
+ result = RedisModule_RegisterStringConfig(ctx, "string", "secret password", REDISMODULE_CONFIG_DEFAULT, getStringConfigCommand, setStringConfigCommand, NULL, NULL);
+ response_ok |= (result == REDISMODULE_OK);
+
+ const char *enum_vals[] = {"none", "five", "one", "two", "four"};
+ const int int_vals[] = {0, 5, 1, 2, 4};
+ result = RedisModule_RegisterEnumConfig(ctx, "enum", 1, REDISMODULE_CONFIG_DEFAULT, enum_vals, int_vals, 5, getEnumConfigCommand, setEnumConfigCommand, NULL, NULL);
+ response_ok |= (result == REDISMODULE_OK);
+
+ result = RedisModule_RegisterNumericConfig(ctx, "numeric", -1, REDISMODULE_CONFIG_DEFAULT, -5, 2000, getNumericConfigCommand, setNumericConfigCommand, longlongApplyFunc, &longval);
+ response_ok |= (result == REDISMODULE_OK);
+
+ result = RedisModule_LoadConfigs(ctx);
+ response_ok |= (result == REDISMODULE_OK);
+
+ /* This validates that it's not possible to register/load configs 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);
@@ -147,6 +178,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
}
return REDISMODULE_ERR;
}
+ /* Creates a command which registers configs outside OnLoad() function. */
+ if (RedisModule_CreateCommand(ctx,"block.register.configs.outside.onload", registerBlockCheck, "write", 0, 0, 0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
return REDISMODULE_OK;
}
diff --git a/tests/unit/moduleapi/aclcheck.tcl b/tests/unit/moduleapi/aclcheck.tcl
index 5dae6e1ad..ae3f67156 100644
--- a/tests/unit/moduleapi/aclcheck.tcl
+++ b/tests/unit/moduleapi/aclcheck.tcl
@@ -92,6 +92,45 @@ start_server {tags {"modules acl"}} {
assert {[dict get $entry reason] eq {command}}
}
+ test {test blocking of Commands outside of OnLoad} {
+ assert_equal [r block.commands.outside.onload] OK
+ }
+
+ test {test users to have access to module commands having acl categories} {
+ r acl SETUSER j1 on >password -@all +@WRITE
+ r acl SETUSER j2 on >password -@all +@READ
+ assert_equal [r acl DRYRUN j1 aclcheck.module.command.aclcategories.write] OK
+ assert_equal [r acl DRYRUN j2 aclcheck.module.command.aclcategories.write.function.read.category] OK
+ assert_equal [r acl DRYRUN j2 aclcheck.module.command.aclcategories.read.only.category] OK
+ }
+
+ test {test existing users to have access to module commands loaded on runtime} {
+ assert_equal [r module unload aclcheck] OK
+ r acl SETUSER j3 on >password -@all +@WRITE
+ assert_equal [r module load $testmodule] OK
+ assert_equal [r acl DRYRUN j3 aclcheck.module.command.aclcategories.write] OK
+ }
+
+ test {test existing users without permissions, do not have access to module commands loaded on runtime.} {
+ assert_equal [r module unload aclcheck] OK
+ r acl SETUSER j4 on >password -@all +@READ
+ r acl SETUSER j5 on >password -@all +@WRITE
+ assert_equal [r module load $testmodule] OK
+ catch {r acl DRYRUN j4 aclcheck.module.command.aclcategories.write} e
+ assert_equal {User j4 has no permissions to run the 'aclcheck.module.command.aclcategories.write' command} $e
+ catch {r acl DRYRUN j5 aclcheck.module.command.aclcategories.write.function.read.category} e
+ assert_equal {User j5 has no permissions to run the 'aclcheck.module.command.aclcategories.write.function.read.category' command} $e
+ }
+
+ test {test users without permissions, do not have access to module commands.} {
+ r acl SETUSER j6 on >password -@all +@READ
+ catch {r acl DRYRUN j6 aclcheck.module.command.aclcategories.write} e
+ assert_equal {User j6 has no permissions to run the 'aclcheck.module.command.aclcategories.write' command} $e
+ r acl SETUSER j7 on >password -@all +@WRITE
+ catch {r acl DRYRUN j7 aclcheck.module.command.aclcategories.write.function.read.category} e
+ assert_equal {User j7 has no permissions to run the 'aclcheck.module.command.aclcategories.write.function.read.category' command} $e
+ }
+
test "Unload the module - aclcheck" {
assert_equal {OK} [r module unload aclcheck]
}
diff --git a/tests/unit/moduleapi/datatype.tcl b/tests/unit/moduleapi/datatype.tcl
index c8fd30ed1..0c87e9597 100644
--- a/tests/unit/moduleapi/datatype.tcl
+++ b/tests/unit/moduleapi/datatype.tcl
@@ -8,6 +8,10 @@ start_server {tags {"modules"}} {
assert {[r datatype.get dtkey] eq {100 stringval}}
}
+ test {test blocking of datatype creation outside of OnLoad} {
+ assert_equal [r block.create.datatype.outside.onload] OK
+ }
+
test {DataType: RM_SaveDataTypeToString(), RM_LoadDataTypeFromStringEncver() work} {
r datatype.set dtkey -1111 MyString
set encoded [r datatype.dump dtkey]
diff --git a/tests/unit/moduleapi/moduleconfigs.tcl b/tests/unit/moduleapi/moduleconfigs.tcl
index 2b28fc307..1709e9d99 100644
--- a/tests/unit/moduleapi/moduleconfigs.tcl
+++ b/tests/unit/moduleapi/moduleconfigs.tcl
@@ -77,6 +77,10 @@ start_server {tags {"modules"}} {
assert_match {*must be one of the following*} $e
}
+ test {test blocking of config registration and load outside of OnLoad} {
+ assert_equal [r block.register.configs.outside.onload] OK
+ }
+
test {Unload removes module configs} {
r module unload moduleconfigs
assert_equal [r config get moduleconfigs.*] ""