Connecting to LDAP Server from .NET - c#

I've been recommended to use System.DirectoryServices.Protocols to be able to support connecting to LDAP servers other than Active Directoy here.
Unfortunately, I have not been able to search the directory properly. I'd like to be able to get a certain attribute for a user (e.g. mail). This is easily done in System.DirectoryServices namespace by using DirectorySearcher class. How can I achieve the same in System.DirectoryServices.Protocols namespace. Here's what I have so far:
var domainParts = domain.Split('.');
string targetOu = string.Format("cn=builtin,dc={0},dc={1}", domainParts[0], domainParts[1]);
string ldapSearchFilter = string.Format("(&(ObjectClass={0})(sAMAccountName={1}))", "person", username);
// establish a connection to the directory
LdapConnection connection = new LdapConnection(
new LdapDirectoryIdentifier(domain),
new NetworkCredential() { UserName = username,
Password = "MyPassword" });
SearchRequest searchRequest = new SearchRequest(
targetOu, ldapSearchFilter, SearchScope.OneLevel, new[] {"mail"});
This code raises exception of type DirectoryOperationException with message The object does not exist.
I suspect there's something wrong with my targetOu and ldapSearchFilter variables.
Thanks.

I suspect the main problem might be: samAccountName is a strictly Windows-only attribute that other LDAP servers won't know about.
So if you're going against a non-Active Directory LDAP, you should use something else for searching - e.g. sn (for surname or last name), givenName (first name), possibly displayName.
Another interesting option might be to use ANR (ambiguous name resolution) searches - see this page on SelfADSI roughly in the middle, where ANR is explained.
With ANR, you would write your query like this:
string ldapSearchFilter =
string.Format("(&(ObjectCategory={0})(anr={1}))", "person", username);
I also changed ObjectClass to ObjectCategory for two reasons:
ObjectCategory is single-valued, e.g. only contains a single value (ObjectClass is multi-valued)
ObjectCategory is typically indexed, and thus searches are typically a lot faster using ObjectCategory
Does this return the results you're looking for?

Related

DirectoryEntry call to only dns configured dc servers under a domain

We are querying Active Directory using the DirectoryEntry method by passing a domain name.
Under this domain, there are 40 DC's, in that 20 of them are DNS configured, and the rest of them are non-DNS configured, which are not maintained well and not reliable(connecting to these non-DNS configured DC's will usually timeout or thread being aborted).
Now, while making an AD call with directoryEntry method, is there a way to query only the DC's which has the DNS configured?
Currently, the code picks the Non-DNS configured DC.
I know picking the DC in a domain is a domain server task, based on the geographical location and other factors. Is there any way we can modify the code to instruct the DirectoryEntry to pick only the DNS configured DC's when we pass the DomainName.
Sample code in c# .net:
DirectoryEntry obEntry = new DirectoryEntry(#"LDAP://" + DomainName + "/<SID=" + new SecurityIdentifier(groupSid, 0).Value + ">", serviceAccountUser, serviceAccountPassword);
if (obEntry.Guid != null)
{
string distinguishedNameObtained = Convert.ToString(obEntry.Properties["distinguishedName"].Value);
}
You can't tell DirectoryEntry to pick a subset of DCs, but you can tell it to use one specific DC. In your code, you would set your DomainName variable to the name of the DC:
var DomainName = "dc1.example.com";
That's the easiest way, but now you've hard-coded one single DC, and if that one goes down, you have to change your code, which isn't ideal.
If you want to chose from the available DCs, you could try using Domain.GetCurrentDomain() or Domain.GetDomain() (if the computer you're running this from is not on the same domain you're connecting to) and then examining the DomainControllers collection. I don't know what you mean by the DCs not being configured for DNS, so I'm not sure if that's something you can determine from the DomainController class. Take a look at the documentation for DomainController and see if there is something you can use. There is a SiteName property if you want to choose a DC from a specific site.
If you are able to do that, then you can use the Name property of the DomainController in your LDAP string.

How to create GMSA account via C#

I have tried to look for the c# code example to see how the AD service account is created but not much luck. Anyone can provide an example code for creating AD service account please?
I have tried UserPrincipal with $ at the end of the name but not much luck. Errors with Access Denied (Cant create under root MyDomain or under a CN)
// Domain Context to use specific LDAP path.
domainContext = new PrincipalContext(ContextType.Domain, domainContext.ConnectedServer, "CN=Managed Service Accounts,dc=mydomain");
UserPrincipal userAccount = new UserPrincipal(domainContext)
{
DisplayName = userName,
SamAccountName = $"{userName}$",
Description = description
};
userAccount.Save();
Little late, but I think I can answer it. You need to use the NetAddServiceAccount function through logoncli.dll. I hadn't been able to get it to work in PowerShell, even with adding what I thought was an appropriate type shim, but I just came across a module that seems to work.
https://github.com/beatcracker/Powershell-Misc/blob/master/Use-ServiceAccount.ps1
The C# code for the type definition in that script should have everything you need to implement it for yourself.

DirectoryEntry results in The server is unwilling to process the request

I am writing a small app that integrates with AD and will be listing users and allowing members of the site to edit certain fields of the users. These fields will be, first name, last name, display name and email.
When I run the following code I get an exception that says
The server is unwilling to process the request.
Please note that result is not null and contains the correct active directory user that I want to edit. Also note that result is of type SearchResult. Also note that the user I am using to connect to AD is the Administrative user.
DirectoryEntry entryToUpdate = result.GetDirectoryEntry();
entryToUpdate.Properties["cn"].Value = user.Name;
entryToUpdate.Properties["mail"].Value = user.Email;
entryToUpdate.Properties["sn"].Value = user.Surname;
entryToUpdate.Properties["displayName"].Value = user.DisplayName;
string username = user.Email.Substring(0, user.Email.IndexOf("#"));
entryToUpdate.Properties["sAMAccountName"].Value = username;
entryToUpdate.CommitChanges();
Any Ideas?
I believe
entryToUpdate.Properties["cn"].Value = user.Name;
is your Problem. Use givenName as first Name. "cn" is managed by the System.
Also:
If using Exchange, the Mail attribute is managed by it.
Consider the ramifications of modifing samAccountName. Users may not know how to log in, seperate Systems with pseudo SSO may not recognize them, the Login Name does not match the local Profile Name, etc.

Is there a way to enable referral chasing for UserPrincipal.FindByIdentity()?

I have a .NET 3.5 web application that uses the System.DirectoryServices.AccountManagement classes. When I search for some users I get a PrincipalOperationException: A referral was returned from the server. If I did this the old school way with my own LDAP code I could enable chasing of referrals. Do I need to rewrite my code?
My code looks like this:
using (var principalContext = new PrincipalContext(ContextType.Domain, null, adPath))
{
// Find the principal object for which you wish to enumerate group
// membership.
using (var userPrincipal = UserPrincipal.FindByIdentity(principalContext, identity))
{
if (userPrincipal != null)
{
Name = userPrincipal.DisplayName;
DistinguishedName = userPrincipal.DistinguishedName;
EmailAddress = userPrincipal.EmailAddress;
Sid = userPrincipal.Sid.Value;
}
}
}
My adPath can be one of 2 values. One of the values is a domain that was recently joined, and can be accessed using different tools. I believe this is a problem with how this .NET library makes the LDAP calls.
Here is a partial Answer, as it's too long for a comment.
According to this Microsoft documentation, as you even know, Referrals are a hint that the client can chase. But concerning RODC they add "For example, in the case of an LDAP application, if chase referrals is enabled on the LDAP connection between the client and the RODC, the application never knows that the client received a referral from the RODC. The client is automatically redirected to the writable domain controller that is specified in the referral. ".
So I look how to enable LDAP chasing on a connexion in Microsoft site and I found this which means ADSI use. I'am very interested in the answer.
Do you try to query the global catalog like this :
/* Retreiving a principal context
*/
PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, "YourGCServer:3268", "dc=dom,dc=fr", "User", "Password");
It's supposed to contains all the forest domain's datas.
I hope it helps.
Have you tried code of the form(put the domain in as the second argument):
var principalContext = new PrincipalContext(ContextType.Domain, "office.local", "OU=Users, DC=office, DC=local" ))
Also make sure that the adPath is from most specific to least specific.

With WMI: How can I get the name of the user account who's running my program?

I need to restrict access to my application to only one specific user account. I have found classes under WMI to find users accounts, but I donĀ“t know how to recognize which one is running my app.
There are simpler ways to get the current username than using WMI.
WindowsIdentity.GetCurrent().Name will get you the name of the current Windows user.
Environment.Username will get you the name of the currently logged on user.
The difference between these two is that WindowsIdentity.GetCurrent().Name will also include the domain name as well as the username (ie. MYDOMAIN\adrian instead of adrian). If you need the domain name from Environment, you can use Environment.UserDomainName.
EDIT
If you really want to do it using WMI, you can do this:
ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT UserName FROM Win32_ComputerSystem");
ManagementObjectCollection collection = searcher.Get();
string username = (string) collection.Cast<ManagementBaseObject>().First()["UserName"];
Unfortunately, there is no indexer property on ManagementObjectCollection so you have to enumerate it to get the first (and only) result.
You don't necessarily need to use WMI. Check out WindowsIdentity.
var identity = WindowsIdentity.GetCurrent();
var username = identity.Name;
The simplest approach is through the Environment class:
string user = Environment.UserDomainName + "\\" + Environment.UserName;
There also are several ways to restrict to a certain user (although checking for a role is more common).
Apart from the obvious
if (userName == "domain\\john")
{ }
You can also use the following attribute on an entire class or specific methods:
[PrincipalPermission(SecurityAction.Demand, Name = "domain\\john",
Authenticated = true)]
void MyMethod()
{
}
Which could be a bit more reliable for low-level, reusable methods.
Note that you could use both, checking for a user with if() as part of the normal flow of the program and the attribute as a safeguard on critical methods.

Categories