locked
Problems binding users to objects that are not in thier DN path RRS feed

  • Question

  • User-243637605 posted

    I'm having problems binding to objects that aren't in the user's DN path via .Net code.

    1)  I can bind with the user to ldp and browse any objects in the AD tree without any problems

    2) If I bind to the root (i'm supplying username and pasword, not integrated) I can use directory sercher and the filter to find the objects that I want and show their properties. 

    2)  If I change the path to an OU at any level that is part of the bound user's DN, I can filter and find objects that exist in that OU.

    3) I can not change to a path or directly bind to an OU that is not a part of the user's DN.  I get a null object.

    The purpose for this is that the users are logically grouped into OU's, and a I would like to provide the user the ability to bind to an OU that isn't in their DN and list out their members.  This would seem to be a common thing, and I can browse the whole tree when connecting with LDP so it's a mystery to me.  If there is a security issue in play, why can I view the objects when bound from the root or from LDP. 

     

    Below are the relevant parts of the code. Any ideas?

     

    // bind the user

    DirectoryEntry searchRoot = BindToAD( ls_username, ls_password);

    // filter by current OU if desired

     

    // Set the seearch path to the bound user's OU,  if set to an OU outisde of their path it sets it to null.

    searchRoot.Path = BuildPathforUser(searchRoot);  

    Trace.Write("path is changing to=" + searchRoot.Path);

     

    // get a datatable of Active Directory users in the OU search path

    DataTable usersDataTable = GetUsers(searchRoot);

    GridView1.DataSource = usersDataTable;

    GridView1.DataBind();

     

    private DirectoryEntry BindToAD( string username, string password)

    {

    string domain = ConfigurationManager.AppSettings["ADDomain"].ToString();

    string server = ConfigurationManager.AppSettings["ADServer"].ToString();

    string loginName = string.Format(CultureInfo.CurrentCulture, "{0}\\{1}", domain, username);

    DirectoryEntry searchRoot = new DirectoryEntry(string.Format("LDAP://{0}", server), loginName, password, AuthenticationTypes.Secure);

    return searchRoot;

    }

     

    private string BuildPathforUser(DirectoryEntry searchRoot)

    {

    string username = ( (string[]) searchRoot.Username.Split('\\') )[1];

    Trace.Write("username = " + username);

    DirectoryEntry objCurrentUser;

    DirectorySearcher searcher = new DirectorySearcher(searchRoot);

    searcher.Filter = "(&" + String.Format("(sAMAccountName={0})", username) + "(objectClass=user) )";

    searcher.SearchScope = SearchScope.Subtree;

    searcher.CacheResults = false;

    searcher.PropertiesToLoad.Add("distinguishedName");

    StringBuilder returnValue = new StringBuilder();

    foreach (SearchResult res in searcher.FindAll())

    {

    // set our instance variable and return

    objCurrentUser = res.GetDirectoryEntry();

    string value = objCurrentUser.Properties["distinguishedName"][0].ToString();

    string[] array = value.Split(',');

    foreach (string entry in array)

    {

    string[] valuePair = entry.Split('=');

    if (valuePair[0] != "CN")

    {

    if (returnValue.Length > 1)

    {

    returnValue.Append(", ");

    }

    returnValue.Append(entry);

    }

    }

    }

    private DataTable GetUsers(DirectoryEntry searchRoot)

    {

    DirectorySearcher searcher = new DirectorySearcher(searchRoot);

    searcher.Filter = BuildFilter();

    searcher.SizeLimit = 500;

    searcher.SearchScope = SearchScope.Subtree;

    searcher.CacheResults = false;

    searcher.Tombstone = false;

    searcher.Sort = new SortOption("sn", System.DirectoryServices.SortDirection.Descending);

    searcher.PropertiesToLoad.Add("objectGuid");

    searcher.PropertiesToLoad.Add("sn");

    searcher.PropertiesToLoad.Add("givenName");

    searcher.PropertiesToLoad.Add("distinguishedName");

    SearchResultCollection results = searcher.FindAll();

    Trace.Write("GetUserTest Results count = " + results.Count);

    // build a datatable with guid, lastname, firstname

    DataTable dataTable = new DataTable("ADResults");

    dataTable.Columns.Add("guid", Type.GetType("System.Guid") );

    dataTable.Columns.Add("name_last", Type.GetType("System.String"));

    dataTable.Columns.Add("name_first", Type.GetType("System.String"));

    dataTable.Columns.Add("DN", Type.GetType("System.String"));

    foreach (SearchResult result in results)

    {

    // get the property into a guid that we can use

    byte[] byteGuid = (byte[])result.Properties["objectGuid"][0];

    System.Guid guid = new System.Guid(byteGuid);

    DataRow row = dataTable.NewRow();

    row["guid"] = guid;

    // last name

    if ( result.Properties.Contains("sn") )

    {

    row["name_last"] = (string)result.Properties["sn"][0];

    }

    // first name

    if (result.Properties.Contains("givenname"))

    {

    row["name_first"] = (string)result.Properties["givenname"][0];

    }

    // distinguished name

    if (result.Properties.Contains("distinguishedName"))

    {

    row["DN"] = (string)result.Properties["distinguishedName"][0];

    }

    dataTable.Rows.Add(row);

    }

    return dataTable;

    }

     

    private string BuildFilter()

    {

    StringBuilder returnValue = new StringBuilder("(&(objectClass=user) (objectclass=person)(objectclass=organizationalPerson)");

    // active users only

    if (cbxActiveOnly.Checked == true)

    {

    returnValue.Append("(!userAccountControl:1.2.840.113556.1.4.803:=2)");

    }

    // First name

    if (tbxFirstName.Text.Length > 0)

    {

    returnValue.Append( string.Format("(givenname={0}*)", tbxFirstName.Text) );

    }

    // Last name

    if (tbxLastName.Text.Length > 0)

    {

    returnValue.Append(string.Format("(sn={0}*)", tbxLastName.Text));

    }

    // username

    if (tbxUserName.Text.Length > 0)

    {

    returnValue.Append( string.Format("(sAMAccountName={0})", tbxUserName.Text) );

    }

    returnValue.Append(")");

    return returnValue.ToString();

    }

     

    Thursday, June 29, 2006 4:59 PM

All replies

  • User1354132231 posted
    It sounds to me as if you are saying that you can only browse the directory using a SearchRoot that is a parent to your objects.  That makes sense as it describes exactly how LDAP searching works.  If you change your parent (SearchRoot) to some other portion of the directory, you should be able to find other objects.  If you set your SearchRoot to the root of the partition, you should be able to search the entire hierarchy.

    Wednesday, July 5, 2006 9:50 AM
  • User-243637605 posted

    My goal is to be able to bind to an OU that the bound user is not a member of, allowing the searchRoot to only show objects below it, and then apply the filter at that point and list out persons that are a part of that OU.

    I can only bind at the root, or somewhere in the hierarchy for the person that I'm trying to bind with.

    When I bind to the root, I can list out properties on objects in the whole directory, but I want to start my search on a specific OU that the bound user isn't a member of.

    I am able to bind to a portion of the directory that is in the user's OU hiearchy and start my search there, but it won't let me attach to someone else's OU.

    When I try to bind to or set my searchroot path to another portion of the directory outside of the user's hiearchy, I get a null object reference. 

    I would think that ldp is doing something simular to setting the searchroot to a spechfic path when I go thru the directory tree and have it show objects that are in OU's outside of the path of the person that I bound with. 

    Thanks,

    Michael H.

    Wednesday, July 5, 2006 10:40 AM
  • User1354132231 posted
    I still might be missing what you are asking.  What exactly do you mean by "OU that the bound user is not a member of".  I think the disconnect is that the bound user might or might not represent the object that you are bound to - so I am unsure of what you mean.  Think of it this way:

    DirectoryEntry foo = new DirectoryEntry(
        "LDAP://OU=foo,DC=mydomain,DC=com",
        "mcfooman@mydomain.com",
        "password",
        AuthenticationTypes.Secure
        );

    In this case, the "bound" object is the OU called "foo", whilst the security context is of mcfooman's.  We might called mcfooman the "bound user", but that can be confusing since we did not bind to a user, but instead an OU and the underlying connection has mcfooman's security context (that's it).

    Let's suppose that mcfooman happens to be located in the same particular OU (e.g. CN=mcfooman,OU=foo,DC=mydomain,DC=com).  This has no relevance whatsoever to what can be searched (i.e. it's a fun fact, but has no bearing on anything).  Unless permissions to other OUs are actually removed, mcfooman will be able to still search anywhere:

    DirectoryEntry notfoo = new DirectoryEntry(
        "LDAP://OU=notfoo,DC=mydomain,DC=com",
        "mcfooman@mydomain.com",
        "password",
        AuthenticationTypes.Secure
        );


    So, mcfooman in this case has bound to a different OU and it free to base his search there just fine.

    My theory at this point is that you just have a general programming error on an uninitialized variable that is leading to this "null reference" error.  There is no LDAP explanation for any of it.  Try making a short repro case that demonstrates the error and I bet it will go away (no fancy datagrids or extraneous fluff).  In the course of making it, I think you will find the issue.  If not, post your barebones repro case here and I will try to repro it for you as well.

    Wednesday, July 5, 2006 11:23 AM
  • User-243637605 posted

    I'm glad to here that what I expect to happen should happen. Here is a simplified example of what is actually happeining:

     

                string bindLocation = "LDAP://ad.MyDomain.org:389/OU=Center A,OU=Centers,DC=ad,DC=MyDomain,DC=org";  // this is the OU for C.Test and it can be bound to
                //string bindLocation = "LDAP://ad.MyDomain.org:389/OU=Center B,OU=Centers,DC=ad,DC=MyDomain,DC=org";  // this one is not his OU and fails when binding
                DirectoryEntry searchRoot = new DirectoryEntry(bindLocation, "MyFriendlyName.org\\C.Test", "pass1234", AuthenticationTypes.Secure);
                Object native = searchRoot.NativeObject;  // this forces the authentication to happen

     In this example, binding to his OU works, but binding to the other one doesn't.  Here is the error that I get:

    The specified directory service attribute or value does not exist.

    Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.Runtime.InteropServices.COMException: The specified directory service attribute or value does not exist.

     

    Any ideas?

    Michael H.

    Wednesday, July 5, 2006 3:35 PM
  • User1354132231 posted
    What happens with this?:

    string adsPath = "LDAP://ad.mydomain.org/OU=Centers,DC=ad,DC=mydomain,DC=org";

    DirectoryEntry parent = new DirectoryEntry(
        adsPath,
        "mydomain\\user",
        "password",
        AuthenticationTypes.Secure
        );

    using (parent)
    using (DirectoryEntry child = parent.Children.Find("OU=Center B"))
    {
        Console.WriteLine("Found {0}", child.Name);
    }


    I just want to make sure you have permission to the OU and that it actually exists (no spelling errors).
    Wednesday, July 5, 2006 4:15 PM
  • User-243637605 posted

    it fails with the following error:

     

    The specified directory service attribute or value does not exist. (Exception from HRESULT: 0x8007200A)

    Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.Runtime.InteropServices.COMException: The specified directory service attribute or value does not exist. (Exception from HRESULT: 0x8007200A)

     

    If I change it to find "Center A" then it works and shows the OU name.

    Michael H.

    Wednesday, July 5, 2006 4:39 PM
  • User1354132231 posted
    Well, there is the obvious and the not-so-obvious issues.  First, you have to confirm with 100% certainty that the OU actually exists.  Usually this means checking it in the MMC or LDP.exe.  If I were to view the OU=Centers node, I should see 2 children called Center A and Center B under it.  That is the obvious one.

    Now, for the less obvious one - has anyone removed permissions or put explicit DENY entries on the OU objects?  Using AD users and computers (ADUC) MMC, right click and check security tab under Properties.  Check Effective Permission on Advanced View of ACL for your user.  If you see that they have read access then we need to keep looking.  If you see they do not have read access then you are out of luck short of changing the permissions.



    Wednesday, July 5, 2006 4:52 PM
  • User-243637605 posted

    I worked with the server guys a little and it's definately a permissions issue.  They added the test account to a group with more priviliges and it now works.  This afternoon I hope to work with them a little bit to narrow down specifically what permission does the trick.  If and when I find out I'll post it here.  The wierd part it is that I've been able to use LDP and BaseDN to different OU's just fine.

    Thank you dunnry for all your time working with me on this!

    Michael H.

    Thursday, July 6, 2006 11:40 AM
  • User1354132231 posted
    Most likely you are using your own credentials in LDP.exe.  If you CTRL-B and set credentials explicitly to your test account, you would get the same behavior as your app.
    Thursday, July 6, 2006 11:52 AM