From where PrincipalContext gets the connected server name? - c#

I have a web application with windows authentication. When I debug the line
var domainContext = new PrincipalContext(ContextType.Domain, "abc");
where "abc" is domain name.
I can see the connected server as domain controller name. I would like to know from where it is getting the DC name.
Actually, the issue is, even though a particular DC is demoted and new DC is added, our application always pointing to the same demoted DC and throws error when we try to get groups of the user using the below lines.
var user = UserPrincipal.FindByIdentity(domainContext, "username");
var groups = user.GetGroups();
[ActiveDirectoryServerDownException: The server is not operational.
Name: "DomainController.Domain.com"
]
System.DirectoryServices.ActiveDirectory.PropertyManager.GetPropertyValue(DirectoryContext
context, DirectoryEntry directoryEntry, String propertyName) +510
System.DirectoryServices.ActiveDirectory.DirectoryEntryManager.ExpandWellKnownDN(WellKnownDN
dn) +239
System.DirectoryServices.ActiveDirectory.DomainController.get_Domain()
+71
System.DirectoryServices.AccountManagement.ADStoreCtx.GetGroupsMemberOf(Principal
p) +839
System.DirectoryServices.AccountManagement.Principal.GetGroups() +32
Do I need to clear IIS cache or some other cache? I have restarted the IIS too.

You can retrieve the DC name programmatically if your server belongs to domain controller.
using (var context = new System.DirectoryServices.AccountManagement.PrincipalContext(ContextType.Domain))
{
server = context.ConnectedServer;
//Output : dc.example.com
var formatted = server.Split('.').Select(s => String.Format("DC={0}", s));
joined = String.Join(",", formatted);
// Output DC=dc,DC=example,DC=com
}
If the server isn't a part of the domain controller. You can retrieve it by using credentials like this;
System.DirectoryServices.ActiveDirectory.DirectoryContext domainContext = new DirectoryContext(DirectoryContextType.Domain, "domainName", "domainUser", "Password");
var domain = ActiveDirectory.Domain.GetDomain(domainContext);
var controller = domain.FindDomainController();

Related

Query services remotely using WMI

I want to get status of services on remote server (Windows 2008 R2). I am using domain account. My problem is that instead of 150+ services, I am getting only 34.
Here is my code:
var connection = new ConnectionOptions
{
Username = $"{domain}\\{userName}",
Password = password,
};
var scope = new ManagementScope(#"\\{ip}\root\cimv2", connection);
var query = new ObjectQuery(importConfig.Query);
var searcher = new ManagementObjectSearcher(scope, "SELECT * FROM Win32_Service");
var result = searcher.Get()
When I add my domain account do Administrators on this server, I am getting valid response - 150+ services.
I also tried to set (I tried AU, SID and many other combinations):
sc sdset SCMANAGER D:(A;;CCLCRPRC;;;AU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)S:(AU;FA;KA;;;WD)(AU;OIIOFA;GA;;;WD)
but the result is the same - 34 services are returned when domain account is not in Admins. Any idea why I don't see other services?

Why is User.Invoke("SetPassword") using my computer's current credentials and how I can I specify them instead?

I am able to create new users in a remote ActiveDirectory server, which I am doing using an admin account on that server. This is how I do that:
String connection = "LDAP://serveraddress/OU=NewUsers,OU=People,DC=mydomain,DC=local";
usersDirectoryEntry = new DirectoryEntry(connection, "adminUserName","adminPass");
DirectoryEntry newUser = usersDirectoryEntry.Children.Add(userString, "user");
newUser.CommitChanges();
However, immediately after I am trying to set the password using:
UpdatePassword(newUser, "userPasswordMeetsRequirements");
where
private static void UpdatePassword(DirectoryEntry User, String password)
{
User.Properties["pwdLastSet"].Value = -1;
User.Invoke("SetPassword", new object[] { password.Trim() });
User.Properties["userAccountControl"].Value = 0x0200 | 0x10000;
User.CommitChanges();
}
Where User is of type DirectoryEntry (https://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentry(v=vs.110).aspx)
And on the User.Invoke line I get a System.Reflection.TargetInvocationException --> System.UnauthorizedAccessException: Access is denied.
That admin user should indeed have set password permissions. After looking in the AD logs I found that at the User.Invoke line my program was trying to connect to the AD server using my current credentials on my computer (which is on a different domain).
Why is this happening, and how can I force it to just use the admin account that it used for user creation?
Are you passing that newUser object to UpdatePassword? I would assume it's using the same credentials.
But if not, you can create a new object like this:
var user = new DirectoryEntry(newUser.Path, "adminUserName","adminPass");
UpdatePassword(user, somepassword);
Begining on .NET 3.5 and newer, you can use the System.DirectoryServices.AccountManagement
You can find information about it in System.DirectoryServices.AccountManagement Namespace.
Why don't you try the following :
PrincipalContext context = new PrincipalContext( ContextType.Domain, null, adAdminLogin, adAdminPassword );
UserPrincipal user = UserPrincipal.FindByIdentity( context, adUserLogin );
user.SetPassword( adUserNewPassword );
I'm able swicth to 3 diff domain using this (just changing domain LDAP path)
i don't see why you couldn't.
Domain = "LDAP://domain2.com";
ADUSER = MySuperAdmin;
ADpassword = "123";
DirectoryEntry Entry = new DirectoryEntry(Domain, ADUSER, ADpassword);
DirectoryEntry Entry = new DirectoryEntry(Domain, ADUSER, ADpassword);
DirectorySearcher Search = new DirectorySearcher(Entry);
Search.Filter = string.Format("(&(objectCategory=computer)(cn={0}))", HostName);
SearchResult Result = Search.FindOne();
now check if your are able find a device on your targeted domain
if it work try change the password if you not able it have nothing to do with the C# code but more with "MySuperAdmin" rigth on the domain2 .try direct in mmc you should pop the same error than in your application.
maybee a checkbox "User cannot change password" may cause the fail too

Programmatically set up user account for custom identity application pool in IIS 7

In my C# code I need to create a custom identity for my web application and add it to IIS 7. I do the following:
string strAppPoolName = "MyAppPool";
string strUserName = Environment.UserDomainName + "\\" + "myappusername";
addUserAccount(strUserName, strUserPass);
using (ServerManager serverManager = new ServerManager())
{
//Add application pool
ApplicationPool appPool = serverManager.ApplicationPools.Add(strAppPoolName);
appPool.AutoStart = true;
appPool.ManagedPipelineMode = ManagedPipelineMode.Integrated;
appPool.ManagedRuntimeVersion = "v4.0";
appPool.ProcessModel.MaxProcesses = 1;
//Assign identity to a custom user account
appPool.ProcessModel.IdentityType = ProcessModelIdentityType.SpecificUser;
appPool.ProcessModel.UserName = strUserName;
appPool.ProcessModel.Password = strUserPass;
}
Where the user is added to the Active Directory as such:
public static void addUserAccount(string sUserName, string sPassword)
{
using (PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain))
{
using (UserPrincipal up = new UserPrincipal(oPrincipalContext))
{
up.SamAccountName = sUserName;
up.SetPassword(sPassword);
up.Enabled = true;
up.PasswordNeverExpires = true;
up.Description = "My app's user account";
up.Save();
}
}
}
The issue is that when I later add my site and application to IIS 7 under that application pool, the web application cannot run because it does not have sufficient permissions. More importantly for me, some of the .NET classes, such as System.Security.Cryptography fail with unexpected error codes even if I manually set read/write permissions for this new user account to the file system folder where my web app is installed.
So while doing a research I found the following statement:
If you use a custom identity, make sure that the user account you
specify is a member of the IIS_IUSRS group on the Web server so that
the account has proper access to resources. Additionally, when you use
Windows and Kerberos authentication in your environment, you might
need to register a Service Principle Name (SPN) with the domain
controller (DC).
So, how do you do this?
If you need to add that account to the IIS_IUSERS group, (which is local on the machine) you can use the GroupPrincipal for that. Keep in mind to create a PrincipalContext that is local for your machine, instead of the Domain one you used for the user. You can simply find the group by identity and then add the new created user to the Memberscollection. The Add method has an overload that accepts an UserPrincipal.
Your code would like this:
using (PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain))
{
using (PrincipalContext oGroupContext = new PrincipalContext(ContextType.Machine))
{
// find the local group IIS_IUSRS
using(var gp = GroupPrincipal.FindByIdentity(oGroupContext,"IIS_IUSRS"))
{
using (UserPrincipal up = new UserPrincipal(oPrincipalContext))
{
up.SamAccountName = sUserName;
up.SetPassword(sPassword);
up.Enabled = true;
up.PasswordNeverExpires = true;
up.Description = "My app's user account";
up.Save();
// add new user to Members of group
gp.Members.Add(up);
// save before Disposing!
gp.Save();
}
}
}
}

UserPrincipal from Active Directory

I have problem with getting UserPrincipal from Active Directory. First of all I have used on my local environment (using not IIS but ASP.NET development Server):
User usr = new User();
usr.SoeId = Request.ServerVariables["LOGON_USER"];
usr.IP = Request.ServerVariables["REMOTE_ADDR"];
usr.FirstName = UserPrincipal.Current.GivenName;
usr.LastName = UserPrincipal.Current.Surname;
And it works fine. I got what I want. But when I install application on testing environment I got error "Object reference not set to an instance of an object". I have tried solution from here.
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain))
{
UserPrincipal up = UserPrincipal.FindByIdentity(pc, usr.SoeId);
return up.DisplayName;
// or return up.GivenName + " " + up.Surname;
}
But it does not work.
I use windows authentication. Impersonation is set to true. Please help me.
change the identity of your ApplicationPool to run using domain user.
in iis 6 right-click your application pool, go to Identity tab and set a domain user under which the pool will run.
in iis 7 right-click your application pool, select advance settings, under process model you'll find Identity, change it to use domain user.
you can also pass a domain user and pass to PrincipalContest Constructor
using (PrincipalContext context = new PrincipalContext(
ContextType.Domain,
"name of your domain",
"container of your domain",
"user#domain", //create a user in domain for context creation purpose.. this username will be constant.. you can keep it in app config
"password")){
UserPrincipal up = UserPrincipal.FindByIdentity(pc, usr.SoeId);
return up.DisplayName;
}
if your domain name is dom.com then your container would be something like DC=dom,DC=com and the user name should be given as user#dom.com or dom\user
Use this:
// find currently logged in user
UserPrincipal adUser = null;
using (HostingEnvironment.Impersonate())
{
var userContext = System.Web.HttpContext.Current.User.Identity;
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, ConfigurationManager.AppSettings["AllowedDomain"], null,
ContextOptions.Negotiate | ContextOptions.SecureSocketLayer);
adUser = UserPrincipal.FindByIdentity(ctx, userContext.Name);
}
You must wrap any 'context' calls in HostingEnvironment.Impersonate

Ldap connection in .net C#

I have an application where I can send emails. Now am asked to use ldap to authenticate the user email. Am very new to this concept. I have been given a ldap server link. No idea how to proceed with that. Any article or hits will be greatly helpful.
Here is the code am trying with
public static UserDetail GetUserDetails(string EmailId, string domainName)
{
UserDetail userDetail = new UserDetail();
try
{
string filter = string.Format("(&(ObjectClass={0})(sAMAccountName={1}))", "person", EmailId);
string[] properties = new string[] { "fullname" };
DirectoryEntry adRoot = new DirectoryEntry("LDAP://" + domainName, null, null, AuthenticationTypes.Secure);
DirectorySearcher searcher = new DirectorySearcher(adRoot);
searcher.SearchScope = SearchScope.Subtree;
searcher.ReferralChasing = ReferralChasingOption.All;
searcher.PropertiesToLoad.AddRange(properties);
searcher.Filter = filter;
SearchResult result = searcher.FindOne();
DirectoryEntry directoryEntry = result.GetDirectoryEntry();
string displayName = directoryEntry.Properties["displayName"[0].ToStrin();
string firstName = directoryEntry.Properties["givenName"][0].ToString();
string lastName = directoryEntry.Properties["sn"][0].ToString();
string emailId = directoryEntry.Properties["mail"][0].ToString();
userDetail.EmailId = emailId;
}
catch (Exception)
{
}
return userDetail;
}
I want to achieve it on click of search button. How do I call the method and pass variables.
If you're on .NET 3.5 or newer, 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 the e-mail of "bruce#example.com"
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.EmailAddress = "bruce#example.com";
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
// try to find that user
UserPrincipal found = srch.FindOne() as UserPrincipal;
if(found != null)
{
// do whatever here - "found" is the user that matched the e-mail given
}
else
{
// there wasn't any user with that e-mail address in your AD
}
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. Or see the MSDN documentation on the System.DirectoryServices.AccountManagement namespace.
Of course, depending on your need, you might want to specify other properties on that "query-by-example" user principal you create:
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.
Given the input of emailAddress (type string) this code will search the LDAP directory for a user with a matching email address and return some information on the user:
string fullName = string.Empty;
string givenName = string.Empty;
string distinguishedName = string.Empty;
string sAMAccountName = string.Empty;
using (var context = new PrincipalContext(ContextType.Domain, "DOMAIN"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (Principal result in searcher.FindAll())
{
var de = result.GetUnderlyingObject() as DirectoryEntry;
if (de.Properties["cn"].Value.ToString().Contains(" "))
{
//var userEntry = new DirectoryUser(de.Properties["sAMAccountName"].Value.ToString());
var currentUserEmail = de.Properties["mail"].Value.ToString().ToLower();
if (currentUserEmail == emailAddress)
{
if (de.Properties["cn"].Value != null)
fullName = de.Properties["cn"].Value.ToString();
if (de.Properties["givenName"].Value != null)
givenName = de.Properties["givenName"].Value.ToString();
if (de.Properties["distinguishedName"].Value != null)
distinguishedName =de.Properties["distinguishedName"].Value.ToString();
if (de.Properties["sAMAccountName"].Value != null)
sAMAccountName = de.Properties["sAMAccountName"].Value.ToString();
}
}
}
}
}
It requires a reference to :
System.DirectoryServices;
System.DirectoryServices.AccountManagement;
One caveat I would like to mention is, directory look up routines can be quite slow. If you have 100,000 users on your domain, this process will take a while to run. WHat I tend to do, is dump the output of a directory search to a database table on a regular basis, and perform any lookups on that table. The frequency of the database dumps will of course depend on your business logic. Sometimes I simply truncate the table before performing a new dump, and in other circumstances, I dump to a 'staging' table, and only apply 'delta' updates to the active directoy record table.
Connect to the directory server, using SSL if possible. Promoting a non-secure connection to a secure connection with the StartTLS extended operation is also possible.
Transmit a SEARCH request to the server that contains the base DN from which the client wishes the search to begin, the scope of the search (base, one-level, or sub-tree), a filter using the information the LDAP client knows that will narrow the search results to the desired user, and the attribute 1.1.
The server will respond with the a SEARCH response containing the number of entries that matched the search request parameters and the distinguished names of each entry that matched.
Transmit a BIND request to the directory server over the secure connection. The BIND request contains a distinguished name and the credentials for the distinguished name
The directory server will verify the credentials and return a BIND response with an integer result code indicating whether the credentials matched those stored in the server database
see also
LDAP: programming practices
LDAP: search practices

Categories

Resources