What permission do I need to add a user to a group? (C#)

  • Thread starter Thread starter Brian Hampson
  • Start date Start date
B

Brian Hampson

I am trying to determine all the groups which the current user has
permissions to add a member.

Here's my code:

foreach (System.DirectoryServices.SearchResult ADSearchres in
ADSearch.FindAll())
{

//ActiveDs.ADSearchres.Properties["ntSecurityDescriptor"]
ADChild = ADSearchres.GetDirectoryEntry();
foreach (ActiveDirectoryAccessRule ace in
ADChild.ObjectSecurity.GetAccessRules(true, true,
typeof(SecurityIdentifier)))
{
if
(System.Security.Principal.WindowsIdentity.GetCurrent().Groups.Contains(ace.IdentityReference))
if (ace.AccessControlType ==
AccessControlType.Allow)
{
ActiveDirectoryRights rights =
ace.ActiveDirectoryRights;
if ((rights &
ActiveDirectoryRights.WriteProperty) ==
ActiveDirectoryRights.WriteProperty)
{
GroupListAddItem(ADChild);
break;
}

}
}

}

I had thougth that "ActiveDirectoryRights.WriteProperty" would have
been the one I wanted. When I do this with a limited access user,
though, it still lists all my groups :(

Suggestions? Help?

Brian Hampson
System Administrator, North America
ALS Laboratory Group, Environmental Division
 
Do you need to look this up for an arbitrary user, or the current user who
is binding to the directory? If the former, there is a much easier way to
accomplish this. AD (and ADAM) have a constructed attribute called
"allowedAttributesEffective" which returns an array of strings listing all
the attributes on a given object that can be modified by the security
context that executed the search.

Essentially, you would just add "allowedAttributesEffective" to your
PropertiesToLoad and then read the result of it for each object in your
search results. If it contains the string "member", then you can change
group memberships.

If you must do this via the security descriptor, it is both slower and more
complex. A user might have rights to modify the membership via a variety of
different types of ACEs such as a property write ACE, a control access right
ACE for the "member" property set, a Full Control ACE, etc. It will be much
harder to get this right. :) If possible, I recommend using the
constructed attribute.

We have a few more details on this type of stuff in our book if you are
interested (ch 7 and 8).

Joe K.
 
Bingo! Thanks Joe! It still takes a long time to enumerate all the
groups for people that are Domain Admins, but it's quick and nice for
our HelpDesk staff now :)

Cheers!
 
Glad that helped. It is a useful trick. :) There is also
allowedChildClassesEffective if you want to know which objects a user can
create as a child of another object. The only thing you can't figure out
easily this way is which objects you can delete. :)

There isn't really any point of running this for a DA. DAs generally are
given full control over all objects and can typically take ownership of any
object that has that ACE removed, so there is no real way to prevent a DA
from modifying just about anything. You should always assume that DAs can
do anything they want to any object in the forest.

Joe K.
 
Here's the gist of what I've created (some might call it a monster)

Our environment had a number of domain admins (which we are now working
on pruning) People would create users and not put in alot of the
information ("Too much work...") so I've created a front end app that
will:

a) create the user
b) create a home directory on a server of choice (based on which OU
they are in)
c) create a share for the home directory from that server.
d) create an exchange mailbox on a designated mailserver (based on OU)
and email address for the user.
e) place the user in a default group based on which OU they are in.
f) give the creator a list of other groups into which they have
permission to add the user. (This was the part that I needed this last
bit of help for)

This was all fine with DA's. Then we created a "HelpDesks" group.
Suddenly things like D$ weren't available, remote WMI, Distributed COM,
reading the GC for email addresses, all fell apart. The last few weeks
I have been rewriting the code to handle the lower privs. When a
"HelpDesk" user creates a new user, they are now only presented with
the groups that they can "legally" add a user, not all :)

Now, like I said, my only problem is that my DA's get a list of about
500+ groups, and adding that process takes upwards of 60 seconds.


Here's what I'm doing for that:

ADNode = new System.DirectoryServices.DirectoryEntry("LDAP://" +
domainstr);
System.DirectoryServices.DirectorySearcher ADSearch = new
System.DirectoryServices.DirectorySearcher(ADNode);
ADSearch.CacheResults = true;
ADSearch.PropertiesToLoad.Add("cn");

ADSearch.PropertiesToLoad.Add("allowedAttributesEffective");
ADSearch.Filter = "(&(objectCategory=group)(!cn=domain
computers)(!cn=domain controllers))";
GroupList.Sorted = true;
foreach (System.DirectoryServices.SearchResult ADSearchres
in ADSearch.FindAll())
{

//ActiveDs.ADSearchres.Properties["ntSecurityDescriptor"]
if
(ADSearchres.Properties["allowedAttributesEffective"].Contains("member"))
{
ADChild = ADSearchres.GetDirectoryEntry();
GroupListAddItem(ADChild);
if (ADChild.Name == "CN=G Env Env")
GroupListAddSelectedItems(ADChild);
}
}

Note, the calls to "functions" are delegates for background thread
update via invoke. Am I doing something horribly wrong?
 
I was really just trying to suggest that checking permissions for DA's
probably isn't that helpful. If you could, you might consider determining
if the user is a DA and not filtering your list if they are. You can do
this by getting the user's tokenGroups attribute and seeing if the DA group
SID is in there. You can also get this directly from the user's
WindowsIdentity.Groups property (which is probably easier).

In our environment with >150K users and ~80K groups, this performance hit
would really work. :) Neither would showing someone a list of 80K groups
though. :)

It is probably best to avoid doing queries that return
allowedAttributesEffective against many many objects, as that result is
fairly expensive for the DC to produce. However, if you are trying to trim
a list, it is hard not to use it.

A way to get much better perf would be to add your own attribute that
contains the DNs of groups that are allowed to use that particular group and
query against that. Then, of course, you need a way to keep the permissions
delegations in sync with the contents of that attribute, but your UI will be
much snappier and your DCs will be lest beaten up servicing that query.

It is weird that your helpdesk users can't search the GC. That should work
fine. Authenticated Users can typically query the GC, just like the
standard LDAP ports.

The rest of the stuff doesn't surprise me though. The way this kind of
thing is often implemented is using a trusted subsystem design, where your
client calls some server process that does have the credentials to perform
the required operations, but then provides its own authorization layer to
implement a role-based security mechanism. The role-based security
mechanism is often a PITA to build and test, but it can gain you a bunch of
important functionality. Some companies have whole products based on
providing role-based delegation of operations against AD, so you could also
consider buying something. :)

Best of luck with your solution. It sounds like you've made a lot of
progress. We have a book out there that might have helped you with some of
the stuff you are doing if you are interested.

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
Brian Hampson said:
Here's the gist of what I've created (some might call it a monster)

Our environment had a number of domain admins (which we are now working
on pruning) People would create users and not put in alot of the
information ("Too much work...") so I've created a front end app that
will:

a) create the user
b) create a home directory on a server of choice (based on which OU
they are in)
c) create a share for the home directory from that server.
d) create an exchange mailbox on a designated mailserver (based on OU)
and email address for the user.
e) place the user in a default group based on which OU they are in.
f) give the creator a list of other groups into which they have
permission to add the user. (This was the part that I needed this last
bit of help for)

This was all fine with DA's. Then we created a "HelpDesks" group.
Suddenly things like D$ weren't available, remote WMI, Distributed COM,
reading the GC for email addresses, all fell apart. The last few weeks
I have been rewriting the code to handle the lower privs. When a
"HelpDesk" user creates a new user, they are now only presented with
the groups that they can "legally" add a user, not all :)

Now, like I said, my only problem is that my DA's get a list of about
500+ groups, and adding that process takes upwards of 60 seconds.


Here's what I'm doing for that:

ADNode = new System.DirectoryServices.DirectoryEntry("LDAP://" +
domainstr);
System.DirectoryServices.DirectorySearcher ADSearch = new
System.DirectoryServices.DirectorySearcher(ADNode);
ADSearch.CacheResults = true;
ADSearch.PropertiesToLoad.Add("cn");

ADSearch.PropertiesToLoad.Add("allowedAttributesEffective");
ADSearch.Filter = "(&(objectCategory=group)(!cn=domain
computers)(!cn=domain controllers))";
GroupList.Sorted = true;
foreach (System.DirectoryServices.SearchResult ADSearchres
in ADSearch.FindAll())
{

//ActiveDs.ADSearchres.Properties["ntSecurityDescriptor"]
if
(ADSearchres.Properties["allowedAttributesEffective"].Contains("member"))
{
ADChild = ADSearchres.GetDirectoryEntry();
GroupListAddItem(ADChild);
if (ADChild.Name == "CN=G Env Env")
GroupListAddSelectedItems(ADChild);
}
}

Note, the calls to "functions" are delegates for background thread
update via invoke. Am I doing something horribly wrong?
There isn't really any point of running this for a DA. DAs generally are
given full control over all objects and can typically take ownership of
any
object that has that ACE removed, so there is no real way to prevent a DA
from modifying just about anything. You should always assume that DAs
can
do anything they want to any object in the forest.
 
Thanks Joe. I really appreciate all the tips. I had originally
planned on it being a "quick tool" and it has really grown. The reason
I wrote it is because I wanted to learn some AD programming, and make
my life easier. At least I got one of those two :)

The lines that caused the anonymous GC query were:

CDOEXM.IMailboxStore mailbox;
mailbox = (IMailboxStore)userentry.NativeObject;

mailbox.CreateMailbox(MailServerList.SelectedValue.ToString());
userentry.CommitChanges();

//Give Mailbox some properties, like email address.

userentry.Properties["mailNickname"].Add(iFname.Text + "." +
iLName.Text);

userentry.Properties["mail"].Add(ioemailaddress.Text +
emaildomain.Text);
userentry.CommitChanges();

The subsystem apparently makes the query as anon if you aren't an admin
(or something like that) Anon read access to GC and things were happy.
I was really just trying to suggest that checking permissions for DA's
probably isn't that helpful. If you could, you might consider determining
if the user is a DA and not filtering your list if they are. You can do
this by getting the user's tokenGroups attribute and seeing if the DA group
SID is in there. You can also get this directly from the user's
WindowsIdentity.Groups property (which is probably easier).

In our environment with >150K users and ~80K groups, this performance hit
would really work. :) Neither would showing someone a list of 80K groups
though. :)

It is probably best to avoid doing queries that return
allowedAttributesEffective against many many objects, as that result is
fairly expensive for the DC to produce. However, if you are trying to trim
a list, it is hard not to use it.

A way to get much better perf would be to add your own attribute that
contains the DNs of groups that are allowed to use that particular group and
query against that. Then, of course, you need a way to keep the permissions
delegations in sync with the contents of that attribute, but your UI will be
much snappier and your DCs will be lest beaten up servicing that query.

It is weird that your helpdesk users can't search the GC. That should work
fine. Authenticated Users can typically query the GC, just like the
standard LDAP ports.

The rest of the stuff doesn't surprise me though. The way this kind of
thing is often implemented is using a trusted subsystem design, where your
client calls some server process that does have the credentials to perform
the required operations, but then provides its own authorization layer to
implement a role-based security mechanism. The role-based security
mechanism is often a PITA to build and test, but it can gain you a bunch of
important functionality. Some companies have whole products based on
providing role-based delegation of operations against AD, so you could also
consider buying something. :)

Best of luck with your solution. It sounds like you've made a lot of
progress. We have a book out there that might have helped you with some of
the stuff you are doing if you are interested.

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
Brian Hampson said:
Here's the gist of what I've created (some might call it a monster)

Our environment had a number of domain admins (which we are now working
on pruning) People would create users and not put in alot of the
information ("Too much work...") so I've created a front end app that
will:

a) create the user
b) create a home directory on a server of choice (based on which OU
they are in)
c) create a share for the home directory from that server.
d) create an exchange mailbox on a designated mailserver (based on OU)
and email address for the user.
e) place the user in a default group based on which OU they are in.
f) give the creator a list of other groups into which they have
permission to add the user. (This was the part that I needed this last
bit of help for)

This was all fine with DA's. Then we created a "HelpDesks" group.
Suddenly things like D$ weren't available, remote WMI, Distributed COM,
reading the GC for email addresses, all fell apart. The last few weeks
I have been rewriting the code to handle the lower privs. When a
"HelpDesk" user creates a new user, they are now only presented with
the groups that they can "legally" add a user, not all :)

Now, like I said, my only problem is that my DA's get a list of about
500+ groups, and adding that process takes upwards of 60 seconds.


Here's what I'm doing for that:

ADNode = new System.DirectoryServices.DirectoryEntry("LDAP://" +
domainstr);
System.DirectoryServices.DirectorySearcher ADSearch = new
System.DirectoryServices.DirectorySearcher(ADNode);
ADSearch.CacheResults = true;
ADSearch.PropertiesToLoad.Add("cn");

ADSearch.PropertiesToLoad.Add("allowedAttributesEffective");
ADSearch.Filter = "(&(objectCategory=group)(!cn=domain
computers)(!cn=domain controllers))";
GroupList.Sorted = true;
foreach (System.DirectoryServices.SearchResult ADSearchres
in ADSearch.FindAll())
{

//ActiveDs.ADSearchres.Properties["ntSecurityDescriptor"]
if
(ADSearchres.Properties["allowedAttributesEffective"].Contains("member"))
{
ADChild = ADSearchres.GetDirectoryEntry();
GroupListAddItem(ADChild);
if (ADChild.Name == "CN=G Env Env")
GroupListAddSelectedItems(ADChild);
}
}

Note, the calls to "functions" are delegates for background thread
update via invoke. Am I doing something horribly wrong?
There isn't really any point of running this for a DA. DAs generally are
given full control over all objects and can typically take ownership of
any
object that has that ACE removed, so there is no real way to prevent a DA
from modifying just about anything. You should always assume that DAs
can
do anything they want to any object in the forest.
 
Ah, that is CDOEXM doing that. It is a terrible API and I hate it, but it
is also the only supported method for mailbox-enabling objects in Exchange.
Argh.

But yes, AD in 2003 doesn't allow anonymous queries (GC or LDAP port), so
that would cause a problem. Yet another reason to hate CDOEXM! The crappy
deployment model (install Exchange System Manager?!) and dependence on ADSI
integration (can't be used directly from LDAP) and other weird behaviors
with little value-add increase my loathing as well.

These "little" provisionings apps always do tend to get out of hand, don't
they? :)

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
Brian Hampson said:
Thanks Joe. I really appreciate all the tips. I had originally
planned on it being a "quick tool" and it has really grown. The reason
I wrote it is because I wanted to learn some AD programming, and make
my life easier. At least I got one of those two :)

The lines that caused the anonymous GC query were:

CDOEXM.IMailboxStore mailbox;
mailbox = (IMailboxStore)userentry.NativeObject;

mailbox.CreateMailbox(MailServerList.SelectedValue.ToString());
userentry.CommitChanges();

//Give Mailbox some properties, like email address.

userentry.Properties["mailNickname"].Add(iFname.Text + "." +
iLName.Text);

userentry.Properties["mail"].Add(ioemailaddress.Text +
emaildomain.Text);
userentry.CommitChanges();

The subsystem apparently makes the query as anon if you aren't an admin
(or something like that) Anon read access to GC and things were happy.
I was really just trying to suggest that checking permissions for DA's
probably isn't that helpful. If you could, you might consider
determining
if the user is a DA and not filtering your list if they are. You can do
this by getting the user's tokenGroups attribute and seeing if the DA
group
SID is in there. You can also get this directly from the user's
WindowsIdentity.Groups property (which is probably easier).

In our environment with >150K users and ~80K groups, this performance hit
would really work. :) Neither would showing someone a list of 80K
groups
though. :)

It is probably best to avoid doing queries that return
allowedAttributesEffective against many many objects, as that result is
fairly expensive for the DC to produce. However, if you are trying to
trim
a list, it is hard not to use it.

A way to get much better perf would be to add your own attribute that
contains the DNs of groups that are allowed to use that particular group
and
query against that. Then, of course, you need a way to keep the
permissions
delegations in sync with the contents of that attribute, but your UI will
be
much snappier and your DCs will be lest beaten up servicing that query.

It is weird that your helpdesk users can't search the GC. That should
work
fine. Authenticated Users can typically query the GC, just like the
standard LDAP ports.

The rest of the stuff doesn't surprise me though. The way this kind of
thing is often implemented is using a trusted subsystem design, where
your
client calls some server process that does have the credentials to
perform
the required operations, but then provides its own authorization layer to
implement a role-based security mechanism. The role-based security
mechanism is often a PITA to build and test, but it can gain you a bunch
of
important functionality. Some companies have whole products based on
providing role-based delegation of operations against AD, so you could
also
consider buying something. :)

Best of luck with your solution. It sounds like you've made a lot of
progress. We have a book out there that might have helped you with some
of
the stuff you are doing if you are interested.

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services
Programming"
http://www.directoryprogramming.net
--
Brian Hampson said:
Here's the gist of what I've created (some might call it a monster)

Our environment had a number of domain admins (which we are now working
on pruning) People would create users and not put in alot of the
information ("Too much work...") so I've created a front end app that
will:

a) create the user
b) create a home directory on a server of choice (based on which OU
they are in)
c) create a share for the home directory from that server.
d) create an exchange mailbox on a designated mailserver (based on OU)
and email address for the user.
e) place the user in a default group based on which OU they are in.
f) give the creator a list of other groups into which they have
permission to add the user. (This was the part that I needed this last
bit of help for)

This was all fine with DA's. Then we created a "HelpDesks" group.
Suddenly things like D$ weren't available, remote WMI, Distributed COM,
reading the GC for email addresses, all fell apart. The last few weeks
I have been rewriting the code to handle the lower privs. When a
"HelpDesk" user creates a new user, they are now only presented with
the groups that they can "legally" add a user, not all :)

Now, like I said, my only problem is that my DA's get a list of about
500+ groups, and adding that process takes upwards of 60 seconds.


Here's what I'm doing for that:

ADNode = new System.DirectoryServices.DirectoryEntry("LDAP://" +
domainstr);
System.DirectoryServices.DirectorySearcher ADSearch = new
System.DirectoryServices.DirectorySearcher(ADNode);
ADSearch.CacheResults = true;
ADSearch.PropertiesToLoad.Add("cn");

ADSearch.PropertiesToLoad.Add("allowedAttributesEffective");
ADSearch.Filter = "(&(objectCategory=group)(!cn=domain
computers)(!cn=domain controllers))";
GroupList.Sorted = true;
foreach (System.DirectoryServices.SearchResult ADSearchres
in ADSearch.FindAll())
{

//ActiveDs.ADSearchres.Properties["ntSecurityDescriptor"]
if
(ADSearchres.Properties["allowedAttributesEffective"].Contains("member"))
{
ADChild = ADSearchres.GetDirectoryEntry();
GroupListAddItem(ADChild);
if (ADChild.Name == "CN=G Env Env")
GroupListAddSelectedItems(ADChild);
}
}

Note, the calls to "functions" are delegates for background thread
update via invoke. Am I doing something horribly wrong?

Joe Kaplan (MVP - ADSI) wrote:

There isn't really any point of running this for a DA. DAs generally
are
given full control over all objects and can typically take ownership
of
any
object that has that ACE removed, so there is no real way to prevent a
DA
from modifying just about anything. You should always assume that DAs
can
do anything they want to any object in the forest.
 
You took the words right out of my mouth. Perhaps .Net v4 or 5 will
have nice native implementations for these things that don't make you
jump through a few dozen hoops. 2.0 was a nice step up from 1.1 It's
got to keep getting better!

Thanks for all your tips. I've never been a big book fan, but from
what I've seen it looks like yours might make a nice addition to my
thin collection :)

Cheers!

B.

Ah, that is CDOEXM doing that. It is a terrible API and I hate it, but it
is also the only supported method for mailbox-enabling objects in Exchange.
Argh.

But yes, AD in 2003 doesn't allow anonymous queries (GC or LDAP port), so
that would cause a problem. Yet another reason to hate CDOEXM! The crappy
deployment model (install Exchange System Manager?!) and dependence on ADSI
integration (can't be used directly from LDAP) and other weird behaviors
with little value-add increase my loathing as well.

These "little" provisionings apps always do tend to get out of hand, don't
they? :)

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
Brian Hampson said:
Thanks Joe. I really appreciate all the tips. I had originally
planned on it being a "quick tool" and it has really grown. The reason
I wrote it is because I wanted to learn some AD programming, and make
my life easier. At least I got one of those two :)

The lines that caused the anonymous GC query were:

CDOEXM.IMailboxStore mailbox;
mailbox = (IMailboxStore)userentry.NativeObject;

mailbox.CreateMailbox(MailServerList.SelectedValue.ToString());
userentry.CommitChanges();

//Give Mailbox some properties, like email address.

userentry.Properties["mailNickname"].Add(iFname.Text + "." +
iLName.Text);

userentry.Properties["mail"].Add(ioemailaddress.Text +
emaildomain.Text);
userentry.CommitChanges();

The subsystem apparently makes the query as anon if you aren't an admin
(or something like that) Anon read access to GC and things were happy.
I was really just trying to suggest that checking permissions for DA's
probably isn't that helpful. If you could, you might consider
determining
if the user is a DA and not filtering your list if they are. You can do
this by getting the user's tokenGroups attribute and seeing if the DA
group
SID is in there. You can also get this directly from the user's
WindowsIdentity.Groups property (which is probably easier).

In our environment with >150K users and ~80K groups, this performance hit
would really work. :) Neither would showing someone a list of 80K
groups
though. :)

It is probably best to avoid doing queries that return
allowedAttributesEffective against many many objects, as that result is
fairly expensive for the DC to produce. However, if you are trying to
trim
a list, it is hard not to use it.

A way to get much better perf would be to add your own attribute that
contains the DNs of groups that are allowed to use that particular group
and
query against that. Then, of course, you need a way to keep the
permissions
delegations in sync with the contents of that attribute, but your UI will
be
much snappier and your DCs will be lest beaten up servicing that query.

It is weird that your helpdesk users can't search the GC. That should
work
fine. Authenticated Users can typically query the GC, just like the
standard LDAP ports.

The rest of the stuff doesn't surprise me though. The way this kind of
thing is often implemented is using a trusted subsystem design, where
your
client calls some server process that does have the credentials to
perform
the required operations, but then provides its own authorization layer to
implement a role-based security mechanism. The role-based security
mechanism is often a PITA to build and test, but it can gain you a bunch
of
important functionality. Some companies have whole products based on
providing role-based delegation of operations against AD, so you could
also
consider buying something. :)

Best of luck with your solution. It sounds like you've made a lot of
progress. We have a book out there that might have helped you with some
of
the stuff you are doing if you are interested.

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services
Programming"
http://www.directoryprogramming.net
--
Here's the gist of what I've created (some might call it a monster)

Our environment had a number of domain admins (which we are now working
on pruning) People would create users and not put in alot of the
information ("Too much work...") so I've created a front end app that
will:

a) create the user
b) create a home directory on a server of choice (based on which OU
they are in)
c) create a share for the home directory from that server.
d) create an exchange mailbox on a designated mailserver (based on OU)
and email address for the user.
e) place the user in a default group based on which OU they are in.
f) give the creator a list of other groups into which they have
permission to add the user. (This was the part that I needed this last
bit of help for)

This was all fine with DA's. Then we created a "HelpDesks" group.
Suddenly things like D$ weren't available, remote WMI, Distributed COM,
reading the GC for email addresses, all fell apart. The last few weeks
I have been rewriting the code to handle the lower privs. When a
"HelpDesk" user creates a new user, they are now only presented with
the groups that they can "legally" add a user, not all :)

Now, like I said, my only problem is that my DA's get a list of about
500+ groups, and adding that process takes upwards of 60 seconds.


Here's what I'm doing for that:

ADNode = new System.DirectoryServices.DirectoryEntry("LDAP://" +
domainstr);
System.DirectoryServices.DirectorySearcher ADSearch = new
System.DirectoryServices.DirectorySearcher(ADNode);
ADSearch.CacheResults = true;
ADSearch.PropertiesToLoad.Add("cn");

ADSearch.PropertiesToLoad.Add("allowedAttributesEffective");
ADSearch.Filter = "(&(objectCategory=group)(!cn=domain
computers)(!cn=domain controllers))";
GroupList.Sorted = true;
foreach (System.DirectoryServices.SearchResult ADSearchres
in ADSearch.FindAll())
{

//ActiveDs.ADSearchres.Properties["ntSecurityDescriptor"]
if
(ADSearchres.Properties["allowedAttributesEffective"].Contains("member"))
{
ADChild = ADSearchres.GetDirectoryEntry();
GroupListAddItem(ADChild);
if (ADChild.Name == "CN=G Env Env")
GroupListAddSelectedItems(ADChild);
}
}

Note, the calls to "functions" are delegates for background thread
update via invoke. Am I doing something horribly wrong?

Joe Kaplan (MVP - ADSI) wrote:

There isn't really any point of running this for a DA. DAs generally
are
given full control over all objects and can typically take ownership
of
any
object that has that ACE removed, so there is no real way to prevent a
DA
from modifying just about anything. You should always assume that DAs
can
do anything they want to any object in the forest.
 
Look for something they are calling the "Principal API" in the Orcas release
timeframe. They actually showed it at Tech Ed 3 years ago, but then moved
the release back.

The idea behind it is to provide wrappers around objects like Users and
Groups, but with a focus on provisioning and lifecycle instead of
authorization (like the current IPrincipal class). I think that may in fact
be called .NET 4.0 or 5.0 by then. :)

Whether or not Exchange provisioning will still suck or not will depend a
great deal on the Exchange team (and the version of Exchange you use), but I
don't have high hopes...

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
Brian Hampson said:
You took the words right out of my mouth. Perhaps .Net v4 or 5 will
have nice native implementations for these things that don't make you
jump through a few dozen hoops. 2.0 was a nice step up from 1.1 It's
got to keep getting better!

Thanks for all your tips. I've never been a big book fan, but from
what I've seen it looks like yours might make a nice addition to my
thin collection :)

Cheers!

B.

Ah, that is CDOEXM doing that. It is a terrible API and I hate it, but
it
is also the only supported method for mailbox-enabling objects in
Exchange.
Argh.

But yes, AD in 2003 doesn't allow anonymous queries (GC or LDAP port), so
that would cause a problem. Yet another reason to hate CDOEXM! The
crappy
deployment model (install Exchange System Manager?!) and dependence on
ADSI
integration (can't be used directly from LDAP) and other weird behaviors
with little value-add increase my loathing as well.

These "little" provisionings apps always do tend to get out of hand,
don't
they? :)

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services
Programming"
http://www.directoryprogramming.net
--
Brian Hampson said:
Thanks Joe. I really appreciate all the tips. I had originally
planned on it being a "quick tool" and it has really grown. The reason
I wrote it is because I wanted to learn some AD programming, and make
my life easier. At least I got one of those two :)

The lines that caused the anonymous GC query were:

CDOEXM.IMailboxStore mailbox;
mailbox = (IMailboxStore)userentry.NativeObject;

mailbox.CreateMailbox(MailServerList.SelectedValue.ToString());
userentry.CommitChanges();

//Give Mailbox some properties, like email address.

userentry.Properties["mailNickname"].Add(iFname.Text + "." +
iLName.Text);

userentry.Properties["mail"].Add(ioemailaddress.Text +
emaildomain.Text);
userentry.CommitChanges();

The subsystem apparently makes the query as anon if you aren't an admin
(or something like that) Anon read access to GC and things were happy.

Joe Kaplan (MVP - ADSI) wrote:
I was really just trying to suggest that checking permissions for DA's
probably isn't that helpful. If you could, you might consider
determining
if the user is a DA and not filtering your list if they are. You can
do
this by getting the user's tokenGroups attribute and seeing if the DA
group
SID is in there. You can also get this directly from the user's
WindowsIdentity.Groups property (which is probably easier).

In our environment with >150K users and ~80K groups, this performance
hit
would really work. :) Neither would showing someone a list of 80K
groups
though. :)

It is probably best to avoid doing queries that return
allowedAttributesEffective against many many objects, as that result
is
fairly expensive for the DC to produce. However, if you are trying to
trim
a list, it is hard not to use it.

A way to get much better perf would be to add your own attribute that
contains the DNs of groups that are allowed to use that particular
group
and
query against that. Then, of course, you need a way to keep the
permissions
delegations in sync with the contents of that attribute, but your UI
will
be
much snappier and your DCs will be lest beaten up servicing that
query.

It is weird that your helpdesk users can't search the GC. That should
work
fine. Authenticated Users can typically query the GC, just like the
standard LDAP ports.

The rest of the stuff doesn't surprise me though. The way this kind
of
thing is often implemented is using a trusted subsystem design, where
your
client calls some server process that does have the credentials to
perform
the required operations, but then provides its own authorization layer
to
implement a role-based security mechanism. The role-based security
mechanism is often a PITA to build and test, but it can gain you a
bunch
of
important functionality. Some companies have whole products based on
providing role-based delegation of operations against AD, so you could
also
consider buying something. :)

Best of luck with your solution. It sounds like you've made a lot of
progress. We have a book out there that might have helped you with
some
of
the stuff you are doing if you are interested.

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services
Programming"
http://www.directoryprogramming.net
--
Here's the gist of what I've created (some might call it a monster)

Our environment had a number of domain admins (which we are now
working
on pruning) People would create users and not put in alot of the
information ("Too much work...") so I've created a front end app
that
will:

a) create the user
b) create a home directory on a server of choice (based on which OU
they are in)
c) create a share for the home directory from that server.
d) create an exchange mailbox on a designated mailserver (based on
OU)
and email address for the user.
e) place the user in a default group based on which OU they are in.
f) give the creator a list of other groups into which they have
permission to add the user. (This was the part that I needed this
last
bit of help for)

This was all fine with DA's. Then we created a "HelpDesks" group.
Suddenly things like D$ weren't available, remote WMI, Distributed
COM,
reading the GC for email addresses, all fell apart. The last few
weeks
I have been rewriting the code to handle the lower privs. When a
"HelpDesk" user creates a new user, they are now only presented with
the groups that they can "legally" add a user, not all :)

Now, like I said, my only problem is that my DA's get a list of
about
500+ groups, and adding that process takes upwards of 60 seconds.


Here's what I'm doing for that:

ADNode = new System.DirectoryServices.DirectoryEntry("LDAP://" +
domainstr);
System.DirectoryServices.DirectorySearcher ADSearch = new
System.DirectoryServices.DirectorySearcher(ADNode);
ADSearch.CacheResults = true;
ADSearch.PropertiesToLoad.Add("cn");

ADSearch.PropertiesToLoad.Add("allowedAttributesEffective");
ADSearch.Filter = "(&(objectCategory=group)(!cn=domain
computers)(!cn=domain controllers))";
GroupList.Sorted = true;
foreach (System.DirectoryServices.SearchResult
ADSearchres
in ADSearch.FindAll())
{

//ActiveDs.ADSearchres.Properties["ntSecurityDescriptor"]
if
(ADSearchres.Properties["allowedAttributesEffective"].Contains("member"))
{
ADChild = ADSearchres.GetDirectoryEntry();
GroupListAddItem(ADChild);
if (ADChild.Name == "CN=G Env Env")
GroupListAddSelectedItems(ADChild);
}
}

Note, the calls to "functions" are delegates for background thread
update via invoke. Am I doing something horribly wrong?

Joe Kaplan (MVP - ADSI) wrote:

There isn't really any point of running this for a DA. DAs
generally
are
given full control over all objects and can typically take
ownership
of
any
object that has that ACE removed, so there is no real way to
prevent a
DA
from modifying just about anything. You should always assume that
DAs
can
do anything they want to any object in the forest.
 
Back
Top