如果有人像我一样来到这里寻找如何使用 .NET Core 3.1 执行此操作,这是我想出的解决方案,用于在 AD 中的 UserAccountControl 属性上获取和设置 PasswordCannotChange 位。
我使用System.DirectoryServices.Protocols 库来提供对LdapConnection 类以及相关类和方法的访问。我还使用System.Security.AccessControl 库来处理安全描述符。
假设您可以成功连接到 AD 服务器以创建 LdapConnection 类,其余的应该可以工作。
这是我对get的解决方案:
public bool GetUserCannotChangePassword(string userDistinguishedName){
using (var ldapConnection = CreateLdapConnection()) //Assuming you've connected using Admin rights
{
bool cantChange = false;
//Get RootDomainNamingContext as searchContainer
var r1 = (SearchResponse)ldapConnection.SendRequest(new SearchRequest("", "(objectClass=*)", SearchScope.Base));
var searchContainer = response.Entries[0].Attributes["rootdomainnamingcontext"].GetValues(typeof(string))[0]
.ToString();
//Set Filter to get specified user
var filter = $"(&(objectClass=user)(!(objectClass=computer))(distinguishedName={userDistinguishedName}))";
//Get the ntSecurityDescriptor attribute of the user
var searchRequest = new SearchRequest(searchContainer, filter, SearchScope.Subtree, new[] { "ntSecurityDescriptor" });
var searchOptions = new SearchOptionsControl(SearchOption.DomainScope);
searchRequest.Controls.Add(searchOptions);
var searchResponse = (SearchResponse)ldapConnection.SendRequest(searchRequest);
var result = searchResponse.Entries.OfType<SearchResultEntry>()
.SingleOrDefault();
if (result != null)
{
//Parse as RawSecurityDescriptor
RawSecurityDescriptor sd =
new RawSecurityDescriptor((byte[]) result.Attributes["ntSecurityDescriptor"][0], 0);
var oACL = sd.DiscretionaryAcl;
bool everyoneCantChange = false;
bool selfCantChange = false;
//Loop through the Access Control Entries that are of ObjectAce type
foreach (var ace in oACL.OfType<ObjectAce>())
{
if (ace?.ObjectAceType.ToString().Equals("AB721A53-1E2F-11D0-9819-00AA0040529B",
StringComparison.OrdinalIgnoreCase) == true) //Match on change password ACE (https://docs.microsoft.com/en-us/windows/win32/adsi/modifying-user-cannot-change-password-ldap-provider)
{
if (ace.SecurityIdentifier.Value.Equals("S-1-1-0", StringComparison.OrdinalIgnoreCase) &&
ace.AceType == AceType.AccessDeniedObject) //Match on Everyone SecurityIdentifier
{
everyoneCantChange = true;
}
if (ace.SecurityIdentifier.Value.Equals("S-1-5-10", StringComparison.OrdinalIgnoreCase) &&
ace.AceType == AceType.AccessDeniedObject) //Match on Self SecurityIdentifier
{
selfCantChange = true;
}
}
}
if (everyoneCantChange || selfCantChange)
{
cantChange = true;
}
}
return cantChange;
}
}
这是我对set 的解决方案:
public bool SetUserCannotChangePassword(string userDistinguishedName, bool userCannotChangePassword)
{
using (var ldapConnection = CreateLdapConnection()) //Assuming you've connected using Admin rights
{
bool success = true;
try
{
//Get RootDomainNamingContext as searchContainer
var r1 = (SearchResponse)ldapConnection.SendRequest(new SearchRequest("", "(objectClass=*)", SearchScope.Base));
var searchContainer = response.Entries[0].Attributes["rootdomainnamingcontext"].GetValues(typeof(string))[0]
.ToString();
//Set Filter to get specified user
var filter = $"(&(objectClass=user)(!(objectClass=computer))(distinguishedName={userDistinguishedName}))";
//Get the ntSecurityDescriptor attribute of the user
var searchRequest = new SearchRequest(searchContainer, filter, SearchScope.Subtree, new[] { "ntSecurityDescriptor", "distinguishedName" });
var searchOptions = new SearchOptionsControl(SearchOption.DomainScope);
searchRequest.Controls.Add(searchOptions);
var searchResponse = (SearchResponse)ldapConnection.SendRequest(searchRequest);
var result = searchResponse.Entries.OfType<SearchResultEntry>()
.SingleOrDefault();
if (result != null)
{
try
{
RawSecurityDescriptor sd =
new RawSecurityDescriptor((byte[]) result.Attributes["ntSecurityDescriptor"][0], 0);
var dn = result.Attributes["distinguishedName"][0];
var oACL = sd.DiscretionaryAcl;
int? everyoneCantChangeIndex = null;
ObjectAce everyoneAce = null;
int? selfCantChangeIndex = null;
ObjectAce selfAce = null;
for (var i = 0; i < oACL.Count; i++)
{
var oAce = oACL[i] as ObjectAce;
if (oAce?.ObjectAceType.ToString().Equals("AB721A53-1E2F-11D0-9819-00AA0040529B",
StringComparison.OrdinalIgnoreCase) == true)
{
if (oAce.SecurityIdentifier.Value.Equals("S-1-1-0",
StringComparison.OrdinalIgnoreCase))
{
everyoneCantChangeIndex = i;
everyoneAce = oAce;
}
if (oAce.SecurityIdentifier.Value.Equals("S-1-5-10",
StringComparison.OrdinalIgnoreCase) &&
oAce.AceType == AceType.AccessDeniedObject)
{
selfCantChangeIndex = i;
selfAce = oAce;
}
}
}
if (everyoneCantChangeIndex.HasValue)
{
oACL.RemoveAce(everyoneCantChangeIndex.Value);
}
if (selfCantChangeIndex.HasValue)
{
if (everyoneCantChangeIndex.HasValue &&
everyoneCantChangeIndex.Value < selfCantChangeIndex.Value)
{
selfCantChangeIndex--; //Adjust index to ensure removing correct ACE
}
oACL.RemoveAce(selfCantChangeIndex.Value);
}
if (userCannotChangePassword)
{
oACL.InsertAce(everyoneCantChangeIndex ?? oACL.Count,
new ObjectAce(AceFlags.None, AceQualifier.AccessDenied,
everyoneAce?.AccessMask ?? 256,
everyoneAce?.SecurityIdentifier ??
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
ObjectAceFlags.ObjectAceTypePresent,
everyoneAce?.ObjectAceType ??
new Guid("{AB721A53-1E2F-11D0-9819-00AA0040529B}"),
everyoneAce?.InheritedObjectAceType ?? Guid.Empty,
everyoneAce?.IsCallback ?? false, everyoneAce?.GetOpaque()));
oACL.InsertAce(selfCantChangeIndex ?? oACL.Count,
new ObjectAce(AceFlags.None, AceQualifier.AccessDenied, selfAce?.AccessMask ?? 256,
selfAce?.SecurityIdentifier ??
new SecurityIdentifier(WellKnownSidType.SelfSid, null),
ObjectAceFlags.ObjectAceTypePresent,
selfAce?.ObjectAceType ?? new Guid("{AB721A53-1E2F-11D0-9819-00AA0040529B}"),
selfAce?.InheritedObjectAceType ?? Guid.Empty, selfAce?.IsCallback ?? false,
selfAce?.GetOpaque()));
}
else
{
oACL.InsertAce(everyoneCantChangeIndex ?? oACL.Count,
new ObjectAce(AceFlags.None, AceQualifier.AccessAllowed,
everyoneAce?.AccessMask ?? 256,
everyoneAce?.SecurityIdentifier ??
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
ObjectAceFlags.ObjectAceTypePresent,
everyoneAce?.ObjectAceType ??
new Guid("{AB721A53-1E2F-11D0-9819-00AA0040529B}"),
everyoneAce?.InheritedObjectAceType ?? Guid.Empty,
everyoneAce?.IsCallback ?? false, everyoneAce?.GetOpaque()));
}
var modification = new DirectoryAttributeModification
{
Operation = DirectoryAttributeOperation.Replace,
Name = "ntSecurityDescriptor"
};
sd.DiscretionaryAcl = OrderRawAcl(oACL);
var ba = new byte[sd.BinaryLength];
sd.GetBinaryForm(ba, 0);
modification.Add(ba);
var modifyRequest = new ModifyRequest(dn.ToString(), modification);
var modifyResponse = ldapConnection.SendRequest(modifyRequest);
if (modifyResponse.ResultCode != ResultCode.Success)
{
success = false;
}
}
catch (Exception ex)
{
success = false;
}
}
}
catch (Exception ex)
{
success = false;
}
return success;
}
}
private RawAcl OrderRawAcl(RawAcl oAcl)
{
// Thanks to this post for this awesome method (https://stackoverflow.com/questions/8126827/how-do-you-programmatically-fix-a-non-canonical-acl)
// A canonical ACL must have ACES sorted according to the following order:
// 1. Access-denied on the object
// 2. Access-denied on a child or property
// 3. Access-allowed on the object
// 4. Access-allowed on a child or property
// 5. All inherited ACEs
List<GenericAce> implicitDenyDacl = new List<GenericAce>();
List<GenericAce> implicitDenyObjectDacl = new List<GenericAce>();
List<GenericAce> inheritedDacl = new List<GenericAce>();
List<GenericAce> implicitAllowDacl = new List<GenericAce>();
List<GenericAce> implicitAllowObjectDacl = new List<GenericAce>();
foreach (var ace in oAcl)
{
if ((ace.AceFlags & AceFlags.Inherited) == AceFlags.Inherited)
{
inheritedDacl.Add(ace);
}
else
{
switch (ace.AceType)
{
case AceType.AccessAllowed:
implicitAllowDacl.Add(ace);
break;
case AceType.AccessDenied:
implicitDenyDacl.Add(ace);
break;
case AceType.AccessAllowedObject:
implicitAllowObjectDacl.Add(ace);
break;
case AceType.AccessDeniedObject:
implicitDenyObjectDacl.Add(ace);
break;
}
}
}
Int32 aceIndex = 0;
RawAcl newDacl = new RawAcl(oAcl.Revision, oAcl.Count);
implicitDenyDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
implicitDenyObjectDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
implicitAllowDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
implicitAllowObjectDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
inheritedDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
if (aceIndex != oAcl.Count)
{
throw new Exception("Reordering Access Control List unsuccessful. The number of items in the reordered list does not match the number of items submitted list.");
}
return newDacl;
}
这基本上遵循本文档中详述的步骤:https://docs.microsoft.com/en-us/windows/win32/adsi/modifying-user-cannot-change-password-ldap-provider
希望有人觉得这很有帮助。