Comment PR #5916 and changes a few details.

This commit is contained in:
antirez 2019-11-20 17:35:51 +01:00
parent 05292e342f
commit da77103157
1 changed files with 113 additions and 34 deletions

View File

@ -349,16 +349,36 @@ list *RedisModule_EventListeners; /* Global list of all the active events. */
unsigned long long ModulesInHooks = 0; /* Total number of modules in hooks
callbacks right now. */
/* Data structures related to the redis module users */
/* Data structures and callbacks related to the modules ACL API. */
/* This callback type is called by moduleNotifyUserChanged() every time
* a user authenticated via the module API is associated with a different
* user or gets disconnected. */
typedef void (*RedisModuleUserChangedFunc) (RedisModuleCtx ctx, void *privdata);
/* This is the object returned by RM_CreateModuleUser(). The module API is
* able to create users, set ACLs to such users, and later authenticate
* clients using such newly created users. */
typedef struct RedisModuleUser {
user *user; /* Reference to the real redis user */
} RedisModuleUser;
/* The authentication context is an object that can be created using the
* RM_CreateAuthCtx() API, and optionally passed as argument to the two
* API to authenticate the user using a global ACL user, or a user created
* by the module itself via RM_CreateModuleUser().
*
* The object contains the needed state and the callback pointer, in order
* for the module to be notified when the authentication state for a given
* user changes (if the user of the connection changes or if the connection
* gets disconnected). It is possible to call the authenticating APIs passing
* NULL instead of an authentication context if we are not interested in
* getting notified. */
typedef struct RedisModuleAuthCtx {
RedisModuleUserChangedFunc callback; /* Callback when user is changed or
* deleted. */
RedisModuleUserChangedFunc callback; /* Callback called when the user
* associated with the a given client
* changes or the client gets
* disconnected. */
void *privdata; /* Private data for the callback */
client *authenticated_client; /* A reference to the client that was
* authenticated */
@ -5133,12 +5153,17 @@ int RM_GetTimerInfo(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remain
/* --------------------------------------------------------------------------
* Modules ACL API
*
* Implements a hook into the authentication and authorization within Redis.
* Implements a hook into the authentication and authorization within Redis.
* --------------------------------------------------------------------------*/
/* This function is called when a clients user has changed, since a module
* may be tracking extra meta data about the client. This is also called
* when a client is deleted. */
/* This function is called when the user associated with a client has changed,
* or when a client disconnects. A module may be tracking extra meta data
* about the client. If the client is authenticated without an authentication
* context, nothing is performed by the function, otherwise the callback gets
* called.
*
* As a side effect of calling this function, the module context associated
* with this client gets freed, so the function is idempotent. */
void moduleNotifyUserChanged(client *c) {
RedisModuleAuthCtx *auth_ctx = (RedisModuleAuthCtx *) c->auth_ctx;
if (auth_ctx) {
@ -5154,10 +5179,15 @@ void moduleNotifyUserChanged(client *c) {
}
}
/* Set the client user back to the default user, and perform the clean up
* of the associated authentication context in case this client was
* authenticated using the modules API with an auth context. */
void revokeClientAuthentication(client *c) {
/* Fire the client changed handler now in case we are unloading the module
* and need to cleanup. */
/* Freeing the client would result in moduleNotifyUserChanged() to be
* called later, however since we use revokeClientAuthentication() also
* in moduleFreeAuthenticatedClients() to implement module unloading, we
* do this action ASAP: this way if the module is unloaded, when the client
* is eventually freed we don't rely on the module to still exist. */
moduleNotifyUserChanged(c);
c->user = DefaultUser;
@ -5165,7 +5195,8 @@ void revokeClientAuthentication(client *c) {
freeClientAsync(c);
}
/* Cleanup all clients with an auth_ctx to prevent leaking. */
/* Cleanup all clients with an auth_ctx to prevent leaking. This function
* is used when the module gets unloaded. */
void moduleFreeAuthenticatedClients(RedisModule *module) {
listIter li;
listNode *ln;
@ -5175,18 +5206,21 @@ void moduleFreeAuthenticatedClients(RedisModule *module) {
if (!c->auth_ctx) continue;
RedisModuleAuthCtx *auth_ctx = (RedisModuleAuthCtx *) c->auth_ctx;
if (auth_ctx->module == module) {
if (auth_ctx->module == module) {
revokeClientAuthentication(c);
}
}
}
/* This implements the authentication of the user using either a global ACL
* user, or a user created by the module itself. See the wrapping functions
* for more info. */
static int authenticateClientWithUser(RedisModuleCtx *ctx, user *user, RedisModuleAuthCtx *auth_ctx) {
if (auth_ctx && auth_ctx->authenticated_client) {
/* Prevent basic misuse of the auth context */
return REDISMODULE_ERR;
}
if (user->flags & USER_FLAG_DISABLED) {
return REDISMODULE_ERR;
}
@ -5205,13 +5239,27 @@ static int authenticateClientWithUser(RedisModuleCtx *ctx, user *user, RedisModu
return REDISMODULE_OK;
}
/* Creates a new user that is unlinked from the main ACL user dictionary. These
* users behave the same way as those in ACL.c except for a few minor
* differences. These users do not exist within a namespace, and handling
* duplicate users is the responsibility of the calling module. These users are
* also not attached to the redis users dictionary, so they are not returned
* via ACL LIST or GETUSER. This also means that users created here must be
* updated with the SetUserACL function instead of through ACL SETUSER. */
/* Creates a Redis ACL user that the module can use to authenticate a client.
* After obtaining the user, the module should set what such user can do
* using the RedisModukle_SetUserACL() function. Once configured, the user
* can be used in order to authenticate a connection, with the specified
* ACL rules, using the RedisModule_AuthClientWithUser() function.
*
* Note that:
*
* * Users created here are not listed by the ACL command.
* * Users created here are not checked for duplicated name, so it's up to
* the module calling this function to take care of not creating users
* with the same name.
* * The created user can be used to authenticate multiple Redis connections.
*
* The caller can later free the user using the function
* RedisModule_FreeModuleUser(). When this function is called, if there are
* still clients authenticated with this user, they are disconnected.
* The function to free the user should only be used when the caller really
* wants to invalidate the user to define a new one with different
* capabilities.
*/
RedisModuleUser *RM_CreateModuleUser(const char *name) {
RedisModuleUser *new_user = zmalloc(sizeof(RedisModuleUser));
new_user->user = ACLCreateUnlinkedUser();
@ -5222,30 +5270,48 @@ RedisModuleUser *RM_CreateModuleUser(const char *name) {
return new_user;
}
/* Frees a given user and disconnects all of the clients that have been
* authenticated with it. */
/* Free a given user and disconnect all of the clients that have been
* authenticated with it. Also check the specular API to create users
* RedisModule_CreateModuleUser(). */
int RM_FreeModuleUser(RedisModuleUser *user) {
ACLFreeUserAndKillClients(user->user);
zfree(user);
return REDISMODULE_OK;
}
/* Sets the user permission of a user created through the redis module
* interface. The syntax is the sam as ACL SETUSER, so refer to the
/* Sets the user permission of a user created through the redis module
* interface. The syntax is the sam as ACL SETUSER, so refer to the
* documentation in acl.c for more information. */
int RM_SetModuleUserACL(RedisModuleUser *user, const char* acl) {
return ACLSetUser(user->user, acl, -1);
}
/* Authenticate the current contexts user with the provided module user.
* Throws an error if the user is disabled or the AuthCtx is misued */
/* Authenticate the current connection with the provided module user.
* The module user was obtained using a RedisModule_CreateModuleUser() call
* and setup using the RedisModule_SetModuleUserACL().
*
* This API only works from module commands callbacks, because otherwise
* there is no obvious associated client to authenticate.
*
* The caller of this function may pass a null 'auth_ctx' if there is no
* need to register a callback and get notified when this connection user
* changes or the connection gets terminated. Otherwise, the caller may
* create an authentication context using RedisModule_CreateAuthCtx() with
* the needed callback and private data as arguments, and then pass it to
* this function.
*
* Throws an error if the user is disabled or the AuthCtx is misued
* (you passed a context that already associated with another client) */
int RM_AuthClientWithUser(RedisModuleCtx *ctx, RedisModuleUser *module_user, RedisModuleAuthCtx *auth_ctx) {
return authenticateClientWithUser(ctx, module_user->user, auth_ctx);
}
/* Authenticate the current contexts user with the provided redis acl user.
/* Exactly like RedisModule_AuthClientWithUser(), but instead of getting
* a user created by the module, gets a user name that was defined using
* Redis ACLs.
*
* Throws an error if the user is disabled, the user doesn't exit,
* or the AuthCtx is misused. */
* or the AuthCtx is misused (already associated with another client). */
int RM_AuthClientWithACLUser(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleAuthCtx *auth_ctx) {
user *acl_user = ACLGetUserByName(name, len);
@ -5255,9 +5321,17 @@ int RM_AuthClientWithACLUser(RedisModuleCtx *ctx, const char *name, size_t len,
return authenticateClientWithUser(ctx, acl_user, auth_ctx);
}
/* Create a redis Auth Ctx which can be used to notify the module when
* the client authenticates with a different user or to revoke access
* to the authenticated client. */
/* Create a redis authentication context, with its associated callback and
* private data: this context will be passed to one of the following
* functions:
*
* * RedisModule_AuthClientWithUser()
* * RedisModule_AuthClientWithACLUser()
*
* If the user is authenticated using an authentication context and not
* NULL, the auth context callback will be invoked when the connection
* changes user, is revoked, or is terminated.
*/
RedisModuleAuthCtx *RM_CreateAuthCtx(RedisModuleUserChangedFunc callback, void *privdata) {
RedisModuleAuthCtx *auth_ctx = zmalloc(sizeof(RedisModuleAuthCtx));
auth_ctx->callback = callback;
@ -5267,10 +5341,15 @@ RedisModuleAuthCtx *RM_CreateAuthCtx(RedisModuleUserChangedFunc callback, void *
return auth_ctx;
}
/* Revokes the authentication that was granted during the specified
* AuthCtx. The method is not thread safe. */
/* Given an authentication context that was used to authenticate the client
* with the function RedisModule_AuthClientWithUser() or the function
* RedisModule_AuthClientWithACLUser(), this function revokes the
* authentication (effectively setting it in the just connected status of the
* "default" user and non authenticated state) of the client and terminates
* it. */
void RM_RevokeAuthentication(RedisModuleAuthCtx *ctx) {
revokeClientAuthentication(ctx->authenticated_client);
if (ctx->authenticated_client)
revokeClientAuthentication(ctx->authenticated_client);
}
/* --------------------------------------------------------------------------