我正在尝试准备无法在AD中更改密码的用户的报告。 AD安装在Window Server 2012上。
以下是我认为有效但不起作用的方法 -
/// <summary>
/// Check whether password of user cannot be changed.
/// </summary>
/// <param name="user">The DirectoryEntry object of user.</param>
/// <returns>Return true if password cannot be changed else false.</returns>
public static bool IsPasswordCannotBeChanged(DirectoryEntry user)
{
if (user.Properties.Contains("userAccountControl") &&
user.Properties["userAccountControl"].Value != null)
{
var userFlags = (UserFlags)user.Properties["userAccountControl"].Value;
return userFlags.Contains(UserFlags.PasswordCannotChange);
}
return false;
}
这是enum UserFlags -
[Flags]
public enum UserFlags
{
// Reference - Chapter 10 (from The .NET Developer's Guide to Directory Services Programming)
Script = 1, // 0x1
AccountDisabled = 2, // 0x2
HomeDirectoryRequired = 8, // 0x8
AccountLockedOut = 16, // 0x10
PasswordNotRequired = 32, // 0x20
PasswordCannotChange = 64, // 0x40
EncryptedTextPasswordAllowed = 128, // 0x80
TempDuplicateAccount = 256, // 0x100
NormalAccount = 512, // 0x200
InterDomainTrustAccount = 2048, // 0x800
WorkstationTrustAccount = 4096, // 0x1000
ServerTrustAccount = 8192, // 0x2000
PasswordDoesNotExpire = 65536, // 0x10000 (Also 66048 )
MnsLogonAccount = 131072, // 0x20000
SmartCardRequired = 262144, // 0x40000
TrustedForDelegation = 524288, // 0x80000
AccountNotDelegated = 1048576, // 0x100000
UseDesKeyOnly = 2097152, // 0x200000
DontRequirePreauth = 4194304, // 0x400000
PasswordExpired = 8388608, // 0x800000 (Applicable only in Window 2000 and Window Server 2003)
TrustedToAuthenticateForDelegation = 16777216, // 0x1000000
NoAuthDataRequired = 33554432 // 0x2000000
}
您能否分享 64 (密码无法更改)的原因,不能为无法更改密码的用户退回?
或者你有更好的方法来解决这个问题?
编辑 -
UserFlagExtension 使事情变得更快的代码 -
public static class UserFlagExtensions
{
/// <summary>
/// Check if flags contains the specific user flag.
/// </summary>
/// <param name="haystack">The bunch of flags</param>
/// <param name="needle">The flag to look for.</param>
/// <returns>Return true if flag found in flags.</returns>
public static bool Contains(this UserFlags haystack, UserFlags needle)
{
return (haystack & needle) == needle;
}
}
答案 0 :(得分:3)
请继续关联AD .NET - User's Can't Change Password Attribute (Get/Set)
您需要添加对 ActiveDS 的引用才能使其正常工作。虽然我没有时间去测试它。但它应该有很多地方可以工作。所以......
上述文章中的代码段 - (如果文章被删除)
public bool GetCantChangePassword(string userid)
{
bool cantChange = false;
try
{
DirectoryEntry entry = new DirectoryEntry(string.Format("LDAP://{0},{1}", "OU=Standard Users,OU=Domain", "DC=domain,DC=org"));
entry.AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.ServerBind;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = string.Format("(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))", userid);
search.SearchScope = SearchScope.Subtree;
SearchResult results = search.FindOne();
if (results != null)
{
try
{
DirectoryEntry user = results.GetDirectoryEntry();
ActiveDirectorySecurity userSecurity = user.ObjectSecurity;
SecurityDescriptor sd = (SecurityDescriptor)user.Properties["ntSecurityDescriptor"].Value;
AccessControlList oACL = (AccessControlList)sd.DiscretionaryAcl;
bool everyoneCantChange = false;
bool selfCantChange = false;
foreach (ActiveDs.AccessControlEntry ace in oACL)
{
try
{
if (ace.ObjectType.ToUpper().Equals("{AB721A53-1E2F-11D0-9819-00AA0040529B}".ToUpper()))
{
if (ace.Trustee.Equals("Everyone") && (ace.AceType == (int)ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_DENIED_OBJECT))
{
everyoneCantChange = true;
}
if (ace.Trustee.Equals(@"NT AUTHORITY\SELF") && (ace.AceType == (int)ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_DENIED_OBJECT))
{
selfCantChange = true;
}
}
}
catch (NullReferenceException ex)
{
//Logger.append(ex.Message);
}
catch (Exception ex)
{
Logger.append(ex);
}
}
if (everyoneCantChange || selfCantChange)
{
cantChange = true;
}
else
{
cantChange = false;
}
user.Close();
}
catch (Exception ex)
{
// Log your errors!
}
}
entry.Close();
}
catch (Exception ex)
{
// Log your errors!
}
return cantChange;
}
这就是我能够把它钉死的方式。它很容易修复。但是,我需要使用AuthenticablePrincipal.UserCannotChangePassword Property。
我使用的代码段 -
/// <summary>
/// Check whether password of user cannot be changed.
/// </summary>
/// <param name="user">The DirectoryEntry object of user.</param>
/// <returns>Return true if password cannot be changed else false.</returns>
public static bool IsPasswordCannotBeChanged(DirectoryEntry user)
{
var isUserCantChangePass = false;
try
{
// 1. Get SamAccountName
var samAccountName = Convert.ToString(user.Properties["sAMAccountName"].Value);
if (!string.IsNullOrEmpty(samAccountName))
{
// 2. Prepare domain context
using (var domainContext = new PrincipalContext(ContextType.Domain, _domain, _domainUser, _domainPass))
{
// 3. Find user
var userPrincipal = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, samAccountName);
// 4. Check if user cannot change password
using (userPrincipal)
if (userPrincipal != null) isUserCantChangePass = userPrincipal.UserCannotChangePassword;
}
}
}
catch (Exception exc)
{
Logger.Write(exc);
}
return isUserCantChangePass;
}
答案 1 :(得分:1)
活动目录不使用所有这些标志。具体来说,
Active Directory实际上使用不同的机制来控制它们 帐户属性,所以不要尝试从中读取它们 userAccountControl的!我们讨论如何处理特殊情况 即将到来的部分。
PasswordCannotChange背后的想法表明该帐户的密码不能由帐户本身更改,但要做到这一点,您实际上必须拒绝这一点(在帐户安全标签下)
尝试使用msDS-User-Account-Control-Computed属性检查ADS_UF_PASSWD_CANT_CHANGE flag。像这样:
DirectoryEntry user = ...
const string ATTRIBUTE_NAME= "msDS-User-Account-Control-Computed";
const ADS_UF_PASSWD_CANT_CHANGE = 64; // use enum for more robust code
using (user)
{
user.RefreshCache(new string[]{ATTRIBUTE_NAME});
int userFlags = (int)user.Properties[ATTRIBUTE_NAME].Value;
bool userCantChangePassword = (userFlags & ADS_UF_PASSWD_CANT_CHANGE) == ADS_UF_PASSWD_CANT_CHANGE;
...
}
答案 2 :(得分:1)
如果有人像我一样来这里寻找如何使用.NET Core 3.1进行操作,这是我想出的解决方案,用于获取并设置PasswordCannotChange
属性上的UserAccountControl
位在广告中。
我使用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
希望有人发现这有帮助。