Skip to content

חיפוש ב־LDAP עם חלוקה לעמודים

חיפוש ב־LDAP עם חלוקה לעמודים

פורסם במקור: · המאמר המקורי באנגלית

LDAP

בחיפוש בספריית LDAP גדולה, השרת עשוי להגביל את מספר הרשומות שהוא מחזיר בתגובה אחת. ברירת המחדל המתועדת של MaxPageSize ב־Active Directory היא 1,000 אובייקטים, ולא 20,000. המגבלה בפועל תלויה במדיניות השאילתות של השרת; מגבלת עמוד אינה המספר הכולל של רשומות שניתן לקבל לאורך חיפוש המחולק לעמודים. ראו מדיניות LDAP של Microsoft.

משתמשים בבקרי הבקשה והתגובה של LDAP simple paged results כדי לקבל עמודים בזה אחר זה. מתחילים עם עוגייה (cookie) ריקה. ראו RFC 2696.

שולחים את גודל העמוד המבוקש, ולאחר מכן משתמשים בבקשה הבאה בעוגייה האטומה שהתקבלה בתגובה האחרונה. שומרים על אותם פרמטרים של חיפוש בין העמודים. עוגייה ריקה בתגובה מסמנת שהחיפוש הסתיים. אין לפרש בשקט בקר תגובה חסר או חיפוש שנכשל כסיום מוצלח. החלוקה לעמודים אינה מבטיחה תמונת מצב קבועה בזמן שתוכן הספרייה משתנה. ראו RFC 2696, האינטראקציה בין הלקוח לשרת.

הנה דוגמה ברמת היישום המשתמשת ב־LdapForNet. היא מניחה שכבר קיימים חיבור מוגן כראוי, DN בסיסי לחיפוש, רשימת מאפיינים מבוקשים, אוסף תוצאות ורכיב רישום ליומן. הדוגמה שומרת על המסנן המקורי objectClass=user ועל המיון לפי sAMAccountName, אך בודקת את תוצאות החיפוש, החלוקה לעמודים והמיון לפני קבלת כל עמוד. ב־Active Directory המסנן הזה יכול להתאים גם לאובייקטים של מחשבים; השתמשו במסנן שמתאים לאובייקטים הדרושים לכם, למשל (&(objectCategory=person)(objectClass=user)) עבור אובייקטים של משתמשים. ראו מסנני חיפוש של ADSI ו־מימוש שאילתות AD ב־.NET.

byte[] cookie = Array.Empty<byte>();

while (true)
{
    SearchRequest searchRequest = new(ldapSource.BaseDN,
        "(objectClass=user)", LdapSearchScope.LDAP_SCOPE_SUB, attributes);

    searchRequest.Controls.Add(new PageResultRequestControl(1000)
    {
        Cookie = cookie,
        IsCritical = true
    });
    searchRequest.Controls.Add(new SortRequestControl(
        new SortKey { AttributeName = "sAMAccountName" }));

    SearchResponse searchResponse =
        (SearchResponse)ldapConnection.SendRequest(searchRequest);

    if ((int)searchResponse.ResultCode != 0)
        throw new InvalidOperationException("LDAP search did not succeed.");
    if (searchResponse.References.Count != 0 || searchResponse.Referral.Length != 0)
        throw new InvalidOperationException("LDAP referral handling is required.");

    PageResultResponseControl page = searchResponse.Controls
        .OfType<PageResultResponseControl>().FirstOrDefault()
        ?? throw new InvalidOperationException("Missing paged-results response.");
    SortResponseControl sort = searchResponse.Controls
        .OfType<SortResponseControl>().FirstOrDefault()
        ?? throw new InvalidOperationException("Missing sort response.");
    if ((int)sort.Result != 0)
        throw new InvalidOperationException("LDAP server-side sorting failed.");

    ldapEntries.AddRange(searchResponse.Entries.Select(e => e.ToLdapEntry()));
    cookie = page.Cookie;
    _logger.LogDebug("Collected entries: {ldapEntries}", ldapEntries.Count);

    if (cookie.Length == 0)
        break;
}

מיון בצד השרת הוא בקר נפרד, ולא דרישה של החלוקה הפשוטה לעמודים; כאשר מבקשים אותו, יש לבדוק את התוצאה שלו כפי שמוצג. הדוגמה נכשלת אם נדרש טיפול בהפניות לשרתי LDAP אחרים, במקום לדווח שתוצאה חלקית היא מלאה. לפני שימוש בתהליך עבודה בייצור, הוסיפו טיפול בביטול ובכשלים בהתאם ליישום, והגדירו במפורש מדיניות לטיפול בהפניות. הדוגמה אינה תוכנית עצמאית שנבדקה. מקורות: RFC 2891, בקרי LdapForNet ו־סוגי התגובות.

המחלקה System.DirectoryServices.AccountManagement.PrincipalSearcher של .NET, שבה משתמשים בהמשך, מפעילה חלוקה לעמודים בהקשר שמבוסס על AD במימוש שנבדק. אין פירוש הדבר שכל ממשק LDAP של .NET מחלק תוצאות לעמודים אוטומטית: באובייקט DirectorySearcher שנוצר ישירות, ברירת המחדל היא PageSize = 0, שמבטלת חלוקה לעמודים. קוד המקור הנוכחי של PrincipalSearcher מגדיר 256 עבור הקשרים שמבוססים על AD; זהו מספר רשומות, ולא קילובייטים. ראו DirectorySearcher.PageSize.

using PrincipalContext ctx = new(
    ContextType.Domain, "[hostname]:389", username, password);
using UserPrincipal userPrincipal = new(ctx);
using PrincipalSearcher searcher = new(userPrincipal);
using PrincipalSearchResult<Principal> result = searcher.FindAll();
// Enumerate result and finish processing before these objects are disposed.

שם המארח ומשתני פרטי ההתחברות הם מצייני מקום שהיישום מספק, ולא הגדרה מלאה של אבטחת החיבור. הצהרות using מבהירות את משך החיים של ההקשר, השאילתה והתוצאה. עברו על התוצאות כל עוד האובייקטים האלה קיימים; בחרו הגנה לחיבור ואימות שמתאימים לסביבה שלכם.

Post a Comment

Previous Post Next Post