Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ACL commands #2838

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
572 changes: 572 additions & 0 deletions src/StackExchange.Redis/APITypes/ACLRules.cs

Large diffs are not rendered by default.

462 changes: 462 additions & 0 deletions src/StackExchange.Redis/APITypes/ACLRulesBuilder.cs

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/StackExchange.Redis/Enums/RedisCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace StackExchange.Redis;
internal enum RedisCommand
{
NONE, // must be first for "zero reasons"

ACL,
APPEND,
ASKING,
AUTH,
Expand Down Expand Up @@ -358,6 +358,7 @@ internal static bool IsPrimaryOnly(this RedisCommand command)
return true;
// Commands that can be issued anywhere
case RedisCommand.NONE:
case RedisCommand.ACL:
case RedisCommand.ASKING:
case RedisCommand.AUTH:
case RedisCommand.BGREWRITEAOF:
Expand Down
148 changes: 148 additions & 0 deletions src/StackExchange.Redis/Interfaces/IServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,154 @@ public partial interface IServer : IRedis
/// </summary>
int DatabaseCount { get; }

/// <summary>
/// Gets the categories of access control commands.
/// </summary>
/// <param name="flags">The command flags to use.</param>
/// <returns>An array of Redis values representing the categories.</returns>
RedisValue[] AccessControlGetCategories(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Asynchronously gets the categories of access control commands.
/// </summary>
/// <param name="flags">The command flags to use.</param>
/// <returns>A task representing the asynchronous operation, with an array of Redis values representing the categories.</returns>
Task<RedisValue[]> AccessControlGetCategoriesAsync(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Gets the access control commands for a specified category.
/// </summary>
/// <param name="category">The category to get commands for.</param>
/// <param name="flags">The command flags to use.</param>
/// <returns>An array of Redis values representing the commands.</returns>
RedisValue[] AccessControlGetCommands(RedisValue category, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Asynchronously gets the access control commands for a specified category.
/// </summary>
/// <param name="category">The category to get commands for.</param>
/// <param name="flags">The command flags to use.</param>
/// <returns>A task representing the asynchronous operation, with an array of Redis values representing the commands.</returns>
Task<RedisValue[]> AccessControlGetCommandsAsync(RedisValue category, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Deletes specified access control users.
/// </summary>
/// <param name="usernames">The usernames of the users to delete.</param>
/// <param name="flags">The command flags to use.</param>
/// <returns>The number of users deleted.</returns>
long AccessControlDeleteUsers(RedisValue[] usernames, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Asynchronously deletes specified access control users.
/// </summary>
/// <param name="usernames">The usernames of the users to delete.</param>
/// <param name="flags">The command flags to use.</param>
/// <returns>A task representing the asynchronous operation, with the number of users deleted.</returns>
Task<long> AccessControlDeleteUsersAsync(RedisValue[] usernames, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Generates a password for access control.
/// </summary>
/// <param name="bits">The number of bits for the password.</param>
/// <param name="flags">The command flags to use.</param>
/// <returns>The generated password as a Redis value.</returns>
RedisValue AccessControlGeneratePassword(long bits, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Asynchronously generates a password for access control.
/// </summary>
/// <param name="bits">The number of bits for the password.</param>
/// <param name="flags">The command flags to use.</param>
/// <returns>A task representing the asynchronous operation, with the generated password as a Redis value.</returns>
Task<RedisValue> AccessControlGeneratePasswordAsync(long bits, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Loads access control rules.
/// </summary>
/// <param name="flags">The command flags to use.</param>
void AccessControlLoad(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Asynchronously loads access control rules.
/// </summary>
/// <param name="flags">The command flags to use.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task AccessControlLoadAsync(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Resets the access control log.
/// </summary>
/// <param name="flags">The command flags to use.</param>
void AccessControlLogReset(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Asynchronously resets the access control log.
/// </summary>
/// <param name="flags">The command flags to use.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task AccessControlLogResetAsync(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Gets the access control log.
/// </summary>
/// <param name="count">The number of log entries to retrieve.</param>
/// <param name="flags">The command flags to use.</param>
/// <returns>An array of key-value pairs representing the log entries.</returns>
KeyValuePair<string, RedisValue>[][] AccessControlLog(long count, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Asynchronously gets the access control log.
/// </summary>
/// <param name="count">The number of log entries to retrieve.</param>
/// <param name="flags">The command flags to use.</param>
/// <returns>A task representing the asynchronous operation, with an array of key-value pairs representing the log entries.</returns>
Task<KeyValuePair<string, RedisValue>[][]> AccessControlLogAsync(long count, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Saves access control rules.
/// </summary>
/// <param name="flags">The command flags to use.</param>
void AccessControlSave(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Asynchronously saves access control rules.
/// </summary>
/// <param name="flags">The command flags to use.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task AccessControlSaveAsync(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Gets the current access control user.
/// </summary>
/// <param name="flags">The command flags to use.</param>
/// <returns>The current access control user as a Redis value.</returns>
RedisValue AccessControlWhoAmI(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Asynchronously gets the current access control user.
/// </summary>
/// <param name="flags">The command flags to use.</param>
/// <returns>A task representing the asynchronous operation, with the current access control user as a Redis value.</returns>
Task<RedisValue> AccessControlWhoAmIAsync(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Sets access control rules for a user.
/// </summary>
/// <param name="userName">The username to set rules for.</param>
/// <param name="rules">The access control rules to set.</param>
/// <param name="flags">The command flags to use.</param>
void AccessControlSetUser(RedisValue userName, ACLRules rules, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Asynchronously sets access control rules for a user.
/// </summary>
/// <param name="userName">The username to set rules for.</param>
/// <param name="rules">The access control rules to set.</param>
/// <param name="flags">The command flags to use.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task AccessControlSetUserAsync(RedisValue userName, ACLRules rules, CommandFlags flags = CommandFlags.None);

/// <summary>
/// The <c>CLIENT KILL</c> command closes a given client connection identified by <c>ip:port</c>.
/// The <c>ip:port</c> should match a line returned by the <c>CLIENT LIST</c> command.
Expand Down
79 changes: 79 additions & 0 deletions src/StackExchange.Redis/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ public bool IsAdmin
{
switch (Command)
{
case RedisCommand.ACL:
case RedisCommand.BGREWRITEAOF:
case RedisCommand.BGSAVE:
case RedisCommand.CLIENT:
Expand Down Expand Up @@ -525,6 +526,26 @@ internal static Message Create(int db, CommandFlags flags, RedisCommand command,
return new CommandKeyValuesKeyMessage(db, flags, command, key0, values, key1);
}

internal static Message Create(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisValue[] values)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(values);
#else
if (values == null) throw new ArgumentNullException(nameof(values));
#endif
return new CommandValueValuesMessage(db, flags, command, value, values);
}

internal static Message Create(int db, CommandFlags flags, RedisCommand command, RedisValue value0, RedisValue value1, RedisValue[] values)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(values);
#else
if (values == null) throw new ArgumentNullException(nameof(values));
#endif
return new CommandValueValueValuesMessage(db, flags, command, value0, value1, values);
}

internal static CommandFlags GetPrimaryReplicaFlags(CommandFlags flags)
{
// for the purposes of the switch, we only care about two bits
Expand All @@ -535,6 +556,7 @@ internal static bool RequiresDatabase(RedisCommand command)
{
switch (command)
{
case RedisCommand.ACL:
case RedisCommand.ASKING:
case RedisCommand.AUTH:
case RedisCommand.BGREWRITEAOF:
Expand Down Expand Up @@ -1048,6 +1070,63 @@ protected override void WriteImpl(PhysicalConnection physical)
public override int ArgCount => values.Length;
}

private sealed class CommandValueValuesMessage : Message
{
private readonly RedisValue value;
private readonly RedisValue[] values;

public CommandValueValuesMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisValue[] values) : base(db, flags, command)
{
this.value = value;
for (int i = 0; i < values.Length; i++)
{
values[i].AssertNotNull();
}
this.values = values;
}

protected override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(command, values.Length + 1);
physical.WriteBulkString(value);
for (int i = 0; i < values.Length; i++)
{
physical.WriteBulkString(values[i]);
}
}
public override int ArgCount => values.Length + 1;
}

private sealed class CommandValueValueValuesMessage : Message
{
private readonly RedisValue value0;
private readonly RedisValue value1;
private readonly RedisValue[] values;

public CommandValueValueValuesMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value0, RedisValue value1, RedisValue[] values) : base(db, flags, command)
{
this.value0 = value0;
this.value1 = value1;
for (int i = 0; i < values.Length; i++)
{
values[i].AssertNotNull();
}
this.values = values;
}

protected override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(command, values.Length + 2);
physical.WriteBulkString(value0);
physical.WriteBulkString(value1);
for (int i = 0; i < values.Length; i++)
{
physical.WriteBulkString(values[i]);
}
}
public override int ArgCount => values.Length + 2;
}

private sealed class CommandKeysMessage : Message
{
private readonly RedisKey[] keys;
Expand Down
Loading
Loading