Это продолжение мой предыдущий вопрос.
What is the correct way of validating the credentials passed to a
PrincipalContext?
В моем приложении я создаю экземпляр PrincipalContext, используя PrincipalContext(ContextType, String, String, String). У меня есть несколько интеграционных тестов, которые терпят неудачу, когда учетные данные неверны (или предоставленные учетные данные не для администратора), поэтому я хочу уловить это.
Если учетные данные недействительны, PrincipalContext.ConnectedServer выдает System.DirectoryServices.DirectoryServicesCOMException, однако это не обнаруживается до первого использования PrincipalContext.
try
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "my_domain.local", "wrong_username", "wrong_password");
}
catch (exception e)
{
// This block is not hit
}
// `System.DirectoryServices.DirectoryServicesCOMException` raised here
using (UserPrincipal user = UserPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, samAccountName)) {}
Сведения об исключении:
System.DirectoryServices.DirectoryServicesCOMException
HResult=0x8007052E
Message=The user name or password is incorrect.
Source=System.DirectoryServices
StackTrace:
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.PropertyValueCollection.PopulateList()
at System.DirectoryServices.PropertyValueCollection..ctor(DirectoryEntry entry, String propertyName)
at System.DirectoryServices.PropertyCollection.get_Item(String propertyName)
at System.DirectoryServices.AccountManagement.PrincipalContext.DoLDAPDirectoryInitNoContainer()
at System.DirectoryServices.AccountManagement.PrincipalContext.DoDomainInit()
at System.DirectoryServices.AccountManagement.PrincipalContext.Initialize()
at System.DirectoryServices.AccountManagement.PrincipalContext.get_QueryCtx()
at System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithTypeHelper(PrincipalContext context, Type principalType, Nullable`1 identityType, String identityValue, DateTime refDate)
at System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithType(PrincipalContext context, Type principalType, IdentityType identityType, String identityValue)
at System.DirectoryServices.AccountManagement.UserPrincipal.FindByIdentity(PrincipalContext context, IdentityType identityType, String identityValue)
Моя первоначальная мысль заключалась в том, чтобы проверить учетные данные при создании, однако, если мы повторно используем PrincipalContext с другими учетными данными, мы получим System.DirectoryServices.Protocols.LdapException.
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "my_domain.local", "correct_username", "correct_password");
if (ctx.ValidateCredentials("correct_username", "correct_password"))
{
// `System.DirectoryServices.Protocols.LdapException` raised here
using (UserPrincipal user = UserPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, different_user)) {}
}
Сведения об исключении:
System.DirectoryServices.Protocols.LdapException
HResult=0x80131500
Message=The LDAP server is unavailable.
Source=System.DirectoryServices.Protocols
StackTrace:
at System.DirectoryServices.Protocols.ErrorChecking.CheckAndSetLdapError(Int32 error)
at System.DirectoryServices.Protocols.LdapSessionOptions.FastConcurrentBind()
at System.DirectoryServices.AccountManagement.CredentialValidator.BindLdap(NetworkCredential creds, ContextOptions contextOptions)
at System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName, String password)
at System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(String userName, String password)
Есть ли общепринятый способ проверить это? Должен ли я попытаться назначить PrincipalContext.ConnectedServer локальной переменной и поймать исключение?





Вы можете просто переместить фактическое использование контекста в блок try:
try
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "my_domain.local", "wrong_username", "wrong_password");
using (UserPrincipal user = UserPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, samAccountName)) {}
}
catch (exception e)
{
}
Если вы планируете использовать этот контекст для других операций, то это единственный способ проверить учетные данные.
Но если ваша цель Только - проверить учетные данные, вы можете использовать DirectoryEntry напрямую (установите System.DirectoryServices из NuGet). Из трассировки стека вы увидите, что PrincipalContext в любом случае использует DirectoryEntry. Я обнаружил, что использование DirectoryEntry напрямую в любом случае намного, намного быстрее, хотя иногда с ним может быть сложнее работать.
Вот как можно проверить учетные данные только с помощью DirectoryEntry:
var entry = new DirectoryEntry("LDAP://domain.local", "username", "password");
//creating the object doesn't actually make a connection, so we have to do something to test it
try {
//retrieve only the 'cn' attribute from the object
entry.RefreshCache(new[] {"cn"});
} catch (Exception e) {
}
Другой способ - использовать LdapConnection напрямую (установить System.DirectoryServices.Protocols из NuGet). Вероятно, это наименьший объем фактического сетевого трафика, который должен произойти для проверки учетных данных. Но, возможно, вам придется выяснить метод аутентификации. По умолчанию он использует Negotiate, но если это не сработает, вам придется использовать другой конструктор и выбрать метод аутентификации вручную.
var id = new LdapDirectoryIdentifier("domain.local");
var conn = new LdapConnection(id, new NetworkCredential("username", "password"));
try {
conn.Bind();
} catch (Exception e) {
}