How to get down-level logon name from UserPrincipal in .Net - c#

I have the following search query where I use a User Principal Name identification:
PrincipalContext principalContext = new PrincipalContext(ContextType.Domain);
var user = UserPrincipal.FindByIdentity(principalContext, IdentityType.UserPrincipalName, "user1#domain1.local"); //the searched UPN will be dynamic!
After I get the UserPrincipal object, I have to create a down-level logon format (domain1\user1).
Unfortunately I have the following distinguished name:
CN=User One,OU=Every employee,DC=domain1,DC=local
It looks like user.Context.Name is null which is claimed to be the name of the domain.
Also, if the selected user is a local one, I should get the machine1\localuser1 value.
The SamAccountName contains only the user name, without the context (user1).
What is the best way to get that format from any UserPrincipal object?

If you need to do this for machine accounts, then your answer is the way to go.
But another way, strictly for Active Directory (in case it comes in handy some other time), is to look at the msDS-PrincipalName attribute, which you can get via the underlying DirectoryEntry object:
var de = (DirectoryEntry) user.GetUnderlyingObject();
de.RefreshCache(new [] { "msDS-PrincipalName" }); //retrieve it from AD
var domainUsername = de.Properties["msDS-PrincipalName"].Value;
Since it's a constructed attribute, it's not returned unless you specifically ask for it, which is what you use RefreshCache for.

The down-level logon format can be obtained via an NTAccount object. However, it requires some translation to convert the Principal's SID to an NTAccount:
string downLevelLogonName =
userPrincipal.Sid.Translate(typeof(System.Security.Principal.NTAccount)).ToString();
Using that code works in both case (domain or local computer users).

Related

Active Directory: How to determine whether account is service account?

Question: Is it possible to determine whether an account is a service account in Active Directory using C# LDAP? If yes, how?
Context: I have a program that is retrieving all objects of schema class type USER, GROUP, COMPUTER, FOREIGN SECURITY PRINCIPAL, and CONTACT. Currently, a service account is identified by string parsing the canonical name for 'service account'. I do not like this solution because string parsing is dependent on a folder location in the hierarchy that literally says 'service account'. It seems possible that a service account could be created and then placed in a folder path that does not include the string 'service account'. Unfortunately, I cannot test this because I am not an AD admin.
I have browsed around online without any luck so I am not sure if it is even possible.
Update:
Per Microsoft, it appears that the service account is contained in objectClass msDS-ManagedServiceAccount. However, when I set the DirectoryEntry filter to msDS-ManagedServiceAccount, no results are returned.
directoryEntry = new DirectoryEntry(strActiveDirectoryHost, null, null, AuthenticationTypes.Secure);
string strDsFilter = "(objectClass=msDS-ManagedServiceAccount)";
DirectorySearcher directorySearcher = new DirectorySearcher(directoryEntry)
{
Filter = strDsFilter,
SearchScope = SearchScope.Subtree,
PageSize = intActiveDirectoryPageSize,
};
return searchResultCollection = directorySearcher.FindAll();
I have testing your code, and it does in fact return results in my environment. A few things to note:
Be sure that strActiveDirectoryHost is formatted correctly. The format should be LDAP://DC=contoso,DC=com
Check that you are searching from the root (or high enough to find the accounts you are looking for). MSAs are under the Managed Service Accounts container under the domain NC (i.e. LDAP://CN=Managed Service Accounts,DC=contoso,DC=com)
In my tests, I call new DirectoryEntry() with only the path. Not sure if passing AuthenticationTypes.Secure is causing an issue for you
The objectClass you have is correct.
So I am working on this to get the MSA as well as create them. I am able to get the MSA using the System.DirectoryServices.AccountManagement namespace, still working on creating it (unsure if this is really possible)
But for finding the accounts which are MSAs you can use the below code
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain, sDomain, sDefaultOU, ContextOptions.SimpleBind, sServiceUser, sServicePassword);
GroupPrincipal currentGroup = GroupPrincipal.FindByIdentity(oPrincipalContext, "YourGroupName");
foreach (Principal a_principal in currentGroup.GetMembers())
{
if (a_principal.StructuralObjectClass == "msDS-ManagedServiceAccount")
{
Console.Write(a_principal.SamAccountName); //To get the name
ComputerPrincipal oComputerPrincipal = ComputerPrincipal.FindByIdentity(oPrincipalContext, a_principal.Name); //creating a computerprincipal to get more details about the MSA
}
}
You can use the above logic and create a Principal for the user account and get the structural object class for that account to find out if it is MSA.
Something like this:
UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPrincipalContext, sUserName);
if (oUserPrincipal.StructuralObjectClass == "msDS-ManagedServiceAccount")
{
Console.Write(oUserPrincipal.SamAccountName); //To get the samaccountname
}

Unable to extract information

I'm trying to query a domain to determine if:
User is a valid user (and has the correct password)
User is enabled
User belongs to group x
My development machine does not belong to this domain.
I want to specify the username and password via my application
I'm using the System.DirectoryServices.AccountManagement namespace as this seems to be the most efficient way doing this, however I've struggling to get even the most basic of information out of my domain controller.
I can explore LDAP via another tool.
First test is to collect user information, the code below returns null on user.
The user however is valid.
What am I doing wrong?
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "server","CN=Users,DC=doom,DC=home", "ldapuser","password");
// get user contect
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, IdentityType.Name, username);
//is user locked?
var locked = user.Enabled;
Update:
Having defined the bind method as below, I now receive error
"Information about the domain could not be retrieved (1355)."
var ctx = new PrincipalContext(ContextType.Domain, "server", "DC=doom,DC=home", ContextOptions.SimpleBind, "ldapuser", "password");
Sorted.
This answer resolves the two issues I came across when attempting to connect to a domain controller that I am not a member of.
This article get me the final answer:
http://elegantcode.com/2009/03/21/one-scenario-where-the-systemdirectoryservices-accountmanagement-api-falls-down/
you need to define the Bind in the context (i.e. ContextOptions.SimpleBind)
You must set up the domain server in your Network adaptors DNS settings as the first DNS server to use.
I can now connect to my AD and collect data.

Could not query AD without distinguished name in SearchRequest?

I am running a test Active directory and am trying to query with ldap. I created a searchrequest object with distingueshed name empty and a filter this is throwing noSuchObject error code with "object does not exist" message. I am only getting this from my test AD , if I use my company's production AD I am not getting exception, just a response with no hit. What do I need to change in my test AD to see similar behaviour ?
You can use a PrincipalSearcher and a "query-by-example" principal to do your searching:
// create your domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// define a "query-by-example" principal - here, we search for a UserPrincipal
// and with specified last name (surname)
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.Surname = "Willis";
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
// find all matches
foreach(var found in srch.FindAll())
{
// do whatever here - "found" is of type "Principal" - it could be user, group, computer.....
}
If you haven't already - absolutely read the MSDN article Managing Directory Security Principals in the .NET Framework 3.5 which shows nicely how to make the best use of the new features in System.DirectoryServices.AccountManagement
Of course, depending on your need, you might want to specify other properties on that "query-by-example" user principal you create:
Surname (or last name)
DisplayName (typically: first name + space + last name)
SAM Account Name - your Windows/AD account name
User Principal Name - your "username#yourcompany.com" style name
You can specify any of the properties on the UserPrincipal and use those as "query-by-example" for your PrincipalSearcher.
#marc_s answered by giving you a way of searching
Back to your question, just a recall :
A LDAP search is
The nod from which you ask to begin the search (in your case the DN of your OU)
The scope of your search (base, onelevel, subtree)
The filter of your search ((objectClass=group))
The attributes you want to retreive
In you case it works when your ADSI layer is able to find a default Domain. So I think that you have to create a real LDAP-SEARCH request en perhaps also give credentials.
Thanks for the other answers. I solved my problem by using GC port 3268 instead of DC port 389 in the connection.

Obtaining the SecurityIdentifier (SID) from just a domain username (c#)

I wish to obtain the domain SID from a user account that has been used to access my web page, but...
In some instances, you dont have access to the HttpContext object to call HttpContext.Current.Request.LogonUserIdentity.User.Value
All the static function is able to accept is a user-name as a string, and a domain-name as a string.
If the domain is left blank, it is amused that the user account name is within the same domain as the IIS server that the process is executing upon. :/
your help would be most appreciated as always.
Cheers.
To get the a user's domain, you can use LookupAccountName. In case you need a pinvoke sample, get it from pinvoke.net
You pass in DOMAIN\UserName to the function. The LookupAccountName function will give you back the user SID and the domain name.
I suspect that you don't really need to know the domain SID. I would think the returned domain name should be enough for you. If you really want to know the domain SID, you can extract it directly from a the returned user SID.
A user SID is always starting with a domain SID. For the details of how a SID structure looks like and how to extract the domain SID, please check here
Try using Directory Services? Need .Net 3.5 for this:
public UserPrincipal GetUserPrincipal(string userName)
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, LDAPDomain, LDAPUser, LDAPPassword);
return UserPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, userName);
}
You can then use the UserPrincipal Class which has a .Sid method.
I set my Domain and AD account info from the web.config like so
private String LDAPDomain
{
get
{
return ConfigurationManager.AppSettings["LDAPDomain"];
}
}

ASP.NET - Get the Principal / Relative Identifier (RID) for a DirectoryEntry / SID

I am using Active Directory in a custom MembershipProvider class to authenticate users in an ASP.NET 2.0 intranet application and associate their sid with a profile for the application.
When the ActiveDirectoryMembershipProvider is used, the ProviderUserKey object for the MembershipUser is as follows
SecurityIdentifier sid = (SecurityIdentifier)Membership.GetUser().ProviderUserKey;
string sidValue = sid.ToString();
/* sidValue = "S-1-5-21-XXXX-XXXX-XXXX-YY" */
As I understand it, YY is the principal within the namespace (also referred to as a group/domain).
When using the custom MembershipProvider, I can get the sid using the objectSid property of a DirectoryEntry object
DirectoryEntry entry = new DirectoryEntry(path, username, password);
SecurityIdentifier sid = new SecurityIdentifier((byte[])entry.Properties["objectSid"].Value, 0);
string sidValue = sid.ToString();
/* sidValue = "S-1-5-21-XXXX-XXXX-XXXX" */
The sidValue in this case is identical, except it does not contain the principal YY.
My question is two-fold
Is the principal required in order to uniquely identify an individual?
Is it possible to obtain the principal from the DirectoryEntry object (or through any other classes available in System.DirectoryServices)?
EDIT:
Having done some further reading ({1} {2}), I now know that the sid can change if the user is moved from one group/domain to another. In light of this, would using the GUID defined in the DirectoryEntry Properties["objectGUID"] be a better choice for uniquely identifying a user?
The objectGUID is the best choice for identifying a user account. I highlight this because the objectGUID is unique and fixed for an instance of an account. If you delete and recreate the account with the same distinguishedName you'll get a different objectGUID. So, objectGUID doesn't identify the user, it identifies the account.
So, if you want to identify the account, use objectGUID.
Sometimes, accounts can be deleted and recreated by admins to solve problems. If you need to identify the user even after this has happened, you need to pick something else on the account object. That will probably have to depend on your account definition policies. Maybe you have sAMAccountNames that are not based on the user's name? Maybe the admins populate employeeid or employeeNumber? Maybe they enforce uniqueness for displayNames?
Here's a link to AD attribute info.
Here's a link to DirectoryEntry Properties.

Categories

Resources