@@ -355,6 +355,21 @@ list *RedisModule_EventListeners; /* Global list of all the active events. */
355
355
unsigned long long ModulesInHooks = 0 ; /* Total number of modules in hooks
356
356
callbacks right now. */
357
357
358
+ /* Data structures related to the redis module users */
359
+
360
+ /* This callback type is called by moduleNotifyUserChanged() every time
361
+ * a user authenticated via the module API is associated with a different
362
+ * user or gets disconnected. */
363
+ typedef void (* RedisModuleUserChangedFunc ) (uint64_t client_id , void * privdata );
364
+
365
+ /* This is the object returned by RM_CreateModuleUser(). The module API is
366
+ * able to create users, set ACLs to such users, and later authenticate
367
+ * clients using such newly created users. */
368
+ typedef struct RedisModuleUser {
369
+ user * user ; /* Reference to the real redis user */
370
+ } RedisModuleUser ;
371
+
372
+
358
373
/* --------------------------------------------------------------------------
359
374
* Prototypes
360
375
* -------------------------------------------------------------------------- */
@@ -719,6 +734,7 @@ int commandFlagsFromString(char *s) {
719
734
else if (!strcasecmp (t ,"allow-stale" )) flags |= CMD_STALE ;
720
735
else if (!strcasecmp (t ,"no-monitor" )) flags |= CMD_SKIP_MONITOR ;
721
736
else if (!strcasecmp (t ,"fast" )) flags |= CMD_FAST ;
737
+ else if (!strcasecmp (t ,"no-auth" )) flags |= CMD_NO_AUTH ;
722
738
else if (!strcasecmp (t ,"getkeys-api" )) flags |= CMD_MODULE_GETKEYS ;
723
739
else if (!strcasecmp (t ,"no-cluster" )) flags |= CMD_MODULE_NO_CLUSTER ;
724
740
else break ;
@@ -780,6 +796,9 @@ int commandFlagsFromString(char *s) {
780
796
* example, is unable to report the position of the
781
797
* keys, programmatically creates key names, or any
782
798
* other reason.
799
+ * * **"no-auth"**: This command can be run by an un-authenticated client.
800
+ * Normally this is used by a command that is used
801
+ * to authenticate a client.
783
802
*/
784
803
int RM_CreateCommand (RedisModuleCtx * ctx , const char * name , RedisModuleCmdFunc cmdfunc , const char * strflags , int firstkey , int lastkey , int keystep ) {
785
804
int flags = strflags ? commandFlagsFromString ((char * )strflags ) : 0 ;
@@ -5236,6 +5255,202 @@ int RM_GetTimerInfo(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remain
5236
5255
return REDISMODULE_OK ;
5237
5256
}
5238
5257
5258
+ /* --------------------------------------------------------------------------
5259
+ * Modules ACL API
5260
+ *
5261
+ * Implements a hook into the authentication and authorization within Redis.
5262
+ * --------------------------------------------------------------------------*/
5263
+
5264
+ /* This function is called when a client's user has changed and invoked a
5265
+ * a modules client changed callback if it was set. This callback should
5266
+ * cleanup any state the module was tracking about this client.
5267
+ *
5268
+ * A client's user can be changed through the AUTH command, module
5269
+ * authentication, and when the client is freed. */
5270
+ void moduleNotifyUserChanged (client * c ) {
5271
+ if (c -> auth_callback ) {
5272
+ c -> auth_callback (c -> id , c -> auth_callback_privdata );
5273
+
5274
+ /* The callback will fire exactly once, even if the user remains
5275
+ * the same, it is expected to completely clean up it's state
5276
+ * so all references are removed */
5277
+ c -> auth_callback = NULL ;
5278
+ c -> auth_callback_privdata = NULL ;
5279
+ c -> auth_module = NULL ;
5280
+ }
5281
+ }
5282
+
5283
+ void revokeClientAuthentication (client * c ) {
5284
+ /* Fire the client changed handler now in case we are unloading the module
5285
+ * and need to cleanup. */
5286
+ moduleNotifyUserChanged (c );
5287
+
5288
+ c -> user = DefaultUser ;
5289
+ c -> authenticated = 0 ;
5290
+ freeClientAsync (c );
5291
+ }
5292
+
5293
+ /* Cleanup all clients that have been authenticated with this module. This
5294
+ * is called from onUnload() to give the module a chance to cleanup any
5295
+ * resources associated with the authentication. */
5296
+ static void moduleFreeAuthenticatedClients (RedisModule * module ) {
5297
+ listIter li ;
5298
+ listNode * ln ;
5299
+ listRewind (server .clients ,& li );
5300
+ while ((ln = listNext (& li )) != NULL ) {
5301
+ client * c = listNodeValue (ln );
5302
+ if (!c -> auth_module ) continue ;
5303
+
5304
+ RedisModule * auth_module = (RedisModule * ) c -> auth_module ;
5305
+ if (auth_module == module ) {
5306
+ revokeClientAuthentication (c );
5307
+ }
5308
+ }
5309
+ }
5310
+
5311
+ /* Creates a Redis ACL user that the module can use to authenticate a client.
5312
+ * After obtaining the user, the module should set what such user can do
5313
+ * using the RM_SetUserACL() function. Once configured, the user
5314
+ * can be used in order to authenticate a connection, with the specified
5315
+ * ACL rules, using the RedisModule_AuthClientWithUser() function.
5316
+ *
5317
+ * Note that:
5318
+ *
5319
+ * * Users created here are not listed by the ACL command.
5320
+ * * Users created here are not checked for duplicated name, so it's up to
5321
+ * the module calling this function to take care of not creating users
5322
+ * with the same name.
5323
+ * * The created user can be used to authenticate multiple Redis connections.
5324
+ *
5325
+ * The caller can later free the user using the function
5326
+ * RM_FreeModuleUser(). When this function is called, if there are
5327
+ * still clients authenticated with this user, they are disconnected.
5328
+ * The function to free the user should only be used when the caller really
5329
+ * wants to invalidate the user to define a new one with different
5330
+ * capabilities. */
5331
+ RedisModuleUser * RM_CreateModuleUser (const char * name ) {
5332
+ RedisModuleUser * new_user = zmalloc (sizeof (RedisModuleUser ));
5333
+ new_user -> user = ACLCreateUnlinkedUser ();
5334
+
5335
+ /* Free the previous temporarily assigned name to assign the new one */
5336
+ sdsfree (new_user -> user -> name );
5337
+ new_user -> user -> name = sdsnew (name );
5338
+ return new_user ;
5339
+ }
5340
+
5341
+ /* Frees a given user and disconnects all of the clients that have been
5342
+ * authenticated with it. See RM_CreateModuleUser for detailed usage.*/
5343
+ int RM_FreeModuleUser (RedisModuleUser * user ) {
5344
+ ACLFreeUserAndKillClients (user -> user );
5345
+ zfree (user );
5346
+ return REDISMODULE_OK ;
5347
+ }
5348
+
5349
+ /* Sets the permissions of a user created through the redis module
5350
+ * interface. The syntax is the same as ACL SETUSER, so refer to the
5351
+ * documentation in acl.c for more information. See RM_CreateModuleUser
5352
+ * for detailed usage.
5353
+ *
5354
+ * Returns REDISMODULE_OK on success and REDISMODULE_ERR on failure
5355
+ * and will set an errno describing why the operation failed. */
5356
+ int RM_SetModuleUserACL (RedisModuleUser * user , const char * acl ) {
5357
+ return ACLSetUser (user -> user , acl , -1 );
5358
+ }
5359
+
5360
+ /* Authenticate the client associated with the context with
5361
+ * the provided user. Returns REDISMODULE_OK on success and
5362
+ * REDISMODULE_ERR on error.
5363
+ *
5364
+ * This authentication can be tracked with the optional callback and private
5365
+ * data fields. The callback will be called whenever the user of the client
5366
+ * changes. This callback should be used to cleanup any state that is being
5367
+ * kept in the module related to the client authentication. It will only be
5368
+ * called once, even when the user hasn't changed, in order to allow for a
5369
+ * new callback to be specified. If this authentication does not need to be
5370
+ * tracked, pass in NULL for the callback and privdata.
5371
+ *
5372
+ * If client_id is not NULL, it will be filled with the id of the client
5373
+ * that was authenticated. This can be used with the
5374
+ * RM_DeauthenticateAndCloseClient() API in order to deauthenticate a
5375
+ * previously authenticated client if the authentication is no longer valid.
5376
+ *
5377
+ * For expensive authentication operations, it is recommended to block the
5378
+ * client and do the authentication in the background then attach the user
5379
+ * to the client in a threadsafe context. */
5380
+ static int authenticateClientWithUser (RedisModuleCtx * ctx , user * user , RedisModuleUserChangedFunc callback , void * privdata , uint64_t * client_id ) {
5381
+ if (user -> flags & USER_FLAG_DISABLED ) {
5382
+ return REDISMODULE_ERR ;
5383
+ }
5384
+
5385
+ /* Freeing the client would result in moduleNotifyUserChanged() to be
5386
+ * called later, however since we use revokeClientAuthentication() also
5387
+ * in moduleFreeAuthenticatedClients() to implement module unloading, we
5388
+ * do this action ASAP: this way if the module is unloaded, when the client
5389
+ * is eventually freed we don't rely on the module to still exist. */
5390
+ moduleNotifyUserChanged (ctx -> client );
5391
+
5392
+ ctx -> client -> user = user ;
5393
+ ctx -> client -> authenticated = 1 ;
5394
+
5395
+ if (callback ) {
5396
+ ctx -> client -> auth_callback = callback ;
5397
+ ctx -> client -> auth_callback_privdata = privdata ;
5398
+ ctx -> client -> auth_module = ctx -> module ;
5399
+ }
5400
+
5401
+ if (client_id ) {
5402
+ * client_id = ctx -> client -> id ;
5403
+ }
5404
+
5405
+ return REDISMODULE_OK ;
5406
+ }
5407
+
5408
+
5409
+ /* Authenticate the current context's user with the provided redis acl user.
5410
+ * Returns REDISMODULE_ERR if the user is disabled.
5411
+ *
5412
+ * See authenticateClientWithUser for information about callback and client_id,
5413
+ * and general usage for authentication. */
5414
+ int RM_AuthenticateClientWithUser (RedisModuleCtx * ctx , RedisModuleUser * module_user , RedisModuleUserChangedFunc callback , void * privdata , uint64_t * client_id ) {
5415
+ return authenticateClientWithUser (ctx , module_user -> user , callback , privdata , client_id );
5416
+ }
5417
+
5418
+ /* Authenticate the current context's user with the provided redis acl user.
5419
+ * Returns REDISMODULE_ERR if the user is disabled or the user does not exist.
5420
+ *
5421
+ * See authenticateClientWithUser for information about callback and client_id,
5422
+ * and general usage for authentication. */
5423
+ int RM_AuthenticateClientWithACLUser (RedisModuleCtx * ctx , const char * name , size_t len , RedisModuleUserChangedFunc callback , void * privdata , uint64_t * client_id ) {
5424
+ user * acl_user = ACLGetUserByName (name , len );
5425
+
5426
+ if (!acl_user ) {
5427
+ return REDISMODULE_ERR ;
5428
+ }
5429
+ return authenticateClientWithUser (ctx , acl_user , callback , privdata , client_id );
5430
+ }
5431
+
5432
+ /* Deauthenticate and close the client. The client resources will not be
5433
+ * be immediately freed, but will be cleaned up in a background job. This is
5434
+ * the recommended way to deauthenicate a client since most clients can't
5435
+ * handle users becomming deauthenticated. Returns REDISMODULE_ERR when the
5436
+ * client doesn't exist and REDISMODULE_OK when the operation was successful.
5437
+ *
5438
+ * The client ID can be obtained from the AuthenticateClientWithUser and
5439
+ * AuthenticateClientWithACLUser APIs or through other APIs such as
5440
+ * server events.
5441
+ *
5442
+ * This function is not thread safe, and must be executed within the context
5443
+ * of a command or thread safe context. */
5444
+ int RM_DeauthenticateAndCloseClient (RedisModuleCtx * ctx , uint64_t client_id ) {
5445
+ UNUSED (ctx );
5446
+ client * c = lookupClientByID (client_id );
5447
+ if (c == NULL ) return REDISMODULE_ERR ;
5448
+
5449
+ /* Revoke also marks client to be closed ASAP */
5450
+ revokeClientAuthentication (c );
5451
+ return REDISMODULE_OK ;
5452
+ }
5453
+
5239
5454
/* --------------------------------------------------------------------------
5240
5455
* Modules Dictionary API
5241
5456
*
@@ -7078,6 +7293,7 @@ int moduleUnload(sds name) {
7078
7293
}
7079
7294
}
7080
7295
7296
+ moduleFreeAuthenticatedClients (module );
7081
7297
moduleUnregisterCommands (module );
7082
7298
moduleUnregisterSharedAPI (module );
7083
7299
moduleUnregisterUsedAPI (module );
@@ -7561,4 +7777,10 @@ void moduleRegisterCoreAPI(void) {
7561
7777
REGISTER_API (ScanCursorRestart );
7562
7778
REGISTER_API (Scan );
7563
7779
REGISTER_API (ScanKey );
7780
+ REGISTER_API (CreateModuleUser );
7781
+ REGISTER_API (SetModuleUserACL );
7782
+ REGISTER_API (FreeModuleUser );
7783
+ REGISTER_API (DeauthenticateAndCloseClient );
7784
+ REGISTER_API (AuthenticateClientWithACLUser );
7785
+ REGISTER_API (AuthenticateClientWithUser );
7564
7786
}
0 commit comments