【问题标题】:How to overcome the "The member's SID could not be resolved" error when checking if user is a member of group?检查用户是否为组成员时,如何克服“无法解析成员的 SID”错误?
【发布时间】:2012-10-24 13:36:21
【问题描述】:

我们有一个流程需要检查特定用户是否是本地管理员组的成员。

检查代码如下所示:

using (PrincipalContext context = new PrincipalContext(ContextType.Machine, null))
{
    UserPrincipal user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, sUserName);
    if (user != null)
    {
         SecurityIdentifier adminsGroupSID = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
         GroupPrincipal group = GroupPrincipal.FindByIdentity(context, IdentityType.Sid, adminsGroupSID.Value);
         if (group != null)
         {
             if (user.IsMemberOf(group))
                 return 0;
         }
    }
}

当组的帐户(例如域帐户)被删除时,我们会收到 PrincipalOperationException 和消息“枚举组成员身份时发生错误 (1332)。无法解析成员的 SID。”

有没有办法克服这个问题: a) 从组中手动删除孤立的 SID b) 不忽略它?

谢谢

【问题讨论】:

    标签: c# active-directory


    【解决方案1】:

    这在很大程度上基于我在 http://www.seirer.net/blog/2013/9/12/how-to-deal-with-localized-or-renamed-administrators-in-net 上的发现,作者是 Michael Seirer。他试图获取本地管理员帐户的 SID,而我们只需要该组中的名称。错误“无法解析成员的 SID”的原因。是因为 Active Directory 中不再识别某些帐户 - 可能是指向已删除用户帐户的遗物。您可以按照微软所说的去做,然后删除它们,并希望您的应用程序永远不会再次崩溃(尽管它会,下次删除该管理员组中的帐户时),或者使用我稍微修改的这段代码永久解决它迈克。

    using System.DirectoryServices;
    using System.Collections;
    using System.Runtime.InteropServices;
    
    [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
    static extern bool ConvertSidToStringSid(IntPtr pSid, out string strSid);
    
    private static string GetTextualSID(DirectoryEntry objGroup)
    {
        string sSID = string.Empty;
        byte[] SID = objGroup.Properties["objectSID"].Value as byte[];
        IntPtr sidPtr = Marshal.AllocHGlobal(SID.Length);
        sSID = "";
        System.Runtime.InteropServices.Marshal.Copy(SID, 0, sidPtr, SID.Length);
        ConvertSidToStringSid((IntPtr)sidPtr, out sSID);
        System.Runtime.InteropServices.Marshal.FreeHGlobal(sidPtr);
        return sSID; 
    }
    
    
    public static List<string> GetLocalAdministratorsNames()
    {
        List<string> admins = new List<string>();
        DirectoryEntry localMachine = new DirectoryEntry("WinNT://" + Environment.MachineName);
        string adminsSID = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).ToString();
    
        string localizedAdmin = new System.Security.Principal.SecurityIdentifier(adminsSID).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
    
        localizedAdmin = localizedAdmin.Replace(@"BUILTIN\", "");
    
        DirectoryEntry admGroup = localMachine.Children.Find(localizedAdmin, "group");
        object adminmembers = admGroup.Invoke("members", null);
    
        DirectoryEntry userGroup = localMachine.Children.Find("users", "group");
        object usermembers = userGroup.Invoke("members", null);
    
        //Retrieve each user name.
        foreach (object groupMember in (IEnumerable)adminmembers)
        {
            DirectoryEntry member = new DirectoryEntry(groupMember);
    
            string sidAsText = GetTextualSID(member);
            admins.Add(member.Name);            
        }
        return admins;
    }
    

    它将返回本地计算机上本地管理员组成员的List&lt;string&gt;。如果您不想要本地计算机,您甚至可以将 Environment.MachineName 更改为您域中的任何计算机名称。

    然后您可以迭代列表以查看它们是否在其中:

    private static bool isAdmin(string user)
    {
        //string user = @"DOMAIN\doej";
        user = user.Split(@'\')[1];
    
        List<string> admins = GetLocalAdministratorsNames();
        foreach (string s in admins)
        {
            if (s == user)
                return true; // admin found
        }
        return false;  // not an admin
    }
    

    【讨论】:

      【解决方案2】:

      大约十年后,即使在 .NET 4.x 和 5.x 中,这仍然是一个问题。解决此错误的另一种方法是从根本上解构 foreach 语句背后的代码并制作自己的搜索列表。您需要获取 Enumerator,然后在 Try/Catch 中调用 MoveNext。我最初担心如果它抛出异常,它会不会移动到下一个,但它确实如此,所以这行得通。它不漂亮,但我已经对其进行了测试,它对我有用。

      PrincipalContext principalContext;
      GroupPrincipal groupPrincipal;
      UserPrincipal userPrincipal;
      bool hasItem;
      
      // `members` is the new list that can be searched without errors.
      List<Principal> members = new List<Principal>();
      
      using (principalContext = new PrincipalContext(ContextType.Machine))
      {
          SecurityIdentifier adminsGroupSID = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
          using (groupPrincipal = GroupPrincipal.FindByIdentity(principalContext, IdentityType.Sid, adminsGroupSID.Value))
          {
              /*
               * We will get our Enumerator. At this point, we are positioned before 
               * the current item, so we have to call MoveNext to get a valid Current
               */
              var e = groupPrincipal.Members.GetEnumerator();
              hasItem = false;
      
              do
              {
                  try
                  {
                      /*
                       * Try and move next. If it failes, it will be caught by the catch
                       * and ignored. At which point, we can try and call MoveNext again
                       * and get to the next one.
                       */
                      hasItem = e.MoveNext();
                      if (hasItem)
                      {
                          members.Add(e.Current);
                      }
                  }
                  catch (PrincipalOperationException)
                  {
                      // We don't care about doing anything here--we just want to ignore the error
                  }
              } while (hasItem);
          }
      }
      

      【讨论】:

      • 赞 到目前为止,如果您需要枚举域对象被删除的域中的本地组成员,这是唯一的解决方案。此外,最好添加第二个异常捕获 (System.Runtime.InteropServices.COMException),如果您在删除域用户/组成员后立即枚举本地组成员,则会发生这种情况。
      【解决方案3】:

      避免错误的一种方法是另辟蹊径。 不要检查用户是否是组的成员,而是首先检索所有组并检查目标组的列表。一个缺点:速度较慢....

      var groups = UserPrincipal.Current.GetAuthorizationGroups();
      var found = groups.FirstOrDefault(principal => principal.Name == "Administrators");
      var isMemberOfAdminGroup = found != null;
      

      感谢arus 的帮助:)

      【讨论】:

      • 如果您想检查机器上的本地管理员组,这将无济于事。这会查询 AD 中的 Administrators 组 - 让我们清楚。
      【解决方案4】:
      public static bool UserHasLocalAdminPrivledges(this UserPrincipal up)
      {
         SecurityIdentifier id = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
         return up.GetAuthorizationGroups().Any(g => g.Sid == id)
      }
      

      【讨论】:

      • GetAuthorizationGroups() 反对 AD,而不是本地计算机或另一台远程计算机。它以广告为中心,与当地团体无关。也就是说,使用内置组的 SID 来查看该组是否在 AD 管理员组中有点奇怪,但可能会起作用。我没有对此进行测试,但它有点先绕着谷仓转,而不是直接穿过门。
      【解决方案5】:

      共有三种可能的解决方案(均未经测试,对所有类型的域组使用最后一种):
      1)加载组并自己枚举成员
      2)加载组的底层对象,使用properties["Members"],这是一个SID列表。
      3) 使用用户的 GetAuthorizationGroups() (这也将使用您的非直接组,服务帐户最终必须是“Windows 授权组”和“PreWindows 2000 兼容...”的成员)并使用组列表查找您的管理员组。

      【讨论】:

      • 感谢您的回复。我会检查可以做什么。另外,您可能会推荐一些类似的解决方案来将用户添加到组中?当我们做 group.Members.Add(user) 时同样的问题。
      • 好吧,底层对象总是可以工作的,但不如 AccountManagement-Namespace 舒服。但似乎 AccountManagement-Namespace 存在一些缺陷,需要解决方法。您还可以为组编写修复功能...
      • @TGlatzer:我该如何枚举?我在循环时遇到错误。 stackoverflow.com/questions/35913995/…
      • 1.无法列举。如 Rajesh 所示,它给出了一个例外 - 微软甚至不关心修复,因为他们说解决方法是删除死帐户。 2. GetUnderlyingObject() 在加载DirectoryEntry de = (DirectoryEntry)groupPrincipal.GetUnderlyingObject() 时没有提供任何值。 3. GetAuthorizationGroups() 与 AD 对抗,而不是本地计算机或另一台远程计算机。它以广告为中心,与本地团体无关。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多