diff options
Diffstat (limited to 'src/module.c')
-rw-r--r-- | src/module.c | 92 |
1 files changed, 88 insertions, 4 deletions
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); |