Primary Group وmemberOf في Active Directory: ليش Domain Users ما تظهر، وكيف تكمل قراءة العضوية؟
تقرأ memberOf لحساب مستخدم وتلقى مجموعتين أو ثلاث، لكن Domain Users ما تظهر. ثم تقرأ member للمجموعة نفسها وتستغرب أن عدد الأسماء أقل من عدد موظفي الدومين. قبل ما تتهم الاستعلام أو تكتب أن الحساب بلا عضوية أساسية، انتبه لطريقة تمثيل Primary Group في Active Directory.
المجموعة الأساسية ليست بالضرورة صفًا إضافيًا داخل memberOf. تُحدد عبر primaryGroupID، وهذه الخاصية تحفظ RID للمجموعة الأساسية. Microsoft توضح أن علاقة المجموعة الأساسية لا تُعرض بالطريقة نفسها في memberOf للمستخدم ولا في member للمجموعة. هذه التفصيلة الصغيرة تؤثر في جرد العضويات، وفهم نتائج أدوات LDAP، ومراجعة تطبيق يستخدم المجموعات للسماح بالدخول. خلنا نمشي من حساب واحد إلى استنتاج يمكن إثباته.
وش الذي تجيب عنه memberOf فعلًا؟
عندما تطلب memberOf، فأنت تقرأ خاصية في كائن الدليل؛ لست تطلب تلقائيًا تقريرًا شاملًا بكل حقوق الحساب في كل مورد. القيمة مفيدة لمعرفة علاقات عضوية يظهرها هذا التمثيل، لكنها تحتاج تفسيرًا حسب نوع السؤال. العضويات المتداخلة والمجموعة الأساسية وسياق الوصول إلى مورد معين ليست فكرة واحدة، ولو جمعتها تحت عنوان «صلاحيات المستخدم» بتضيع فروق مهمة.
خلنا نفترض أن الحساب amal يظهر في Lab-Readers وLab-Students. هذه بداية ممتازة لمعرفة موقعه في تصميم التدريب. لكن لو Lab-Readers عضو في مجموعة أخرى تتحكم في مورد، فأنت تحتاج متابعة العلاقة المتداخلة حتى تفهم المسار. ولو كانت المجموعة الأساسية مختلفة، فأنت تحتاج قراءتها من مكانها المناسب بدل توقع ظهورها في القائمة نفسها.
ومن جهة المورد، لا يكفي الاسم. أي مجموعة تسمح بالعملية المطلوبة؟ هل هي مجموعة أمان مرتبطة بإذن فعلي، أم مجرد تنظيم لا يمنح الوصول إلى هذا المورد؟ وهل التطبيق يعتمد على LDAP أم على سياق مصادقة أو قائمة أدوار داخلية؟ جواب هذه الأسئلة يحدد نوع دليل العضوية الذي يلزمك.
لذلك سمّ المخرجات باسمها: «قيم memberOf المقروءة من DC01» أو «العلاقات المباشرة التي عرضها الاستعلام». لا تسمّها «كل صلاحيات الحساب» إلا لو عندك تحليل أوسع يبرر العبارة. هذا الفرق في اللغة ليس تدقيقًا شكليًا؛ هو الذي يمنع المسؤول من اتخاذ قرار إزالة أو منح وصول اعتمادًا على قائمة ناقصة.
والأمر نفسه ينطبق على member في المجموعة. الخاصية تعرض قائمة مرتبطة بتمثيل عضوياتها، لكن العلاقة الأساسية لها طريقة مستقلة. حين تريد عددًا شاملًا، حدد تعريف العدد قبل التنفيذ: المستخدمون المباشرون فقط، أم المتداخلون، أم من تكون هذه مجموعتهم الأساسية أيضًا؟ كل تعريف يقود إلى مجموعة نتائج مختلفة.
primaryGroupID هو RID، وليس SID كاملًا
الـSID لحساب دومين يتضمن جزءًا يحدد الدومين وجزءًا أخيرًا يميز الكائن داخله. primaryGroupID يحفظ RID للمجموعة الأساسية؛ لذلك القيمة وحدها ما تعطيك هوية مجموعة كاملة عبر أكثر من دومين. إذا رأيت الرقم نفسه في بيئتين، لا تجمع الحسابات في مجموعة واحدة لمجرد تطابق الرقم.
في المثال التدريبي، قد ترى primaryGroupID يساوي 513، وهو RID المعتاد لـDomain Users. المطلوب مع ذلك حل المجموعة في دومين المستخدم نفسه، لا الاعتماد على اسم محلي مترجم أو اسم عرض يمكنك تغييره. إذا كان لابك يحوي دومينين، احتفظ بهوية الدومين مع القيمة حتى لا تخلط بياناتهما في جدول واحد.
هذه الفكرة تشرح ليش بناء سكربت جرد على «إذا الرقم كذا إذن الاسم كذا» قد يكون مقبولًا كتوقع أولي محدود، لكنه ليس أفضل دليل لتعريف الكائن. الأفضل أن تحصل على SID المجموعة الكامل ثم تقرأ الكائن الذي يمثله من الدليل. القراءة تؤكد الاسم الحالي وDN ونوع المجموعة، وتسمح بإعادة التحقق لاحقًا حتى لو تغير اسمها.
ولا تخلط RID المجموعة الأساسية مع RID المستخدم. المستخدم له RID خاص به داخل SID الخاص بحسابه، بينما primaryGroupID يشير إلى RID مجموعة أخرى. لما تبني هوية المجموعة، تستخدم جزء الدومين المناسب ثم RID المشار إليه، من غير استبدال قيمة المستخدم نفسه في الدليل أو تعديل خصائصه.
ما نحتاج تنفيذ عملية تغيير حتى نتعلم هذا الربط. القراءة وحل الهوية يكفيان. وإذا كانت خاصية SID غير متاحة في المصدر الذي جمعته، اعتبر عملية الحل غير مكتملة واطلب المصدر المناسب. التخمين من الاسم أو الرقم في تقرير نهائي يضعف النتيجة أكثر مما يوفر وقتًا.
اقرأ المستخدم ثم حل المجموعة بأمر واضح
في لاب تملكه أو نطاق مصرح، ومع توفر وحدة ActiveDirectory وصلاحية القراءة، ابدأ بحساب واحد: $labUser = Get-ADUser -Identity 'amal' -Server 'dc01.lab.test' -Properties primaryGroupID,memberOf. ثم اعرض الحقول اللازمة باستخدام: $labUser | Select-Object SamAccountName,SID,DistinguishedName,primaryGroupID,memberOf. وجود الخصائص التي طلبتها في النتيجة يسبق أي استنتاج من قائمة فارغة.
لحل المجموعة الأساسية في المثال، يمكن تركيب SID من جزء دومين المستخدم وقيمة المجموعة: $labPrimarySid = $labUser.SID.AccountDomainSid.Value + '-' + $labUser.primaryGroupID. وبعدها اقرأ المجموعة نفسها: Get-ADGroup -Identity $labPrimarySid -Server 'dc01.lab.test' | Select-Object Name,SamAccountName,SID,DistinguishedName,GroupCategory,GroupScope. Get-ADGroup يقبل SID كهوية، وهذا أوضح من محاولة مطابقة الاسم النصي فقط.
استخدم أسماء اللاب بدل الأسماء التوضيحية، ولا تنفذ المثال على حسابات لم يُسمح لك بفحصها. هذه أوامر قراءة؛ فائدتها أنها تجيب «أي كائن يمثل المجموعة الأساسية لهذا المستخدم؟». ما تجيب وحدها «هل يستطيع الدخول إلى تطبيق التقارير؟» لأن السؤال الثاني يحتاج ربط هذه المجموعة بتصميم التطبيق وأذوناته.
لو فشل حل المجموعة، لا تحوّل الفشل مباشرة إلى «المجموعة محذوفة» أو «الحساب تالف». راجع دومين المستخدم ومصدر قراءة المجموعة، والأخطاء المعادة، وهل حسابك يستطيع القراءة. كذلك افصل فشل اتصال الأداة عن غياب الكائن نفسه. تحديد Server في المثال يجعل المصدر صريحًا، لكنه لا يصلح DNS أو تصريح قراءة غير متاح.
وبعد نجاح القراءة، احتفظ بهوية المجموعة الأساسية مع قيم memberOf في حقول منفصلة. تستطيع لاحقًا تكوين قائمة موحدة لغرض معين، لكن الفصل في البيانات الأصلية يسمح لك بشرح لماذا ظهر اسم في قائمك ولم يظهر في استعلام مباشر نسخته من LDAP. هذا هو الفرق بين ناتج يمكن تتبعه وقائمة أسماء فقدت أصل العلاقات.
ثلاث قوائم مختلفة قد تكون كلها صحيحة
تصور أن زميلك جمع memberOf للمستخدم، وأنت حللت المجموعة الأساسية وأضفتها لقائمة عرض، والتطبيق جمع عضوًا متداخلًا عبر مجموعة ثالثة. صار عندكم ثلاث قوائم غير متطابقة. اختلاف الأسماء وحده لا يثبت أن أحد المصادر يكذب؛ قد تكونون تسألون ثلاثة أسئلة مختلفة من غير أن تصرحوا بذلك.
القائمة الأولى تمثل الخاصية المطلوبة كما قرأها المصدر. الثانية توسع العرض بإضافة علاقة primaryGroupID. الثالثة تعتمد على طريقة احتساب مجموعات أو أدوار مرتبطة بعمل التطبيق. قبل مقارنة النتائج، اكتب تعريف كل قائمة ومصدرها وزمنها. إذا كان الهدف مقارنة الأدوات، وحّد السؤال أولًا ثم قارن، بدل الحكم على أداة بأنها ناقصة لأنها لم تنفذ إضافة لم تطلبها منها.
وفي جرد شامل، يلزمك كذلك التعامل مع التكرار. إذا أظهرت آلية جمع أكثر من طريق إلى المجموعة نفسها، فالنتيجة ليست مجموعتين منفصلتين. استخدم هوية ثابتة للكائن مثل SID أو GUID وفق الغرض، واحتفظ بالمسارات إن كانت مهمة للتحليل. لا تجعل عدد الصفوف بعد الدمج مساويًا لعدد المجموعات من غير إزالة التكرار على أساس واضح.
تقدر تستخدم في ملاحظاتك سجلًا صغيرًا لكل علاقة: المستخدم، المجموعة، نوع العلاقة، المصدر، والوقت. نوع العلاقة هنا قد يكون مباشرة أو أساسية أو مسارًا متداخلًا مثبتًا. بهذه الطريقة يستطيع القارئ مراجعة سبب إدراج المجموعة، خصوصًا لو اختلفت أداة الجرد عن واجهة تطبيق قديم.
وهناك حد آخر مهم: قائمة الدليل الحالية لا تضمن أن كل جلسة قائمة تحمل السياق نفسه في اللحظة نفسها. إذا كان السؤال عن أثر تعديل حدث للتو، فاجمع وقت الجلسة والتغيير وقياس المورد. هذا ليس سببًا لإضافة كل الاحتمالات إلى التقرير، بل لتجنب مقارنة لقطة دليل حديثة بسلوك جلسة لم تُجدّد بعد وكأنهما قياس واحد.
حالة تطبيق LDAP: المستخدم موجود لكن الدور ما ظهر
خلنا نفترض تطبيق تدريب يقرأ مستخدمي OU محددة، ثم يمنح دور Reader إذا وجد مجموعة Lab-Readers داخل memberOf. مسؤول التطبيق يقول إن amal عضو في المجموعة المقصودة، لكن التطبيق لا يمنحها الدور. هنا نحتاج معرفة كيف جرى تمثيل العضوية وكيف يطبق البرنامج شروطه، بدل محاولة تغيير كلمة المرور أو إضافة الحساب إلى مجموعة أوسع.
لو كانت Lab-Readers هي المجموعة الأساسية للحساب في هذا اللاب، فقد لا تظهر ضمن memberOf كما يتوقع الكود. بعد حل primaryGroupID تستطيع إثبات العلاقة في الدليل، ثم مراجعة طريقة احتساب العضوية في التطبيق. هذا سيناريو تعليمي لتوضيح الفارق؛ لا يعني أن أي تطبيق LDAP يعالج المجموعات الأساسية بالطريقة نفسها، أو أن تغييرها هو الحل المناسب في كل تصميم.
تأكد من تعريف دور التطبيق قبل التوصية. ربما التصميم يشترط عضوية مباشرة في مجموعة بعينها ويستبعد طرقًا أخرى عمدًا. وربما الهدف كل أعضاء المجموعة بما في ذلك العلاقة الأساسية. إذا كان التعريف الثاني هو المعتمد، فالمشكلة أن التنفيذ لا يمثل الشرط المعلن بدقة. أما إذا كان الأول مقصودًا، فهناك فجوة بين توقع المسؤول وسلوك التصميم، وتحتاج حسم السياسة أولًا.
في التحقق، اقرأ المجموعة من مصدر معروف، ثم راجع سجل التطبيق أو البيانات التي استخدمها لقرار الدور إن توفرت. لا تكتفِ بصورة إعداد داخل الدومين مع صورة رفض في التطبيق؛ اربطهما بالهوية نفسها وبالاستعلام الذي اعتمد عليه التطبيق. راجع أيضًا التخزين المؤقت إن كان موثقًا في المنتج، لكن لا تنسب المشكلة إليه من غير دليل.
معالجة هذه الحالة قد تكون في توضيح شروط العضوية أو تعديل طريقة الجلب وفق تصميم المنتج. لا نختار تلقائيًا تعديل primaryGroupID للمستخدم حتى يصير العرض أسهل، لأن خصائص الهوية قد تستخدمها أنظمة أخرى. المسؤول عن الهوية ومالك التطبيق ينبغي أن يتفقا على التمثيل المطلوب قبل أي تغيير.
كيف تمنع جردًا ناقصًا من التحول إلى قرار خاطئ؟
الجرد الناقص يصير خطرًا عندما تستخدمه للإجابة عن سؤال أكبر من تغطيته. مثلًا، قد يخرج تقرير يقول «هذه المجموعة بلا مستخدمين» لأنه قرأ member فقط، بينما توجد حسابات جعلتها مجموعتها الأساسية. أو يقول «الحساب ليس عضوًا» لأنه بحث عن اسم المجموعة في memberOf وحدها. العبارة تتجاوز مصدر البيانات، حتى لو كان الأمر الذي جمعته قد عمل بنجاح.
ابدأ بتعريف المطلوب: هل تريد جرد أعضاء مجموعة أمان معينة بحسب كل العلاقات ذات الصلة في دومين محدد؟ أم تريد تدقيق خاصية مباشرة تستخدمها خدمة؟ الأول يحتاج تغطية أوسع، والثاني قد يقبل نطاقًا ضيقًا بشرط ذكره. التصريح بتعريف الجرد يوفر وقتًا أكثر من محاولة إصلاح تقرير بعد إرسال قرارات إزالة وصول مبنية عليه.
اختبر أداة الجرد على حالات تدريب تعرف حقيقتها: عضوية مباشرة فقط، ومجموعة أساسية، ومسار متداخل، وحساب لا ينبغي أن يدخل النتيجة. لا تجعل صحة العدد العام معيارك الوحيد؛ العدد قد يتساوى بالصدفة رغم وجود اسم زائد وآخر مفقود. افحص هوية كل حالة وسبب إدراجها أو استبعادها.
إذا كان التقرير متعدد الدومينات، فلا تدمج المستخدمين بحسب SamAccountName وحده. قد توجد amal في دومينين، كما قد تتكرر أرقام RID. أضف هوية الدومين ومعرفات الكائنات، ثم وضح أين جرى حل كل مجموعة. هذه التفاصيل مهمة عند مراجعة الثقة أو تطبيق يخدم أكثر من نطاق هويات.
وأخيرًا، لا تستخدم جرد الدليل كبديل كامل لاختبار تفويض المورد. حتى بعد اكتمال العضويات، تحتاج معرفة أذونات المورد وقيود التطبيق والسياق الذي ينفذ العملية. الجرد يشرح علاقة، والوصول يثبت أثر تلك العلاقة في عملية محددة. الفصل بينهما يحميك من تقرير مرتب بالأسماء لكنه لا يجيب السؤال الأمني الذي بدأ العمل من أجله.
اكتب الفرق في التقرير بلغة يفهمها الفريق
لو وجدت خللًا في تطبيق يعتمد على memberOf وحدها بينما سياسته تشمل المجموعة الأساسية، اكتب المشكلة حول قرار الوصول: التطبيق يستبعد حسابًا من الدور رغم تحقق تعريف العضوية المعتمد، لأن طريقة الجلب لا تشمل العلاقة الممثلة في primaryGroupID. ثم ضع مثال حساب تدريبي ومعرف المجموعة وطريقة القراءة ونتيجة التطبيق. بهذا الوصف يعرف المطور أين يراجع، ويعرف مسؤول الهوية لماذا لا نحتاج توسيع أذونات المستخدم.
لا تصنفها تلقائيًا تجاوز صلاحيات أو تسريبًا. بعض الحالات تمنع وصولًا مشروعًا، وبعض طرق الاحتساب الخاطئة قد تمنح دورًا لغير الجمهور المطلوب. أثبت اتجاه الأثر في الحالة التي راجعتها. إذا لم تختبر إلا الاستبعاد، قل ذلك بدل إضافة أثر سماح غير مقاس. وإذا كانت المشكلة في تقرير جرد فقط، سمّها فجوة تغطية بيانات ووضح القرار الذي يمكن أن يتأثر بها.
معيار إعادة الاختبار ينبغي أن يغطي حالة العلاقة الأساسية، والعضوية التي يدعمها التصميم، وحسابًا خارج المجموعة. الهدف أن تصير النتيجة مطابقة لسياسة الوصول، لا أن تختفي رسالة خطأ أو يزيد عدد المستخدمين المعروضين. احتفظ بتعريف العضوية المكتوب، حتى لا تعود المشكلة عندما يعدل شخص آخر طريقة الاستعلام.
وعند مراجعة أدوات الشهادات، درّب نفسك على السؤال نفسه: ماذا جمع هذا الأمر تحديدًا، وما الذي لم يجمعه؟ فهم primaryGroupID يعطيك مثالًا ممتازًا على أن نجاح الاستعلام لا يساوي اكتمال تفسيره. ما تحتاج عشر أدوات كي تتحسن هنا؛ تحتاج ربط الخاصية بالهوية الصحيحة ثم بالمورد الذي يهمك.
المراجع للتحقق والتوسع: تعريف primaryGroupID والفرق عن memberOf وmember: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-ada3/e12954a4-6865-4432-94e6-00c310ca87c0
تعريف خاصية memberOf: https://learn.microsoft.com/en-us/windows/win32/adschema/a-memberof
قراءة المجموعة عبر SID باستخدام Get-ADGroup: https://learn.microsoft.com/en-us/powershell/module/activedirectory/get-adgroup?view=windowsserver2025-ps