diff options
-rw-r--r-- | src/acl.c | 39 | ||||
-rw-r--r-- | src/module.c | 92 | ||||
-rw-r--r-- | src/redismodule.h | 2 | ||||
-rw-r--r-- | src/server.h | 3 | ||||
-rw-r--r-- | tests/modules/aclcheck.c | 55 | ||||
-rw-r--r-- | tests/modules/datatype.c | 29 | ||||
-rw-r--r-- | tests/modules/moduleconfigs.c | 35 | ||||
-rw-r--r-- | tests/unit/moduleapi/aclcheck.tcl | 39 | ||||
-rw-r--r-- | tests/unit/moduleapi/datatype.tcl | 4 | ||||
-rw-r--r-- | tests/unit/moduleapi/moduleconfigs.tcl | 4 |
10 files changed, 297 insertions, 5 deletions
@@ -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.*] "" |