Странная ошибка при обработке ошибок при вызове UserPrinciapl.GetGroups в System.DirectoryServices.AccountManagement - PullRequest
6 голосов
/ 04 января 2012

Фон

У нас есть веб-приложение asp.net 4.0, написанное на C #, которое вызывает веб-сервис .net 3.5, написанное на C #.Веб-службе передается идентификатор пользователя и возвращается список данных в зависимости от групп активных каталогов, к которым принадлежит пользователь.

Веб-служба использует версию System.DirectoryServices.AccountManagement .net 3.5 для получения Sids ofгруппы, к которым принадлежит пользователь.

Вызов UserPrincipal.GetGroups периодически прерывается с ошибкой ниже.Между событиями были очень большие промежутки времени, но когда это происходило, оно повторялось в течение нескольких минут.Эта проблема возникла для разных пользователей AD.

Трассировка стека этого исключения не имела для нас никакого смысла.Мы потратили много времени на просмотр кода Microsoft AD в Reflector / ILSpy, но не смогли выйти за пределы вызова IADsPathName.Retrieve.

Исключение

System.NotSupportedException: Specified method is not supported.
at System.Web.HttpResponseStream.get_Position()
at System.Drawing.UnsafeNativeMethods.ComStreamFromDataStream.Seek(Int64 offset, Int32 origin)
at System.DirectoryServices.AccountManagement.UnsafeNativeMethods.IADsPathname.Retrieve(Int32 lnFormatType)
at System.DirectoryServices.AccountManagement.ADStoreCtx.LoadDomainInfo()
at System.DirectoryServices.AccountManagement.ADStoreCtx.get_DnsForestName()
at System.DirectoryServices.AccountManagement.ADStoreCtx.GetGroupsMemberOf(Principal p)
at System.DirectoryServices.AccountManagement.Principal.GetGroupsHelper()
at System.DirectoryServices.AccountManagement.Principal.GetGroups()
at Data.SoftwarePublishingItemData.GetSids(String requestedForUserId)
at Data.SoftwarePublishingItemData.GetSoftwarePublishingItems(IDatabaseContext dbContext, GetSoftwarePublishingItemsSettings settings, XBXmlDocument parameters)
at Web.GetSoftwarePublishingItems.GetFlexiFieldData(String xml)

Код для воспроизведения

Обратите внимание, что метод CauseNotSupportedException имитирует код, который выполняется не в нашем приложении, а в коде где-то еще в среде, которую мы не контролируем.

class Program
{
    static void Main(string[] args)
    {
        CauseNotSupportedException();

        string samAccountName = "domain.user";

        using (var principalContext = new PrincipalContext(ContextType.Domain))
        {
            using (var userPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, samAccountName))
            {
                if (userPrincipal == null)
                    throw new ActiveDirectoryObjectNotFoundException();

                using (var groups = userPrincipal.GetGroups())
                {
                    foreach (GroupPrincipal group in groups)
                    {
                        Console.WriteLine(group.Sid);
                    }
                }
            }
        }
    }

    public static void CauseNotSupportedException()
    {
        using (var b = new Bitmap(500, 500, PixelFormat.Format32bppArgb))
        {
            b.Save(new FakeStream(), ImageFormat.Png);
        }
    }
}

Реализация Stream для имитации поведения HttpResponseStream

public class FakeStream : Stream
{
    public override bool CanRead { get { return false; } }
    public override bool CanSeek { get { return false; } }
    public override bool CanWrite { get { return true; } }

    public override void Flush() { }

    public override long Length { get { throw new NotSupportedException("No Seek"); } }

    public override long Position
    {
        get { throw new NotSupportedException("No Seek"); }
        set { throw new NotSupportedException("No Seek"); }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        throw new InvalidOperationException("Write only stream");
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotSupportedException("net_noseek");
    }

    public override void SetLength(long value) { }

    public override void Write(byte[] buffer, int offset, int count) { }
}

Вопросы

  1. Если запустить приведенный выше пример, ошибка возникает вметод CauseNotSupportedException вызывается при вызове GetGroups.Как это может быть?Будем признательны за любые теории или дальнейшее понимание.
  2. Есть ли какие-либо предложения относительно дальнейших расследований?
  3. Есть ли какие-либо лучшие предложения, чем перехват исключения и повторная попытка?Это наша текущая работа.

Спасибо.

Уточнение

Я не уверен, насколько ясноЯ был в моем объяснении, так что вот некоторые пояснения.Во-первых, я доволен кодом активной директории, который получает Sids.Это делает то, что я хочу, и я не думаю, что проблема в этом.Реальная проблема заключается в том, что когда ошибка возникает в другом не связанном коде (его нет в нашем приложении), ошибка проявляется в вызове GetGroups, следовательно, странная трассировка стека с ошибкой, первоначально возникающей в System.Web.HttpResponseStream.get_Position ().В примере приложения NotSupportedException возникает в CauseNotSupportedException, но код там не нарушается, он прерывается при вызове GetGroups.Если вы закомментируете CauseNotSupportedException () в примере приложения, ошибка никогда не произойдет.

Мне неясно, как это может произойти.

Ответы [ 2 ]

4 голосов
/ 11 апреля 2012

После вызова службы поддержки Microsoft выпустила исправление для этой проблемы. См. Ссылку ниже.

Заявленная причина: «Эта проблема возникает из-за того, что пространство имен System.DirectoryServices.AccountManagement представляет собой тонкую оболочку для собственных интерфейсов API Active Directory Service Interface (ADSI). Интерфейс IErrorInfo, реализованный интерфейсом IADsPathName, реагирует на исключения, которые ADSI не выдает. в стеке нет исключения ADSI, интерфейс IErrorInfo создает исключение, находящееся в верхней части стека, даже если исключение обрабатывается другим обработчиком в приложении. "

http://support.microsoft.com/kb/2683913

Спасибо тем, кто предложил свои предложения.

0 голосов
/ 04 января 2012

Если вы используете .NET 3.5 или более позднюю версию, вы можете использовать новое пространство имен System.DirectoryServices.AccountManagement (S.DS.AM), которое делает это намного проще, чем раньше.

Прочтите все об этом здесь: [Управление принципами безопасности каталогов в .NET Framework 3.5] [1]

По сути, вам нужен «основной контекст» (обычно ваш домен), пользовательский принципал, и тогда вы очень легко получите его группы:

public List<GroupPrincipal> GetGroups(string userName)
{
   List<GroupPrincipal> result = new List<GroupPrincipal>();

   // establish domain context
   PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain);

   // find your user
   UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, username);

   // if found - grab its groups
   if(user != null)
   {
      PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups();

      // iterate over all groups
      foreach(Principal p in groups)
      {
         // make sure to add only group principals or change this to add to a list or varible if needed.
         if(p is GroupPrincipal)
         {
             result.Add(p);
         }
      }
   }

   return result;
}

Чтобы получить доступ к определенным свойствам, которые не отображаются на объекте UserPrincipal, вам нужно покопаться в базовом DirectoryEntry:

public string GetDepartment(Principal principal)
{
    string result = string.Empty;

    DirectoryEntry de = (principal.GetUnderlyingObject() as DirectoryEntry);

    if (de != null)
    {
       if (de.Properties.Contains("samAccountName"))
       {
          result = de.Properties["samAccountName"][0].ToString();
       }
    }

    return result;
}

//Change this Method to fit what ever your needs desire.. 
public string GetDepartment(string username)
{
    string result = string.Empty;

    // if you do repeated domain access, you might want to do this *once* outside this method, 
    // and pass it in as a second parameter!
    PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain);

    // find the user
    UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, username);

    // if user is found
    if(user != null)
    {
       // get DirectoryEntry underlying it
       DirectoryEntry de = (user.GetUnderlyingObject() as DirectoryEntry);

       if (de != null)
       {
          if (de.Properties.Contains("department"))
          {
             result = de.Properties["department"][0].ToString();
          }
       }
    }

    return result;
}
...