mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-21 23:58:51 -05:00
Add module APIs for custom authentication
This commit is contained in:
parent
e9b99c78df
commit
034dcf185c
@ -24,4 +24,5 @@ $TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/blockonkeys \
|
||||
--single unit/moduleapi/scan \
|
||||
--single unit/moduleapi/datatype \
|
||||
--single unit/moduleapi/auth \
|
||||
"${@}"
|
||||
|
@ -975,6 +975,7 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password) {
|
||||
if (ACLCheckUserCredentials(username,password) == C_OK) {
|
||||
c->authenticated = 1;
|
||||
c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr));
|
||||
moduleNotifyUserChanged(c);
|
||||
return C_OK;
|
||||
} else {
|
||||
return C_ERR;
|
||||
|
222
src/module.c
222
src/module.c
@ -355,6 +355,21 @@ 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 */
|
||||
|
||||
/* 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) (uint64_t client_id, 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;
|
||||
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Prototypes
|
||||
* -------------------------------------------------------------------------- */
|
||||
@ -719,6 +734,7 @@ int commandFlagsFromString(char *s) {
|
||||
else if (!strcasecmp(t,"allow-stale")) flags |= CMD_STALE;
|
||||
else if (!strcasecmp(t,"no-monitor")) flags |= CMD_SKIP_MONITOR;
|
||||
else if (!strcasecmp(t,"fast")) flags |= CMD_FAST;
|
||||
else if (!strcasecmp(t,"no-auth")) flags |= CMD_NO_AUTH;
|
||||
else if (!strcasecmp(t,"getkeys-api")) flags |= CMD_MODULE_GETKEYS;
|
||||
else if (!strcasecmp(t,"no-cluster")) flags |= CMD_MODULE_NO_CLUSTER;
|
||||
else break;
|
||||
@ -780,6 +796,9 @@ int commandFlagsFromString(char *s) {
|
||||
* example, is unable to report the position of the
|
||||
* keys, programmatically creates key names, or any
|
||||
* other reason.
|
||||
* * **"no-auth"**: This command can be run by an un-authenticated client.
|
||||
* Normally this is used by a command that is used
|
||||
* to authenticate a client.
|
||||
*/
|
||||
int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) {
|
||||
int flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
|
||||
@ -5236,6 +5255,202 @@ int RM_GetTimerInfo(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remain
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules ACL API
|
||||
*
|
||||
* Implements a hook into the authentication and authorization within Redis.
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
/* This function is called when a client's user has changed and invoked a
|
||||
* a modules client changed callback if it was set. This callback should
|
||||
* cleanup any state the module was tracking about this client.
|
||||
*
|
||||
* A client's user can be changed through the AUTH command, module
|
||||
* authentication, and when the client is freed. */
|
||||
void moduleNotifyUserChanged(client *c) {
|
||||
if (c->auth_callback) {
|
||||
c->auth_callback(c->id, c->auth_callback_privdata);
|
||||
|
||||
/* The callback will fire exactly once, even if the user remains
|
||||
* the same, it is expected to completely clean up it's state
|
||||
* so all references are removed */
|
||||
c->auth_callback = NULL;
|
||||
c->auth_callback_privdata = NULL;
|
||||
c->auth_module = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void revokeClientAuthentication(client *c) {
|
||||
/* Fire the client changed handler now in case we are unloading the module
|
||||
* and need to cleanup. */
|
||||
moduleNotifyUserChanged(c);
|
||||
|
||||
c->user = DefaultUser;
|
||||
c->authenticated = 0;
|
||||
freeClientAsync(c);
|
||||
}
|
||||
|
||||
/* Cleanup all clients that have been authenticated with this module. This
|
||||
* is called from onUnload() to give the module a chance to cleanup any
|
||||
* resources associated with the authentication. */
|
||||
static void moduleFreeAuthenticatedClients(RedisModule *module) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = listNodeValue(ln);
|
||||
if (!c->auth_module) continue;
|
||||
|
||||
RedisModule *auth_module = (RedisModule *) c->auth_module;
|
||||
if (auth_module == module) {
|
||||
revokeClientAuthentication(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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 RM_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
|
||||
* RM_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();
|
||||
|
||||
/* Free the previous temporarily assigned name to assign the new one */
|
||||
sdsfree(new_user->user->name);
|
||||
new_user->user->name = sdsnew(name);
|
||||
return new_user;
|
||||
}
|
||||
|
||||
/* Frees a given user and disconnects all of the clients that have been
|
||||
* authenticated with it. See RM_CreateModuleUser for detailed usage.*/
|
||||
int RM_FreeModuleUser(RedisModuleUser *user) {
|
||||
ACLFreeUserAndKillClients(user->user);
|
||||
zfree(user);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Sets the permissions of a user created through the redis module
|
||||
* interface. The syntax is the same as ACL SETUSER, so refer to the
|
||||
* documentation in acl.c for more information. See RM_CreateModuleUser
|
||||
* for detailed usage.
|
||||
*
|
||||
* Returns REDISMODULE_OK on success and REDISMODULE_ERR on failure
|
||||
* and will set an errno describing why the operation failed. */
|
||||
int RM_SetModuleUserACL(RedisModuleUser *user, const char* acl) {
|
||||
return ACLSetUser(user->user, acl, -1);
|
||||
}
|
||||
|
||||
/* Authenticate the client associated with the context with
|
||||
* the provided user. Returns REDISMODULE_OK on success and
|
||||
* REDISMODULE_ERR on error.
|
||||
*
|
||||
* This authentication can be tracked with the optional callback and private
|
||||
* data fields. The callback will be called whenever the user of the client
|
||||
* changes. This callback should be used to cleanup any state that is being
|
||||
* kept in the module related to the client authentication. It will only be
|
||||
* called once, even when the user hasn't changed, in order to allow for a
|
||||
* new callback to be specified. If this authentication does not need to be
|
||||
* tracked, pass in NULL for the callback and privdata.
|
||||
*
|
||||
* If client_id is not NULL, it will be filled with the id of the client
|
||||
* that was authenticated. This can be used with the
|
||||
* RM_DeauthenticateAndCloseClient() API in order to deauthenticate a
|
||||
* previously authenticated client if the authentication is no longer valid.
|
||||
*
|
||||
* For expensive authentication operations, it is recommended to block the
|
||||
* client and do the authentication in the background then attach the user
|
||||
* to the client in a threadsafe context. */
|
||||
static int authenticateClientWithUser(RedisModuleCtx *ctx, user *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) {
|
||||
if (user->flags & USER_FLAG_DISABLED) {
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* 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(ctx->client);
|
||||
|
||||
ctx->client->user = user;
|
||||
ctx->client->authenticated = 1;
|
||||
|
||||
if (callback) {
|
||||
ctx->client->auth_callback = callback;
|
||||
ctx->client->auth_callback_privdata = privdata;
|
||||
ctx->client->auth_module = ctx->module;
|
||||
}
|
||||
|
||||
if (client_id) {
|
||||
*client_id = ctx->client->id;
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
|
||||
/* Authenticate the current context's user with the provided redis acl user.
|
||||
* Returns REDISMODULE_ERR if the user is disabled.
|
||||
*
|
||||
* See authenticateClientWithUser for information about callback and client_id,
|
||||
* and general usage for authentication. */
|
||||
int RM_AuthenticateClientWithUser(RedisModuleCtx *ctx, RedisModuleUser *module_user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) {
|
||||
return authenticateClientWithUser(ctx, module_user->user, callback, privdata, client_id);
|
||||
}
|
||||
|
||||
/* Authenticate the current context's user with the provided redis acl user.
|
||||
* Returns REDISMODULE_ERR if the user is disabled or the user does not exist.
|
||||
*
|
||||
* See authenticateClientWithUser for information about callback and client_id,
|
||||
* and general usage for authentication. */
|
||||
int RM_AuthenticateClientWithACLUser(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) {
|
||||
user *acl_user = ACLGetUserByName(name, len);
|
||||
|
||||
if (!acl_user) {
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
return authenticateClientWithUser(ctx, acl_user, callback, privdata, client_id);
|
||||
}
|
||||
|
||||
/* Deauthenticate and close the client. The client resources will not be
|
||||
* be immediately freed, but will be cleaned up in a background job. This is
|
||||
* the recommended way to deauthenicate a client since most clients can't
|
||||
* handle users becomming deauthenticated. Returns REDISMODULE_ERR when the
|
||||
* client doesn't exist and REDISMODULE_OK when the operation was successful.
|
||||
*
|
||||
* The client ID can be obtained from the AuthenticateClientWithUser and
|
||||
* AuthenticateClientWithACLUser APIs or through other APIs such as
|
||||
* server events.
|
||||
*
|
||||
* This function is not thread safe, and must be executed within the context
|
||||
* of a command or thread safe context. */
|
||||
int RM_DeauthenticateAndCloseClient(RedisModuleCtx *ctx, uint64_t client_id) {
|
||||
UNUSED(ctx);
|
||||
client *c = lookupClientByID(client_id);
|
||||
if (c == NULL) return REDISMODULE_ERR;
|
||||
|
||||
/* Revoke also marks client to be closed ASAP */
|
||||
revokeClientAuthentication(c);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules Dictionary API
|
||||
*
|
||||
@ -7078,6 +7293,7 @@ int moduleUnload(sds name) {
|
||||
}
|
||||
}
|
||||
|
||||
moduleFreeAuthenticatedClients(module);
|
||||
moduleUnregisterCommands(module);
|
||||
moduleUnregisterSharedAPI(module);
|
||||
moduleUnregisterUsedAPI(module);
|
||||
@ -7561,4 +7777,10 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(ScanCursorRestart);
|
||||
REGISTER_API(Scan);
|
||||
REGISTER_API(ScanKey);
|
||||
REGISTER_API(CreateModuleUser);
|
||||
REGISTER_API(SetModuleUserACL);
|
||||
REGISTER_API(FreeModuleUser);
|
||||
REGISTER_API(DeauthenticateAndCloseClient);
|
||||
REGISTER_API(AuthenticateClientWithACLUser);
|
||||
REGISTER_API(AuthenticateClientWithUser);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ endif
|
||||
|
||||
.SUFFIXES: .c .so .xo .o
|
||||
|
||||
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so hellohook.so
|
||||
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
@ -53,6 +53,11 @@ hellohook.xo: ../redismodule.h
|
||||
hellohook.so: hellohook.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
helloacl.xo: ../redismodule.h
|
||||
|
||||
helloacl.so: helloacl.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
testmodule.xo: ../redismodule.h
|
||||
|
||||
testmodule.so: testmodule.xo
|
||||
|
195
src/modules/helloacl.c
Normal file
195
src/modules/helloacl.c
Normal file
@ -0,0 +1,195 @@
|
||||
/* ACL API example - An example of performing custom password authentication
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright 2019 Amazon.com, Inc. or its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "../redismodule.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// A simple global user
|
||||
static RedisModuleUser *global;
|
||||
static uint64_t global_auth_client_id = 0;
|
||||
|
||||
/* HELLOACL.REVOKE
|
||||
* Synchronously revoke access from a user. */
|
||||
int RevokeCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (global_auth_client_id) {
|
||||
RedisModule_DisconnectClient(ctx, global_auth_client_id);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
} else {
|
||||
return RedisModule_ReplyWithError(ctx, "Global user currently not used");
|
||||
}
|
||||
}
|
||||
|
||||
/* HELLOACL.RESET
|
||||
* Synchronously delete and re-create a module user. */
|
||||
int ResetCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
RedisModule_FreeModuleUser(global);
|
||||
global = RedisModule_CreateModuleUser("global");
|
||||
RedisModule_SetModuleUserACL(global, "allcommands");
|
||||
RedisModule_SetModuleUserACL(global, "allkeys");
|
||||
RedisModule_SetModuleUserACL(global, "on");
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
/* Callback handler for user changes, use this to notify a module of
|
||||
* changes to users authenticated by the module */
|
||||
void HelloACL_UserChanged(uint64_t client_id, void *privdata) {
|
||||
REDISMODULE_NOT_USED(privdata);
|
||||
REDISMODULE_NOT_USED(client_id);
|
||||
global_auth_client_id = 0;
|
||||
}
|
||||
|
||||
/* HELLOACL.AUTHGLOBAL
|
||||
* Synchronously assigns a module user to the current context. */
|
||||
int AuthGlobalCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (global_auth_client_id) {
|
||||
return RedisModule_ReplyWithError(ctx, "Global user currently used");
|
||||
}
|
||||
|
||||
RedisModule_AuthenticateClientWithUser(ctx, global, HelloACL_UserChanged, NULL, &global_auth_client_id);
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
#define TIMEOUT_TIME 1000
|
||||
|
||||
/* Reply callback for auth command HELLOACL.AUTHASYNC */
|
||||
int HelloACL_Reply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
size_t length;
|
||||
|
||||
RedisModuleString *user_string = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
const char *name = RedisModule_StringPtrLen(user_string, &length);
|
||||
|
||||
if (RedisModule_AuthenticateClientWithACLUser(ctx, name, length, NULL, NULL, NULL) ==
|
||||
REDISMODULE_ERR) {
|
||||
return RedisModule_ReplyWithError(ctx, "Invalid Username or password");
|
||||
}
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
/* Timeout callback for auth command HELLOACL.AUTHASYNC */
|
||||
int HelloACL_Timeout(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
|
||||
}
|
||||
|
||||
/* Private data frees data for HELLOACL.AUTHASYNC command. */
|
||||
void HelloACL_FreeData(RedisModuleCtx *ctx, void *privdata) {
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
RedisModule_FreeString(NULL, privdata);
|
||||
}
|
||||
|
||||
/* Background authentication can happen here. */
|
||||
void *HelloACL_ThreadMain(void *args) {
|
||||
void **targs = args;
|
||||
RedisModuleBlockedClient *bc = targs[0];
|
||||
RedisModuleString *user = targs[1];
|
||||
RedisModule_Free(targs);
|
||||
|
||||
RedisModule_UnblockClient(bc,user);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* HELLOACL.AUTHASYNC
|
||||
* Asynchronously assigns an ACL user to the current context. */
|
||||
int AuthAsyncCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
|
||||
pthread_t tid;
|
||||
RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, HelloACL_Reply, HelloACL_Timeout, HelloACL_FreeData, TIMEOUT_TIME);
|
||||
|
||||
|
||||
void **targs = RedisModule_Alloc(sizeof(void*)*2);
|
||||
targs[0] = bc;
|
||||
targs[1] = RedisModule_CreateStringFromString(NULL, argv[1]);
|
||||
|
||||
if (pthread_create(&tid, NULL, HelloACL_ThreadMain, targs) != 0) {
|
||||
RedisModule_AbortBlock(bc);
|
||||
return RedisModule_ReplyWithError(ctx, "-ERR Can't start thread");
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* This function must be present on each Redis module. It is used in order to
|
||||
* register the commands into the Redis server. */
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (RedisModule_Init(ctx,"helloacl",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"helloacl.reset",
|
||||
ResetCommand_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"helloacl.revoke",
|
||||
RevokeCommand_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"helloacl.authglobal",
|
||||
AuthGlobalCommand_RedisCommand,"no-auth",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"helloacl.authasync",
|
||||
AuthAsyncCommand_RedisCommand,"no-auth",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
global = RedisModule_CreateModuleUser("global");
|
||||
RedisModule_SetModuleUserACL(global, "allcommands");
|
||||
RedisModule_SetModuleUserACL(global, "allkeys");
|
||||
RedisModule_SetModuleUserACL(global, "on");
|
||||
|
||||
global_auth_client_id = 0;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
@ -154,6 +154,9 @@ client *createClient(connection *conn) {
|
||||
c->peerid = NULL;
|
||||
c->client_list_node = NULL;
|
||||
c->client_tracking_redirection = 0;
|
||||
c->auth_callback = NULL;
|
||||
c->auth_callback_privdata = NULL;
|
||||
c->auth_module = NULL;
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||
if (conn) linkClient(c);
|
||||
@ -1051,6 +1054,9 @@ void freeClient(client *c) {
|
||||
c);
|
||||
}
|
||||
|
||||
/* Notify module system that this client auth status changed. */
|
||||
moduleNotifyUserChanged(c);
|
||||
|
||||
/* If it is our master that's beging disconnected we should make sure
|
||||
* to cache the state to try a partial resynchronization later.
|
||||
*
|
||||
|
@ -395,6 +395,8 @@ typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
||||
typedef struct RedisModuleInfoCtx RedisModuleInfoCtx;
|
||||
typedef struct RedisModuleServerInfoData RedisModuleServerInfoData;
|
||||
typedef struct RedisModuleScanCursor RedisModuleScanCursor;
|
||||
typedef struct RedisModuleUser RedisModuleUser;
|
||||
typedef struct RedisModuleAuthCtx RedisModuleAuthCtx;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
||||
@ -414,6 +416,7 @@ typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *us
|
||||
typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
|
||||
typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keyname, RedisModuleKey *key, void *privdata);
|
||||
typedef void (*RedisModuleScanKeyCB)(RedisModuleKey *key, RedisModuleString *field, RedisModuleString *value, void *privdata);
|
||||
typedef void (*RedisModuleUserChangedFunc) (uint64_t client_id, void *privdata);
|
||||
|
||||
#define REDISMODULE_TYPE_METHOD_VERSION 2
|
||||
typedef struct RedisModuleTypeMethods {
|
||||
@ -613,7 +616,7 @@ void REDISMODULE_API_FUNC(RedisModule_ScanCursorRestart)(RedisModuleScanCursor *
|
||||
void REDISMODULE_API_FUNC(RedisModule_ScanCursorDestroy)(RedisModuleScanCursor *cursor);
|
||||
int REDISMODULE_API_FUNC(RedisModule_Scan)(RedisModuleCtx *ctx, RedisModuleScanCursor *cursor, RedisModuleScanCB fn, void *privdata);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ScanKey)(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleScanKeyCB fn, void *privdata);
|
||||
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
/* Experimental APIs */
|
||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||
#define REDISMODULE_EXPERIMENTAL_API_VERSION 3
|
||||
@ -660,6 +663,12 @@ int REDISMODULE_API_FUNC(RedisModule_ExitFromChild)(int retcode);
|
||||
int REDISMODULE_API_FUNC(RedisModule_KillForkChild)(int child_pid);
|
||||
float REDISMODULE_API_FUNC(RedisModule_GetUsedMemoryRatio)();
|
||||
size_t REDISMODULE_API_FUNC(RedisModule_MallocSize)(void* ptr);
|
||||
RedisModuleUser *REDISMODULE_API_FUNC(RedisModule_CreateModuleUser)(const char *name);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeModuleUser)(RedisModuleUser *user);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetModuleUserACL)(RedisModuleUser *user, const char* acl);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id);
|
||||
#endif
|
||||
|
||||
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
|
||||
@ -891,6 +900,12 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(KillForkChild);
|
||||
REDISMODULE_GET_API(GetUsedMemoryRatio);
|
||||
REDISMODULE_GET_API(MallocSize);
|
||||
REDISMODULE_GET_API(CreateModuleUser);
|
||||
REDISMODULE_GET_API(FreeModuleUser);
|
||||
REDISMODULE_GET_API(SetModuleUserACL);
|
||||
REDISMODULE_GET_API(DeauthenticateAndCloseClient);
|
||||
REDISMODULE_GET_API(AuthenticateClientWithACLUser);
|
||||
REDISMODULE_GET_API(AuthenticateClientWithUser);
|
||||
#endif
|
||||
|
||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||
|
@ -461,8 +461,8 @@ struct redisCommand sentinelcmds[] = {
|
||||
{"role",sentinelRoleCommand,1,"ok-loading",0,NULL,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"read-only no-script",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"auth",authCommand,2,"no-script ok-loading ok-stale fast",0,NULL,0,0,0,0,0},
|
||||
{"hello",helloCommand,-2,"no-script fast",0,NULL,0,0,0,0,0}
|
||||
{"auth",authCommand,2,"no-auth no-script ok-loading ok-stale fast",0,NULL,0,0,0,0,0},
|
||||
{"hello",helloCommand,-2,"no-auth no-script fast",0,NULL,0,0,0,0,0}
|
||||
};
|
||||
|
||||
/* This function overwrites a few normal Redis config default with Sentinel
|
||||
|
11
src/server.c
11
src/server.c
@ -629,7 +629,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"auth",authCommand,-2,
|
||||
"no-script ok-loading ok-stale fast no-monitor no-slowlog @connection",
|
||||
"no-auth no-script ok-loading ok-stale fast no-monitor no-slowlog @connection",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
/* We don't allow PING during loading since in Redis PING is used as
|
||||
@ -824,7 +824,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"hello",helloCommand,-2,
|
||||
"no-script fast no-monitor no-slowlog @connection",
|
||||
"no-auth no-script fast no-monitor no-slowlog @connection",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
/* EVAL can modify the dataset, however it is not flagged as a write
|
||||
@ -2925,6 +2925,8 @@ int populateCommandTableParseFlags(struct redisCommand *c, char *strflags) {
|
||||
c->flags |= CMD_ASKING;
|
||||
} else if (!strcasecmp(flag,"fast")) {
|
||||
c->flags |= CMD_FAST | CMD_CATEGORY_FAST;
|
||||
} else if (!strcasecmp(flag,"no-auth")) {
|
||||
c->flags |= CMD_NO_AUTH;
|
||||
} else {
|
||||
/* Parse ACL categories here if the flag name starts with @. */
|
||||
uint64_t catflag;
|
||||
@ -3345,8 +3347,9 @@ int processCommand(client *c) {
|
||||
DefaultUser->flags & USER_FLAG_DISABLED) &&
|
||||
!c->authenticated;
|
||||
if (auth_required) {
|
||||
/* AUTH and HELLO are valid even in non authenticated state. */
|
||||
if (c->cmd->proc != authCommand && c->cmd->proc != helloCommand) {
|
||||
/* AUTH and HELLO and no auth modules are valid even in
|
||||
* non-authenticated state. */
|
||||
if (!(c->cmd->flags & CMD_NO_AUTH)) {
|
||||
flagTransaction(c);
|
||||
addReply(c,shared.noautherr);
|
||||
return C_OK;
|
||||
|
61
src/server.h
61
src/server.h
@ -166,33 +166,34 @@ typedef long long ustime_t; /* microsecond time type. */
|
||||
#define CMD_SKIP_SLOWLOG (1ULL<<12) /* "no-slowlog" flag */
|
||||
#define CMD_ASKING (1ULL<<13) /* "cluster-asking" flag */
|
||||
#define CMD_FAST (1ULL<<14) /* "fast" flag */
|
||||
#define CMD_NO_AUTH (1ULL<<15) /* "no-auth" flag */
|
||||
|
||||
/* Command flags used by the module system. */
|
||||
#define CMD_MODULE_GETKEYS (1ULL<<15) /* Use the modules getkeys interface. */
|
||||
#define CMD_MODULE_NO_CLUSTER (1ULL<<16) /* Deny on Redis Cluster. */
|
||||
#define CMD_MODULE_GETKEYS (1ULL<<16) /* Use the modules getkeys interface. */
|
||||
#define CMD_MODULE_NO_CLUSTER (1ULL<<17) /* Deny on Redis Cluster. */
|
||||
|
||||
/* Command flags that describe ACLs categories. */
|
||||
#define CMD_CATEGORY_KEYSPACE (1ULL<<17)
|
||||
#define CMD_CATEGORY_READ (1ULL<<18)
|
||||
#define CMD_CATEGORY_WRITE (1ULL<<19)
|
||||
#define CMD_CATEGORY_SET (1ULL<<20)
|
||||
#define CMD_CATEGORY_SORTEDSET (1ULL<<21)
|
||||
#define CMD_CATEGORY_LIST (1ULL<<22)
|
||||
#define CMD_CATEGORY_HASH (1ULL<<23)
|
||||
#define CMD_CATEGORY_STRING (1ULL<<24)
|
||||
#define CMD_CATEGORY_BITMAP (1ULL<<25)
|
||||
#define CMD_CATEGORY_HYPERLOGLOG (1ULL<<26)
|
||||
#define CMD_CATEGORY_GEO (1ULL<<27)
|
||||
#define CMD_CATEGORY_STREAM (1ULL<<28)
|
||||
#define CMD_CATEGORY_PUBSUB (1ULL<<29)
|
||||
#define CMD_CATEGORY_ADMIN (1ULL<<30)
|
||||
#define CMD_CATEGORY_FAST (1ULL<<31)
|
||||
#define CMD_CATEGORY_SLOW (1ULL<<32)
|
||||
#define CMD_CATEGORY_BLOCKING (1ULL<<33)
|
||||
#define CMD_CATEGORY_DANGEROUS (1ULL<<34)
|
||||
#define CMD_CATEGORY_CONNECTION (1ULL<<35)
|
||||
#define CMD_CATEGORY_TRANSACTION (1ULL<<36)
|
||||
#define CMD_CATEGORY_SCRIPTING (1ULL<<37)
|
||||
#define CMD_CATEGORY_KEYSPACE (1ULL<<18)
|
||||
#define CMD_CATEGORY_READ (1ULL<<19)
|
||||
#define CMD_CATEGORY_WRITE (1ULL<<20)
|
||||
#define CMD_CATEGORY_SET (1ULL<<21)
|
||||
#define CMD_CATEGORY_SORTEDSET (1ULL<<22)
|
||||
#define CMD_CATEGORY_LIST (1ULL<<23)
|
||||
#define CMD_CATEGORY_HASH (1ULL<<24)
|
||||
#define CMD_CATEGORY_STRING (1ULL<<25)
|
||||
#define CMD_CATEGORY_BITMAP (1ULL<<26)
|
||||
#define CMD_CATEGORY_HYPERLOGLOG (1ULL<<27)
|
||||
#define CMD_CATEGORY_GEO (1ULL<<28)
|
||||
#define CMD_CATEGORY_STREAM (1ULL<<29)
|
||||
#define CMD_CATEGORY_PUBSUB (1ULL<<30)
|
||||
#define CMD_CATEGORY_ADMIN (1ULL<<31)
|
||||
#define CMD_CATEGORY_FAST (1ULL<<32)
|
||||
#define CMD_CATEGORY_SLOW (1ULL<<33)
|
||||
#define CMD_CATEGORY_BLOCKING (1ULL<<34)
|
||||
#define CMD_CATEGORY_DANGEROUS (1ULL<<35)
|
||||
#define CMD_CATEGORY_CONNECTION (1ULL<<36)
|
||||
#define CMD_CATEGORY_TRANSACTION (1ULL<<37)
|
||||
#define CMD_CATEGORY_SCRIPTING (1ULL<<38)
|
||||
|
||||
/* AOF states */
|
||||
#define AOF_OFF 0 /* AOF is off */
|
||||
@ -480,6 +481,10 @@ typedef void (*moduleTypeDigestFunc)(struct RedisModuleDigest *digest, void *val
|
||||
typedef size_t (*moduleTypeMemUsageFunc)(const void *value);
|
||||
typedef void (*moduleTypeFreeFunc)(void *value);
|
||||
|
||||
/* TODO */
|
||||
typedef void (*RedisModuleUserChangedFunc) (uint64_t client_id, void *privdata);
|
||||
|
||||
|
||||
/* The module type, which is referenced in each value of a given type, defines
|
||||
* the methods and links to the module exporting the type. */
|
||||
typedef struct RedisModuleType {
|
||||
@ -798,6 +803,13 @@ typedef struct client {
|
||||
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
|
||||
sds peerid; /* Cached peer ID. */
|
||||
listNode *client_list_node; /* list node in client list */
|
||||
RedisModuleUserChangedFunc auth_callback; /* Callback to execute when the
|
||||
* authentication changes */
|
||||
void *auth_callback_privdata; /* Private data that is passed when the auth
|
||||
* callback is executed */
|
||||
void *auth_module; /* The module that owns the callback, which is used
|
||||
* to disconnect the client if the module is
|
||||
* unloaded to allow for cleanup. */
|
||||
|
||||
/* If this client is in tracking mode and this field is non zero,
|
||||
* invalidation messages for keys fetched by this client will be send to
|
||||
@ -1516,6 +1528,7 @@ void processModuleLoadingProgressEvent(int is_aof);
|
||||
int moduleTryServeClientBlockedOnKey(client *c, robj *key);
|
||||
void moduleUnblockClient(client *c);
|
||||
int moduleClientIsBlockedOnKeys(client *c);
|
||||
void moduleNotifyUserChanged(client *c);
|
||||
|
||||
/* Utils */
|
||||
long long ustime(void);
|
||||
@ -1809,6 +1822,8 @@ int ACLLoadConfiguredUsers(void);
|
||||
sds ACLDescribeUser(user *u);
|
||||
void ACLLoadUsersAtStartup(void);
|
||||
void addReplyCommandCategories(client *c, struct redisCommand *cmd);
|
||||
user *ACLCreateUnlinkedUser();
|
||||
void ACLFreeUserAndKillClients(user *u);
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
|
@ -21,7 +21,8 @@ TEST_MODULES = \
|
||||
hooks.so \
|
||||
blockonkeys.so \
|
||||
scan.so \
|
||||
datatype.so
|
||||
datatype.so \
|
||||
auth.so
|
||||
|
||||
.PHONY: all
|
||||
|
||||
|
118
tests/modules/auth.c
Normal file
118
tests/modules/auth.c
Normal file
@ -0,0 +1,118 @@
|
||||
/* ACL API example - An example of performing custom password authentication
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright 2019 Amazon.com, Inc. or its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "redismodule.h"
|
||||
|
||||
// A simple global user
|
||||
static RedisModuleUser *global;
|
||||
static long long client_change_delta = 0;
|
||||
|
||||
void UserChangedCallback(uint64_t client_id, void *privdata) {
|
||||
REDISMODULE_NOT_USED(privdata);
|
||||
REDISMODULE_NOT_USED(client_id);
|
||||
client_change_delta++;
|
||||
}
|
||||
|
||||
int Auth_CreateModuleUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
|
||||
if (global) {
|
||||
RedisModule_FreeModuleUser(global);
|
||||
}
|
||||
|
||||
global = RedisModule_CreateModuleUser("global");
|
||||
RedisModule_SetModuleUserACL(global, "allcommands");
|
||||
RedisModule_SetModuleUserACL(global, "allkeys");
|
||||
RedisModule_SetModuleUserACL(global, "on");
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
int Auth_AuthModuleUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
uint64_t client_id;
|
||||
RedisModule_AuthenticateClientWithUser(ctx, global, UserChangedCallback, NULL, &client_id);
|
||||
|
||||
return RedisModule_ReplyWithLongLong(ctx, (uint64_t) client_id);
|
||||
}
|
||||
|
||||
int Auth_AuthRealUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
|
||||
size_t length;
|
||||
uint64_t client_id;
|
||||
|
||||
RedisModuleString *user_string = argv[1];
|
||||
const char *name = RedisModule_StringPtrLen(user_string, &length);
|
||||
|
||||
if (RedisModule_AuthenticateClientWithACLUser(ctx, name, length,
|
||||
UserChangedCallback, NULL, &client_id) == REDISMODULE_ERR) {
|
||||
return RedisModule_ReplyWithError(ctx, "Invalid user");
|
||||
}
|
||||
|
||||
return RedisModule_ReplyWithLongLong(ctx, (uint64_t) client_id);
|
||||
}
|
||||
|
||||
int Auth_ChangeCount(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
long long result = client_change_delta;
|
||||
client_change_delta = 0;
|
||||
return RedisModule_ReplyWithLongLong(ctx, result);
|
||||
}
|
||||
|
||||
/* This function must be present on each Redis module. It is used in order to
|
||||
* register the commands into the Redis server. */
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (RedisModule_Init(ctx,"testacl",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"auth.authrealuser",
|
||||
Auth_AuthRealUser,"no-auth",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"auth.createmoduleuser",
|
||||
Auth_CreateModuleUser,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"auth.authmoduleuser",
|
||||
Auth_AuthModuleUser,"no-auth",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"auth.changecount",
|
||||
Auth_ChangeCount,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
client_change_delta = 0;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
71
tests/unit/moduleapi/auth.tcl
Normal file
71
tests/unit/moduleapi/auth.tcl
Normal file
@ -0,0 +1,71 @@
|
||||
set testmodule [file normalize tests/modules/auth.so]
|
||||
|
||||
start_server {tags {"modules"}} {
|
||||
r module load $testmodule
|
||||
|
||||
test {Modules can create a user that can be authenticated} {
|
||||
# Make sure we start authenticated with default user
|
||||
r auth default ""
|
||||
assert_equal [r acl whoami] "default"
|
||||
r auth.createmoduleuser
|
||||
|
||||
set id [r auth.authmoduleuser]
|
||||
assert_equal [r client id] $id
|
||||
|
||||
# Verify returned id is the same as our current id and
|
||||
# we are authenticated with the specified user
|
||||
assert_equal [r acl whoami] "global"
|
||||
}
|
||||
|
||||
test {De-authenticating clients is tracked and kills clients} {
|
||||
assert_equal [r auth.changecount] 0
|
||||
r auth.createmoduleuser
|
||||
|
||||
# Catch the I/O exception that was thrown when Redis
|
||||
# disconnected with us.
|
||||
catch { [r ping] } e
|
||||
assert_match {*I/O*} $e
|
||||
|
||||
# Check that a user change was registered
|
||||
assert_equal [r auth.changecount] 1
|
||||
}
|
||||
|
||||
test {Modules cant authenticate with ACLs users that dont exist} {
|
||||
catch { [r auth.authrealuser auth-module-test-fake] } e
|
||||
assert_match {*Invalid user*} $e
|
||||
}
|
||||
|
||||
test {Modules can authenticate with ACL users} {
|
||||
assert_equal [r acl whoami] "default"
|
||||
|
||||
# Create user to auth into
|
||||
r acl setuser auth-module-test on allkeys allcommands
|
||||
|
||||
set id [r auth.authrealuser auth-module-test]
|
||||
|
||||
# Verify returned id is the same as our current id and
|
||||
# we are authenticated with the specified user
|
||||
assert_equal [r client id] $id
|
||||
assert_equal [r acl whoami] "auth-module-test"
|
||||
}
|
||||
|
||||
test {Client callback is called on user switch} {
|
||||
assert_equal [r auth.changecount] 0
|
||||
|
||||
# Auth again and validate change count
|
||||
r auth.authrealuser auth-module-test
|
||||
assert_equal [r auth.changecount] 1
|
||||
|
||||
# Re-auth with the default user
|
||||
r auth default ""
|
||||
assert_equal [r auth.changecount] 1
|
||||
assert_equal [r acl whoami] "default"
|
||||
|
||||
# Re-auth with the default user again, to
|
||||
# verify the callback isn't fired again
|
||||
r auth default ""
|
||||
assert_equal [r auth.changecount] 0
|
||||
assert_equal [r acl whoami] "default"
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user