среда, 30 января 2019 г.

Active Directory Sync

  • .NET
По долгу службы пришлось разбираться с Active Directory. Пришлось почитать, поэкспериментировать с классами, но всё в результате заработало превосходно.

В первую очередь хотелось бы описать немного Directory Synchronization объект, который появился в .net framework 2.0. О нём, и о других преимуществах 2го framework вы сможете почитать на сайте microsoft (http://msdn.microsoft.com/en-us/magazine/cc188700.aspx ). Лично мне статья помогла разобраться, хотя я обилия информации в сети на предмет dyrSync нет.


Для синхронизации я использую Web-Service, действия происходят следующим образом:

1. Веб сервис получает LDAP путь к запросу. 
  [WebMethod]
  public void Synchronize (string DomainPath, string Filter, string EntryPath)
  {

        DirectoryEntry de = new DirectoryEntry (DomainPath);
        using (SqlConnection conn = new SqlConnection(Globals.ConnectionString))
        {
          conn.Open();
          SqlCommand command = conn.CreateCommand();
          command.CommandText = «INSERT INTO SyncTable (Snapshot, OU) VALUES (@Snapshot, @OU, @ExchangeServer)»;
          command.Parameters.AddWithValue("@Snapshot", GetSyncData(de, filter));
          command.Parameters.AddWithValue(" @OU", EntryPath);

          command.ExecuteNonQuery();
          conn.Close();
        }

  }

2. GetSyncData возвращает AD cookie:
    public byte[] DirectorySync (DirectoryEntry DomainDE, string Filter)
    {
      ADInit.Init();
      try
      {
        using (DirectorySearcher srch = new DirectorySearcher(DomainDE, Filter))
        {
          srch.SearchScope = SearchScope.Base;
          srch.DirectorySynchronization = new DirectorySynchronization();

          foreach (SearchResult se in srch.FindAll())
          {
          }

          MemoryStream ms = new MemoryStream();
          BinaryFormatter bFormat = new BinaryFormatter();
          bFormat.Serialize(ms, srch.DirectorySynchronization.GetDirectorySynchronizationCookie());
          ms.Close();

          return ms.GetBuffer();         
        }
      }
      catch (Exception Ex)
      {
        throw Ex;
      }
    }

Приходится использовать Домен в качестве точки входа, потому как DirSynch работает с корневым элементом дерева. Фильтр может быть в виде: OU=myOU или что-либо в этом роде. В моём случае я достаточно просто обошел эту особенность: создал иерархическую структуру, из которой можно выбрать и LDAP путь до домена, и LDAP путь до данного объекта. Фильтр же – через имя объекта, который нужно синхронизировать.

3. Обратная синхронизация + сверка
    [WebMethod]
    public bool GetRegionEntry (string Domain, string Filter, string EntryPath)
    {
      DirectoryEntry de = new DirectoryEntry (Domain);

      RegionInfo _regionInfo = regionEntry.GetEntry();
      using (SqlConnection conn = new SqlConnection(Globals.ConnectionString))
      {
        conn.Open();
        SqlCommand command = conn.CreateCommand();
        command.CommandText = «SELECT * FROM SyncTable WHERE OU= @OU»;
        command.Parameters.AddWithValue(" @OU", EntryPath);

        SqlDataReader reader = command.ExecuteReader();
        if (reader.Read())
        {
          return regionEntry.GetSyncDelta(de, Filter, (byte[])reader[«Snapshot»]);
        }
        else
        {
          throw new Exception (“Can’t read Sync Cookie from Database!”);

        }
        conn.Close();
      }
      return _regionInfo;
    }

Здесь поступайте на своё усмотрение, либо получите bool (есть разница – нет разницы), либо получите дельту в полном объеме.
    ///public Dictionary<string, object> GetSyncDelta (DirectoryEntry DomainDE, string Filter, byte[] _cookie)

    public bool GetSyncDelta (DirectoryEntry DomainDE, string Filter, byte[] _cookie)
    {
      Dictionary<stringobject> _delta = new Dictionary<stringobject>();

      BinaryFormatter bf = new BinaryFormatter();
      byte[] cookie = (byte[]) bf.Deserialize(new MemoryStream(_cookie));

      DirectorySynchronization dirSync = new DirectorySynchronization(cookie);
      DirectorySearcher srch = new DirectorySearcher(DomainDE, Filter);
      srch.DirectorySynchronization = dirSync;

      foreach(SearchResult sr in srch.FindAll())
      {
        foreach (string attrName in sr.Properties.PropertyNames)
        {
          _delta.Add( attrName, sr.Properties[attrName]);
        }
        return true;
      }

      return false;
      //return _delta;
    }

(Dictionary<stringobject> не сериализируется стандартными методами в XML, попробуйте использовать свои массивы с keyvaluepair, либо перепишите сериализацию).

4. Для того, чтобы сохранить всю историю изменений записи, создайте еще одну таблицу, добавьте к ней поле-идентификатор и поставьте триггер на INSERT / UPDATE:
ALTER TRIGGER [SyncTableTrg]
  ON [dbo].[SyncTable]
  AFTER INSERTUPDATE
AS
BEGIN
  INSERT INTO SyncTableLog (OU, Snapshot)
    SELECT OU, Snapshot FROM inserted
END

Таким образом получите неплохую историю о каждой из записей, которые синхронизируете. Хотя это – не единственный способ.

спасибо за внимание

з.ы. не судите строго. прежде не блоггил( first experience