Как проверить учетные данные, переданные в `PrincipalContext`

Это продолжение моего предыдущего вопроса.

Вопрос

Каков правильный способ проверки учетных данных, переданных 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 локальной переменной и поймать исключение?


person Wes Toleman    schedule 30.04.2018    source источник


Ответы (1)


Вы можете просто переместить фактическое использование контекста в блок 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) {

}
person Gabriel Luci    schedule 30.04.2018