-
Notifications
You must be signed in to change notification settings - Fork 553
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 ACL GETUSER Command Initial Implementation #959
base: main
Are you sure you want to change the base?
Changes from all commits
71fb785
e8d2fdf
db15410
97d6b5d
339f50b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -124,7 +124,7 @@ public void AddCategory(RespAclCategories category) | |
|
||
/// <summary> | ||
/// Adds the given command to the user. | ||
/// | ||
/// | ||
/// If the command has subcommands, and no specific subcommand is indicated, adds all subcommands too. | ||
/// </summary> | ||
/// <param name="command">Command to add.</param> | ||
|
@@ -246,7 +246,7 @@ public void RemoveCategory(RespAclCategories category) | |
|
||
/// <summary> | ||
/// Removes the given command from the user. | ||
/// | ||
/// | ||
/// If the command has subcommands, and no specific subcommand is indicated, removes all subcommands too. | ||
/// </summary> | ||
/// <param name="command">Command to remove.</param> | ||
|
@@ -420,6 +420,33 @@ public string DescribeUser() | |
return stringBuilder.ToString(); | ||
} | ||
|
||
/// <summary> | ||
/// Returns flags for the <see cref="User"/>. | ||
/// </summary> | ||
/// <returns>A <see cref="List{T}"/> of <see cref="string"/> representing flags for the user.</returns> | ||
public List<string> GetFlags() | ||
{ | ||
return new() { IsEnabled ? "on" : "off" }; | ||
} | ||
|
||
/// <summary> | ||
/// Returns password hashes for the <see cref="User"/>. | ||
/// </summary> | ||
/// <returns>A <see cref="List{T}"/> of <see cref="string"/> representing password hashes for the user.</returns> | ||
public List<string> GetPasswordHashes() | ||
{ | ||
return _passwordHashes.Select(hash => $"#{hash}").ToList(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not thread safe, _passwordHashes is mutable. Also, there's no need to allocate here, we're just prepending a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you onboard with making this field accessible to clients to avoid the allocations? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given the thread safety concerns, I don't think exposing the field is appropriate. |
||
} | ||
|
||
/// <summary> | ||
/// Returns a <see cref="string"/> containing the enabled commands. | ||
/// </summary> | ||
/// <returns>A <see cref="string"/> containing the enabled commands.</returns> | ||
public string GetEnabledCommandsDescription() | ||
{ | ||
return _enabledCommands.Description; | ||
} | ||
|
||
/// <summary> | ||
/// Determine the command / sub command pairs that are associated with this command information entries | ||
/// </summary> | ||
|
@@ -448,7 +475,7 @@ internal static IEnumerable<RespCommand> DetermineCommandDetails(IReadOnlyList<R | |
|
||
/// <summary> | ||
/// Check to see if any tokens from a description can be removed without modifying the effective permissions. | ||
/// | ||
/// | ||
/// This is an expensive method, but ACL modifications are rare enough it's hopefully not a problem. | ||
/// </summary> | ||
private static string RationalizeACLDescription(CommandPermissionSet set, string description) | ||
|
@@ -492,7 +519,7 @@ internal CommandPermissionSet CopyCommandPermissionSet() | |
|
||
/// <summary> | ||
/// A set of all allowed _passwordHashes for the user. | ||
/// | ||
/// | ||
/// NOTE: HashSet is not thread-safe, so accesses need to be synchronized | ||
/// </summary> | ||
readonly HashSet<ACLPassword> _passwordHashes = []; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -366,5 +366,90 @@ private bool NetworkAclSave() | |
|
||
return true; | ||
} | ||
|
||
/// <summary> | ||
/// Processes ACL GETUSER subcommand. | ||
/// </summary> | ||
/// <returns>true if parsing succeeded correctly, false if not all tokens could be consumed and further processing is necessary.</returns> | ||
private bool NetworkAclGetUser() | ||
{ | ||
// Have to have at least the username | ||
if (parseState.Count != 1) | ||
{ | ||
while (!RespWriteUtils.TryWriteError($"ERR Unknown subcommand or wrong number of arguments for ACL GETUSER.", ref dcurr, dend)) | ||
SendAndReset(); | ||
} | ||
else | ||
{ | ||
if (!ValidateACLAuthenticator()) | ||
return true; | ||
|
||
var aclAuthenticator = (GarnetACLAuthenticator)_authenticator; | ||
User user = null; | ||
|
||
try | ||
{ | ||
user = aclAuthenticator | ||
.GetAccessControlList() | ||
.GetUser(parseState.GetString(0)); | ||
} | ||
catch (ACLException exception) | ||
{ | ||
logger?.LogDebug("ACLException: {message}", exception.Message); | ||
|
||
// Abort command execution | ||
while (!RespWriteUtils.TryWriteError($"ERR {exception.Message}", ref dcurr, dend)) | ||
SendAndReset(); | ||
|
||
return true; | ||
} | ||
|
||
if (user is null) | ||
{ | ||
while (!RespWriteUtils.TryWriteNull(ref dcurr, dend)) | ||
SendAndReset(); | ||
} | ||
else | ||
{ | ||
while (!RespWriteUtils.TryWriteArrayLength(6, ref dcurr, dend)) | ||
SendAndReset(); | ||
|
||
var flags = user.GetFlags(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think broadly this will run into issues if the user is modified while the command is running. GetFlags(), GetPasswordHashes(), and GetEnabledCommandsDescription() won't necessarily return consistent results. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think some variant of "one method (that internally takes appropriate) locks, and copies the relevant data into the return"-is the only path forward. I'd be tempted to try and fit things into Span's created by the caller to avoid allocations (or rent some arrays, or something), so the there's some flexibility in the implementation. |
||
var passwordHashes = user.GetPasswordHashes(); | ||
|
||
while (!RespWriteUtils.TryWriteAsciiBulkString("flags", ref dcurr, dend)) | ||
SendAndReset(); | ||
|
||
while (!RespWriteUtils.TryWriteArrayLength(flags.Count, ref dcurr, dend)) | ||
SendAndReset(); | ||
|
||
foreach (var flag in flags) | ||
{ | ||
while (!RespWriteUtils.TryWriteAsciiBulkString(flag, ref dcurr, dend)) | ||
SendAndReset(); | ||
} | ||
|
||
while (!RespWriteUtils.TryWriteAsciiBulkString("passwords", ref dcurr, dend)) | ||
SendAndReset(); | ||
|
||
while (!RespWriteUtils.TryWriteArrayLength(passwordHashes.Count, ref dcurr, dend)) | ||
SendAndReset(); | ||
|
||
foreach (var passwordHash in passwordHashes) | ||
{ | ||
while (!RespWriteUtils.TryWriteAsciiBulkString(passwordHash, ref dcurr, dend)) | ||
SendAndReset(); | ||
} | ||
|
||
while (!RespWriteUtils.TryWriteAsciiBulkString("commands", ref dcurr, dend)) | ||
SendAndReset(); | ||
|
||
while (!RespWriteUtils.TryWriteAsciiBulkString(user.GetEnabledCommandsDescription(), ref dcurr, dend)) | ||
SendAndReset(); | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why allocate here? It's just switching between two constants.