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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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.

119 changes: 119 additions & 0 deletions src/StackExchange.Redis/APITypes/ACLUser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System.Collections.Generic;

namespace StackExchange.Redis;

/// <summary>
/// Represents an Access Control List (ACL) user with various properties such as flags, passwords, commands, keys, channels, and selectors.
/// </summary>
public class ACLUser
{
/// <summary>
/// A dictionary containing user information.
/// </summary>
public readonly Dictionary<string, object>? UserInfo;

/// <summary>
/// An array of flags associated with the user.
/// </summary>
public readonly string[]? Flags;

/// <summary>
/// An array of passwords associated with the user.
/// </summary>
public readonly string[]? Passwords;

/// <summary>
/// A string representing the commands associated with the user.
/// </summary>
public readonly string? Commands;

/// <summary>
/// A string representing the keys associated with the user.
/// </summary>
public readonly string? Keys;

/// <summary>
/// A string representing the channels associated with the user.
/// </summary>
public readonly string? Channels;

/// <summary>
/// An array of selectors associated with the user.
/// </summary>
public readonly ACLSelector[]? Selectors;

/// <summary>
/// Initializes a new instance of the <see cref="ACLUser"/> class with specified parameters.
/// </summary>
/// <param name="userInfo">A dictionary containing user information.</param>
/// <param name="flags">An array oflags associated with the user.</param>
/// <param name="passwords">An array opasswords associated with the user.</param>
/// <param name="commands">A string representing the commands associated with the user.</param>
/// <param name="keys">A string representing the keys associated with the user.</param>
/// <param name="channels">A string representing the channels associated with the user.</param>
/// <param name="selectors">An array oselectors associated with the user.</param>
public ACLUser(Dictionary<string, object>? userInfo, string[]? flags, string[]? passwords, string? commands, string? keys, string? channels, ACLSelector[]? selectors)
{
UserInfo = userInfo;
Flags = flags;
Passwords = passwords;
Commands = commands;
Keys = keys;
Channels = channels;
Selectors = selectors;
}

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return "AccessControlUser{" + "Flags=" + Flags + ", Passwords=" + Passwords
+ ", Commands='" + Commands + "', Keys='" + Keys + "', Channels='" + Channels
+ "', Selectors=" + Selectors + "}";
}
}

/// <summary>
/// Represents an Access Control List (ACL) selector for a Redis user.
/// </summary>
public class ACLSelector
{
/// <summary>
/// Gets the commands associated with the ACL user.
/// </summary>
public readonly string? Commands;

/// <summary>
/// Gets the keys associated with the ACL user.
/// </summary>
public readonly string? Keys;

/// <summary>
/// Gets the channels associated with the ACL user.
/// </summary>
public readonly string? Channels;

/// <summary>
/// Initializes a new instance of the <see cref="ACLSelector"/> class.
/// </summary>
/// <param name="commands">The commands associated with the ACLSelector.</param>
/// <param name="keys">The keys associated with the ACLSelector.</param>
/// <param name="channels">The channels associated with the ACLSelector.</param>
public ACLSelector(string? commands, string? keys, string? channels)
{
Commands = commands;
Keys = keys;
Channels = channels;
}

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return "ACLSelector{" + "Commands='" + Commands + "', Keys='" + Keys + "', Channels='" + Channels + "'}";
}
}
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
15 changes: 15 additions & 0 deletions src/StackExchange.Redis/ExtensionMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,5 +342,20 @@ internal static int VectorSafeIndexOfCRLF(this ReadOnlySpan<byte> span)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static TTo[]? ToArray<TTo, TState>(in this RawResult result, Projection<RawResult, TState, TTo> selector, in TState state)
=> result.IsNull ? null : result.GetItems().ToArray(selector, in state);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static List<T>? ToList<T>(this RawResult result, Func<RawResult, T> selector)
{
List<T>? list = null;
if (!result.IsNull)
{
list = new List<T>();
foreach (var item in result.GetItems())
{
list.Add(selector(item));
}
}
return list;
}
}
}
178 changes: 178 additions & 0 deletions src/StackExchange.Redis/Interfaces/IServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,184 @@ 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>
/// Gets the access control user for a specified username.
/// </summary>
/// <param name="username">The username to get the access control user for.</param>
/// <param name="flags">The command flags to use.</param>
/// <returns>The access control user associated with the specified username, or null if not found.</returns>
ACLUser? AccessControlGetUser(RedisValue username, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Gets the access control user for a specified username.
/// </summary>
/// <param name="username">The username to get the access control user for.</param>
/// <param name="flags">The command flags to use.</param>
/// <returns>A task representing the asynchronous operation, with the access control user associated with the specified username, or null if not found.</returns>
Task<ACLUser?> AccessControlGetUserAsync(RedisValue username, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Lists all access control rules.
/// </summary>
/// <param name="flags">The command flags to use.</param>
/// <returns>An array of Redis values representing the access control rules.</returns>
RedisValue[]? AccessControlList(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Asynchronously lists all access control rules.
/// </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 access control rules.</returns>
Task<RedisValue[]?> AccessControlListAsync(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
Loading
Loading