-
Notifications
You must be signed in to change notification settings - Fork 14
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
feat(pedm): netmgmt wrapper to add/remove local users from local groups #1099
Conversation
Let maintainers know that an action is required on their side
|
5553219
to
dc724c7
Compare
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.
The code is absolutely looking good to me. Unsafe code is also properly documented 💯
Since you asked for pedantry, let me be pedantic 😉
I'll approve now so you can merge when you see fit!
crates/devolutions-pedm/src/utils.rs
Outdated
let mut is_member = is_token_member_of_administrators_group_directly(user_token)?; | ||
|
||
if !is_member { | ||
let local_admin_group_sid = Sid::from_well_known( | ||
win_api_wrappers::raw::Win32::Security::WinBuiltinAdministratorsSid, | ||
None, | ||
)?; | ||
let local_admin_group_account = local_admin_group_sid.account(None)?; | ||
let local_admin_sids = get_local_group_members(local_admin_group_account.account_name)?; | ||
let group_sids_and_attributes = user_token.groups()?.0; | ||
|
||
for user_group_sid_and_attributes in group_sids_and_attributes { | ||
for admin_sid in &local_admin_sids { | ||
is_member |= admin_sid == &user_group_sid_and_attributes.sid; | ||
|
||
if is_member { | ||
break; | ||
} | ||
} | ||
|
||
if is_member { | ||
break; | ||
} | ||
} | ||
} | ||
|
||
Ok(is_member) |
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.
nitpick: I think the code can be made more straightforward and shorter, but there is also personal taste in the mix.
- Early exit instead of maintaining a state that we modify along the way.
- Less indentation.
- Extra if block removed.
if is_token_member_of_administrators_group_directly(user_token)? {
return Ok(true);
}
let local_admin_group_sid = Sid::from_well_known(
win_api_wrappers::raw::Win32::Security::WinBuiltinAdministratorsSid,
None,
)?;
let local_admin_group_account = local_admin_group_sid.account(None)?;
let local_admin_sids = get_local_group_members(local_admin_group_account.account_name)?;
let group_sids_and_attributes = user_token.groups()?.0;
for user_group_sid_and_attributes in group_sids_and_attributes {
for admin_sid in &local_admin_sids {
if admin_sid == &user_group_sid_and_attributes.sid {
return Ok(true);
}
}
}
Ok(false)
The second if is_member
block seemed not useful to me, as we are already checking just after is_member
is modified.
Going further, using iterators you can also turn this
for user_group_sid_and_attributes in group_sids_and_attributes {
for admin_sid in &local_admin_sids {
if admin_sid == &user_group_sid_and_attributes.sid {
return Ok(true);
}
}
}
Ok(false)
into this
group_sids_and_attributes
.flat_map(|local_admin_sids| local_admin_sids.iter())
.any(|admin_sid| admin_sid == &user_group_sid_and_attributes.sid)
Resulting into this code:
if is_token_member_of_administrators_group_directly(user_token)? {
return Ok(true);
}
let local_admin_group_sid = Sid::from_well_known(
win_api_wrappers::raw::Win32::Security::WinBuiltinAdministratorsSid,
None,
)?;
let local_admin_group_account = local_admin_group_sid.account(None)?;
let local_admin_sids = get_local_group_members(local_admin_group_account.account_name)?;
let group_sids_and_attributes = user_token.groups()?.0;
let is_admin = group_sids_and_attributes
.flat_map(|local_admin_sids| local_admin_sids.iter())
.any(|admin_sid| admin_sid == &user_group_sid_and_attributes.sid)
Ok(is_admin)
Which is in my opinion easier to read. Feel free to adjust to your preferences.
(note: Code which does not use iterators may sometimes be easier to read, but as you know there is no easy metric for that 😂 )
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.
I did revise this per your recommendations, but I left the iterative solution at the end. In this instance where I think it's purely a stylistic thing, I still find that easier to parse than the declarative approach. I expect my mindset to shift here (in C# I generally prefer the declarative approach, but my comfort levels are different there)
// WideString holds a null-terminated UTF-16 string, and as_pwstr() returns a valid pointer to it. | ||
let rc = | ||
unsafe { NetLocalGroupAddMembers(None, group_name.as_pcwstr(), 0, &group_info as *const _ as *const u8, 1) }; |
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.
suggestion: Use the .cast()
method for pointer-to-pointer casts (rust-lang/rust-clippy#8017 / https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr)
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.
Left this as-is for now, because I'm not sure I know the best way to follow your suggestion.
Is it the case that I need to break out an intermediate variable?
i.e.
let group_name_ptr = &group_info as *const LOCALGROUP_MEMBERS_INFO_0;
and then use cast()
at the final call site, i.e.
unsafe { NetLocalGroupDelMembers(None, group_name.as_pcwstr(), 0, group_name_ptr.cast(), 1) };
But probably I misunderstand
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.
You can wrap with parenthesis:
(&group_info as *const _).cast::<u8>()
But splitting into several lines may be more readable in some cases, especially if we spell out the type like in your example.
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.
Thanks!
|
||
let group_name = WideString::from(group_name); | ||
|
||
let user_sid = RawSid::try_from(security_identifier)?; |
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.
note: Since you use anyhow::Result
, you can attach a context string to the error.
For instance:
let user_sid = RawSid::try_from(security_identifier)?; | |
let user_sid = RawSid::try_from(security_identifier).context("failed to convert security identifier into a RawSid")?; |
You need to have the anyhow::Context
trait in scope:
use anyhow::Context as _;
This is just an example, it's not necessary to have context for everything either. Depending on the situation you may want to not use anyhow::Result
at all.
dc724c7
to
7e902de
Compare
7e902de
to
e7e6676
Compare
For a "MakeMeAdmin" style EDM mode, we require the ability to check if the user is a local administrator, and add or remove them from the local Administrators group.
Add a WinAPI wrapper over the network management functions
NetLocalGroupAddMembers
,NetLocalGroupGetMembers
, andNetLocalGroupGetMembers
. This allows us to query the membership of local groups and add or remove users.I added functions in
devolutions_pedm::utils
to demonstrate usage, although that will not be final "home" for this logic. We can query if a user is directly a member of the local administrators (in which case, we may add or remove them) or if they are a member of local administrators by virtue of a nested group.Changelog: ignore