Skip to content

Commit b2655bb

Browse files
committed
add support for ACL LIST
1 parent d95cf2e commit b2655bb

File tree

5 files changed

+118
-27
lines changed

5 files changed

+118
-27
lines changed

src/StackExchange.Redis/APITypes/ACLUser.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public class ACLSelector
8383
/// <summary>
8484
/// Gets the commands associated with the ACL user.
8585
/// </summary>
86-
public readonly string? Commmands;
86+
public readonly string? Commands;
8787

8888
/// <summary>
8989
/// Gets the keys associated with the ACL user.
@@ -103,7 +103,7 @@ public class ACLSelector
103103
/// <param name="channels">The channels associated with the ACLSelector.</param>
104104
public ACLSelector(string? commands, string? keys, string? channels)
105105
{
106-
Commmands = commands;
106+
Commands = commands;
107107
Keys = keys;
108108
Channels = channels;
109109
}
@@ -114,6 +114,6 @@ public ACLSelector(string? commands, string? keys, string? channels)
114114
/// <returns>A string that represents the current object.</returns>
115115
public override string ToString()
116116
{
117-
return "ACLSelector{" + "Commmands='" + Commmands + "', Keys='" + Keys + "', Channels='" + Channels + "'}";
117+
return "ACLSelector{" + "Commands='" + Commands + "', Keys='" + Keys + "', Channels='" + Channels + "'}";
118118
}
119119
}

src/StackExchange.Redis/Interfaces/IServer.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,20 @@ public partial interface IServer : IRedis
155155
/// <returns>A task representing the asynchronous operation, with the access control user associated with the specified username, or null if not found.</returns>
156156
Task<ACLUser?> AccessControlGetUserAsync(RedisValue username, CommandFlags flags = CommandFlags.None);
157157

158+
/// <summary>
159+
/// Lists all access control rules.
160+
/// </summary>
161+
/// <param name="flags">The command flags to use.</param>
162+
/// <returns>An array of Redis values representing the access control rules.</returns>
163+
RedisValue[]? AccessControlList(CommandFlags flags = CommandFlags.None);
164+
165+
/// <summary>
166+
/// Asynchronously lists all access control rules.
167+
/// </summary>
168+
/// <param name="flags">The command flags to use.</param>
169+
/// <returns>A task representing the asynchronous operation, with an array of Redis values representing the access control rules.</returns>
170+
Task<RedisValue[]?> AccessControlListAsync(CommandFlags flags = CommandFlags.None);
171+
158172
/// <summary>
159173
/// Loads access control rules.
160174
/// </summary>

src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ readonly StackExchange.Redis.ACLSelectorRules.KeysAllowedPatterns -> string![]?
7979
readonly StackExchange.Redis.ACLSelectorRules.KeysAllowedReadForPatterns -> string![]?
8080
readonly StackExchange.Redis.ACLSelectorRules.KeysAllowedWriteForPatterns -> string![]?
8181
readonly StackExchange.Redis.ACLSelector.Channels -> string?
82-
readonly StackExchange.Redis.ACLSelector.Commmands -> string?
82+
readonly StackExchange.Redis.ACLSelector.Commands -> string?
8383
readonly StackExchange.Redis.ACLSelector.Keys -> string?
8484
readonly StackExchange.Redis.ACLUser.Channels -> string?
8585
readonly StackExchange.Redis.ACLUser.Commands -> string?
@@ -1157,6 +1157,8 @@ StackExchange.Redis.IServer.AccessControlDeleteUsers(StackExchange.Redis.RedisVa
11571157
StackExchange.Redis.IServer.AccessControlDeleteUsersAsync(StackExchange.Redis.RedisValue[]! usernames, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
11581158
StackExchange.Redis.IServer.AccessControlGeneratePassword(long bits, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
11591159
StackExchange.Redis.IServer.AccessControlGeneratePasswordAsync(long bits, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue>!
1160+
StackExchange.Redis.IServer.AccessControlList(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]?
1161+
StackExchange.Redis.IServer.AccessControlListAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue[]?>!
11601162
StackExchange.Redis.IServer.AccessControlLoad(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void
11611163
StackExchange.Redis.IServer.AccessControlLoadAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
11621164
StackExchange.Redis.IServer.AccessControlLogReset(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void

src/StackExchange.Redis/RedisServer.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,18 @@ public Task<RedisValue> AccessControlGeneratePasswordAsync(long bits, CommandFla
111111
return ExecuteAsync(msg, ResultProcessor.ACLUser);
112112
}
113113

114+
public RedisValue[]? AccessControlList(CommandFlags flags = CommandFlags.None)
115+
{
116+
var msg = Message.Create(-1, flags, RedisCommand.ACL, RedisLiterals.LIST);
117+
return ExecuteSync(msg, ResultProcessor.RedisValueArray);
118+
}
119+
120+
public Task<RedisValue[]?> AccessControlListAsync(CommandFlags flags = CommandFlags.None)
121+
{
122+
var msg = Message.Create(-1, flags, RedisCommand.ACL, RedisLiterals.LIST);
123+
return ExecuteAsync(msg, ResultProcessor.RedisValueArray);
124+
}
125+
114126
public void AccessControlLoad(CommandFlags flags = CommandFlags.None)
115127
{
116128
var msg = Message.Create(-1, flags, RedisCommand.ACL, RedisLiterals.LOAD);

tests/StackExchange.Redis.Tests/ACLIntegrationTests.cs

Lines changed: 86 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class ACLIntegrationTests : TestBase
1313

1414
public ACLIntegrationTests(ITestOutputHelper output, SharedConnectionFixture fixture) : base(output, fixture)
1515
{
16-
_conn = Create();
16+
_conn = Create(require: RedisFeatures.v7_4_0_rc1);
1717
_redisServer = GetAnyPrimary(_conn);
1818
}
1919

@@ -47,8 +47,8 @@ public void AccessControlGetUser_ShouldReturnUserDetails()
4747
{
4848
Action<ACLSelectorRulesBuilder> act = rules => rules.CommandsAllowed("GET", "SET");
4949
// Arrange
50-
var username = new RedisValue("testuser");
51-
_redisServer.AccessControlSetUser("testuser", new ACLRulesBuilder()
50+
var userName = new RedisValue(Me());
51+
_redisServer.AccessControlSetUser(userName, new ACLRulesBuilder()
5252
.AppendACLSelectorRules(rules => rules.CommandsAllowed("GET", "SET"))
5353
.AppendACLSelectorRules(rules => rules.KeysAllowedReadForPatterns("key*"))
5454
.WithACLUserRules(rules => rules.PasswordsToSet("psw1", "psw2"))
@@ -58,7 +58,7 @@ public void AccessControlGetUser_ShouldReturnUserDetails()
5858
.Build());
5959

6060
// Act
61-
var user = _redisServer.AccessControlGetUser(username);
61+
var user = _redisServer.AccessControlGetUser(userName);
6262

6363
// Assert
6464
Assert.NotNull(user);
@@ -72,7 +72,7 @@ public void AccessControlGetUser_ShouldReturnUserDetails()
7272
public void AccessControlGetUser_ShouldReturnNullForNonExistentUser()
7373
{
7474
// Act
75-
var user = _redisServer.AccessControlGetUser("nonexistentuser");
75+
var user = _redisServer.AccessControlGetUser(Me());
7676

7777
// Assert
7878
Assert.Null(user);
@@ -82,10 +82,11 @@ public void AccessControlGetUser_ShouldReturnNullForNonExistentUser()
8282
public void AccessControlDeleteUsers_ShouldReturnCorrectCount()
8383
{
8484
// Arrange
85-
_redisServer.AccessControlSetUser(new RedisValue("user1"), new ACLRulesBuilder().Build());
85+
string userName = Me();
86+
_redisServer.AccessControlSetUser(new RedisValue(userName), new ACLRulesBuilder().Build());
8687

8788
// Act
88-
var count = _redisServer.AccessControlDeleteUsers(new RedisValue[] { "user1", "user2" });
89+
var count = _redisServer.AccessControlDeleteUsers(new RedisValue[] { userName, "user2" });
8990

9091
// Assert
9192
Assert.Equal(1, count);
@@ -116,14 +117,15 @@ public void AccessControlLogReset_ShouldExecuteSuccessfully()
116117
public void AccessControlLog_ShouldReturnLogs()
117118
{
118119
// Arrange
120+
string userName = Me();
119121
_redisServer.AccessControlSetUser(
120-
"user1",
122+
userName,
121123
new ACLRulesBuilder()
122124
.WithACLUserRules(rules => rules.PasswordsToSet(new[] { "pass1" })
123125
.UserState(ACLUserState.ON))
124126
.Build());
125127

126-
Assert.Throws<RedisServerException>(() => _conn.GetDatabase().Execute("AUTH", "user1", "pass2"));
128+
Assert.Throws<RedisServerException>(() => _conn.GetDatabase().Execute("AUTH", userName, "pass2"));
127129

128130
// Act
129131
var logs = _redisServer.AccessControlLog(10);
@@ -137,17 +139,6 @@ public void AccessControlLog_ShouldReturnLogs()
137139
[Fact]
138140
public void AccessControlWhoAmI_ShouldReturnCurrentUser()
139141
{
140-
// // Arrange
141-
// var conn = Create(require: RedisFeatures.v7_0_0_rc1);
142-
// var redisServer = (RedisServer)GetAnyPrimary(conn);
143-
144-
// redisServer.AccessControlSetUser(
145-
// "user1",
146-
// new ACLRulesBuilder()
147-
// .WithACLUserRules(rules => rules.PasswordsToSet(new[] { "pass1" })
148-
// .UserState(UserState.ON))
149-
// .Build());
150-
151142
// Act
152143
var user = _redisServer.AccessControlWhoAmI();
153144

@@ -156,14 +147,86 @@ public void AccessControlWhoAmI_ShouldReturnCurrentUser()
156147
Assert.True(user.ToString().Length > 0); // Ensure there's a valid user returned
157148
}
158149

150+
[Fact]
151+
public void AccessControlList_ShouldReturnAllUsers()
152+
{
153+
// Arrange
154+
var userName1 = new RedisValue(Me() + "1");
155+
var userName2 = new RedisValue(Me() + "2");
156+
_redisServer.AccessControlSetUser(userName1, new ACLRulesBuilder().Build());
157+
_redisServer.AccessControlSetUser(userName2, new ACLRulesBuilder().Build());
158+
159+
// Act
160+
var users = _redisServer.AccessControlList();
161+
162+
// Assert
163+
Assert.NotNull(users);
164+
Assert.Contains(users, user => user.ToString().Contains(userName1!));
165+
Assert.Contains(users, user => user.ToString().Contains(userName2!));
166+
}
167+
159168
[Fact]
160169
public void AccessControlSetUser_ShouldSetUserWithGivenRules()
161170
{
171+
string userName = Me();
172+
173+
// Act
174+
_redisServer.AccessControlSetUser(new RedisValue(userName), new ACLRules(null, null, null));
175+
176+
// Assert
177+
var users = _redisServer.AccessControlList();
178+
Assert.NotNull(users);
179+
Assert.Contains(users!, user => user.ToString().Contains(userName));
180+
}
181+
182+
[Fact]
183+
public void AccessControlSetUser_ShouldSetUserWithMultipleRules()
184+
{
185+
// Arrange
186+
var userName = new RedisValue(Me());
187+
var rules = new ACLRulesBuilder()
188+
.AppendACLSelectorRules(r => r.CommandsAllowed("HMGET", "HMSET").KeysAllowedReadForPatterns("key*"))
189+
.WithACLUserRules(r => r.PasswordsToSet("password1", "password2"))
190+
.WithACLCommandRules(r => r.CommandsAllowed("HGET", "HSET")
191+
.KeysAllowedPatterns("key1", "key*")
192+
.PubSubAllowChannels("chan1", "chan*"))
193+
.Build();
194+
195+
// Act
196+
_redisServer.AccessControlSetUser(userName, rules);
197+
198+
// Assert
199+
var user = _redisServer.AccessControlGetUser(userName);
200+
Assert.NotNull(user);
201+
Assert.Contains(user.Selectors!, s => s.Commands!.Contains("hmget") && s.Commands!.Contains("hmset"));
202+
Assert.Contains(user.Selectors!, s => s.Keys!.Contains("key*"));
203+
}
204+
205+
[Fact]
206+
public void AccessControlSetUser_ShouldUpdateExistingUser()
207+
{
208+
// Arrange
209+
var userName = new RedisValue(Me());
210+
var hashedPassword1 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
211+
var hashedPassword2 = "1123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
212+
var updatedPassword = "2123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
213+
var initialRules = new ACLRulesBuilder()
214+
.WithACLUserRules(r => r.HashedPasswordsToSet(hashedPassword1, hashedPassword2))
215+
.Build();
216+
_redisServer.AccessControlSetUser(userName, initialRules);
217+
218+
var updatedRules = new ACLRulesBuilder()
219+
.WithACLUserRules(r => r.HashedPasswordsToSet(updatedPassword).HashedPasswordsToRemove(hashedPassword1))
220+
.Build();
221+
162222
// Act
163-
_redisServer.AccessControlSetUser(new RedisValue("testuser"), new ACLRules(null, null, null));
223+
_redisServer.AccessControlSetUser(userName, updatedRules);
164224

165225
// Assert
166-
// In this case, we're asserting that no exceptions are thrown and the user is successfully set.
167-
// To validate this, you might want to verify if the user exists in your Redis instance or use a similar check.
226+
var user = _redisServer.AccessControlGetUser(userName);
227+
Assert.NotNull(user);
228+
Assert.DoesNotContain(hashedPassword1, user.Passwords!);
229+
Assert.Contains(hashedPassword2, user.Passwords!);
230+
Assert.Contains(updatedPassword, user.Passwords!);
168231
}
169232
}

0 commit comments

Comments
 (0)