Limitation on using PrincipalContext & DomainContext, to retrive Active directory users - asp.net

I have added the following code inside my asp.net mvc web application model class, to retrive the current AD users:-
public List<DomainContext> GetADUsers(string term=null)
{
List<DomainContext> results = new List<DomainContext>();
string ADServerName = System.Web.Configuration.WebConfigurationManager.AppSettings["ADServerName"];
using (var context = new PrincipalContext(ContextType.Domain, ADServerName))
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
var searchResults = searcher.FindAll();
foreach (Principal p in searchResults)
{
if (term == null || p.SamAccountName.ToString().ToUpper().StartsWith(term.ToUpper()))
{
DomainContext dc = new DomainContext();
dc.DisplayName = p.DisplayName;
dc.UserPrincipalName = p.UserPrincipalName;
dc.Name = p.Name;
dc.SamAccountName = p.SamAccountName ;
dc.DistinguishedName = p.DistinguishedName;
results.Add(dc);
}
}
}
return results;
}
I am now on the development machine , where AD is on the same machine as the asp.net mvc web application runs. And there is no need to provide username or password to access the AD. But I have the following questions about using my above approach on production server :-
Will the same approach work well if the AD and the asp.net mvc (deployed on IIS ) are not on the same machine?
Will I be able to provide username and password to access the active directory?
What are the general requirements I should achieve to be able to allow the Domaincontext class to access AD on remote servers ?
Thanks I advance for any help.
Regards

I think you're asking if you're able to use the same code if the web server is not apart of the Active Directory domain. PrincipalContext does have an overload for username and password to allow for credentials to be used to connect, instead of relying on the machine having enough permissions to read from the directory.
As for permissions, grant as few as possible. I would get your system administrator involved to create a the account. You maybe able to use Service Accounts which were introduced in Windows Server 2008 to allow for the authentication to happen.

Related

SharePoint High trust provider hosted app - user impersonation - Site minder

We are using SiteMinder to authenticate user but all we get from site minder is user identity in header:
ASP.NET Authentication with Siteminder
However since we are using high trust provider hosted SharePoint app we have access to tokenHelper.cs but impersonating a user requires System.Security.Principal.WindowsIdentity
My questions are:
How to get WindowsIdentity in this case?
OR
How to extend tokenHelper to impersonate user just with user identity(without windowsIdentity)?
Check this blog by Steve Peschka. I have set up provider hosted app in SiteMinder protected SharePoint 2013 using that blog. To impersonate a user you need to create a ClaimsIdentity of the user and insert it to the HttpContext as current user. Sample code for that below:
var identity = new ClaimsIdentity(AuthenticationTypes.Federation, "http://schemas.xmlsoap.org/claims/useridentifier", String.Empty);
identity.AddClaim(new Claim("http://schemas.xmlsoap.org/claims/useridentifier", userId, "http://www.w3.org/2001/XMLSchema#string"));
identity.AddClaim(new Claim(ClaimTypes.Email, smtp, "http://www.w3.org/2001/XMLSchema#string"));
identity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sip", nameIdentifier, "http://www.w3.org/2001/XMLSchema#string"));
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
Set this ClaimsPrincipalas the Httpcontext user.
The claim values to be passed are smtp= email of user , nameidentifier=loginname of user , userId= Account name of user
I will explain above scenario with my SP+Siteminder environment.
First of all you cant get the ClientContext of the site which is protected by site-minder.
You can only get clientContext of the site using internal url of site [http://hostname:port/sites/xyz].
To get the currenct user :-
var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);
// We store internal url of webapplication in web.config
string strAdminSiteURL = ConfigurationManager.AppSettings["AdminSiteURL"].ToString();
// We have written one function to convert site-minder url to internal url
string webUrl = Helper.Helper.GetInternalSiteUrl(strAdminSiteURL, spContext.SPHostUrl.ToString());
// Use internal url to create client-context
using (ClientContext clientContext = new ClientContext(webUrl))
{
clientContext.AuthenticationMode = ClientAuthenticationMode.FormsAuthentication;
clientContext.FormsAuthenticationLoginInfo = new FormsAuthenticationLoginInfo(uName, pswd);
Web web = clientContext.Web;
clientContext.Load(web);
clientContext.ExecuteQuery();
// Load SP user from login name found from httpcontext
string currentSPUser = string.Concat("<<FBAIdentity>>", User.Identity.Name);
var currentUser = clientContext.Web.EnsureUser(currentSPUser);
clientContext.Load(currentUser);
clientContext.ExecuteQuery();
}
above code will work fine if authentication mode is FBA and will help you in getting current user.

User impersonation between asp.net and ssrs

I have a web application that has application pool configured using domain account. In SSRS, domain account is given the browser access to the folder and SSRS report is configured with proper credentials.
My question is if SSRS report is launched from the web application, will SSRS report be opened under domain account or my account. Currently, it is giving error message as \ doesn't have sufficient permissions to access the report location.
You just need to set a fixed user in the web config along with the SSRS address. This way you set a default for the site to use instead of depending on the user running the site:
Example from a WPF app but very similar to ASP.NET in code behind.
<Button x:Name="btnGetViewerRemoteData" Content="Remote" Click="ReportViewerRemote_Load"/>
Reference name of element in code behind, ensure you import Namespace for 'Microsoft.Reporting.WinForms' (or ASP.NET equivalent).
private void ResetReportViewer(ProcessingMode mode)
{
this.reportViewer.Clear();
this.reportViewer.LocalReport.DataSources.Clear();
this.reportViewer.ProcessingMode = mode;
}
private ICredentials giveuser(string aUser, string aPassword, string aDomain)
{
return new NetworkCredential(aUser, aPassword, aDomain);
}
private void ReportViewerRemoteWithCred_Load(object sender, EventArgs e)
{
ResetReportViewer(ProcessingMode.Remote);
var user = giveuser("User", "Password", "Domain");
reportViewer.ServerReport.ReportServerCredentials.ImpersonationUser = (System.Security.Principal.WindowsIdentity)user;
;
reportViewer.ServerReport.ReportServerUrl = new Uri(#"http:// (server)/ReportServer");
reportViewer.ServerReport.ReportPath = "/Test/ComboTest";
DataSourceCredentials dsCrendtials = new DataSourceCredentials();
dsCrendtials.Name = "DataSource1";
dsCrendtials.UserId = "User";
dsCrendtials.Password = "Password";
reportViewer.ServerReport.SetDataSourceCredentials(new DataSourceCredentials[] { dsCrendtials });
reportViewer.RefreshReport();
}
I hard coded my example but you can have the server, user and password be in a config file. Although security of password may be a concern so depending on your organization so it may be preferable to hard code it or mask it first.

Cannot acces users Active Directory password information on production server

I'm developing an MVC application and I have a routine that gets the currently logged on users password info and it works fine on my PC but when I publish my application to a live server on the domain, I don't seem to be able to gain access to the AD information. I have used very similar code in a currently running asp.net web application and it works just fine. I compared security settings on both applications and they look identical. Here is the routine:
public int GetPasswordExpiration()
{
PrincipalContext domain = new PrincipalContext(ContextType.Domain);
string currUserName = WindowsIdentity.GetCurrent().Name;
UserPrincipal currLogin = UserPrincipal.FindByIdentity(domain, currUserName);
DateTime passwordLastSet = currLogin.LastPasswordSet.Value; //here is where it chokes***
int doyPasswordSet = passwordLastSet.DayOfYear;
int doy = DateTime.Today.DayOfYear;
int daysSinceLastset = (doy - doyPasswordSet);
int daysTilDue = (120 - daysSinceLastset);
return (daysTilDue);
}
I am an administrator on the domain so I think I have an application permissions issue, but since the failing application has the same permissions as the working application, I'm not sure where to look next. Any help is appreciated.
I'm answernig my own question because I want to post the code that works. Wiktor Zychla nailed it when asking if WindowsIdentity.GetCurrent().Name applied to the identity of the application pool rather than the logged in user. As a matter of fact it did, thanks Wiktor!
Here is the modified code that works. I did change the way I got the users identity (explained why below).
Controller Code:
using MyProject.Utilities; // Folder where I created the CommonFunctions.cs Class
var cf = new CommonFunctions();
string user = User.Identity.Name;
ViewBag.PasswordExpires = cf.GetPasswordExpiration(user);
Code in CommonFunctions
public int GetPasswordExpiration(string user)
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
UserPrincipal currLogin = UserPrincipal.FindByIdentity(ctx, user);
DateTime passwordLastSet = currLogin.LastPasswordSet.Value;
int doyPasswordSet = passwordLastSet.DayOfYear;
int doy = DateTime.Today.DayOfYear;
int daysSinceLastset = (doy - doyPasswordSet);
int daysTilDue = (120 - daysSinceLastset);
return (daysTilDue);
}
The one thing that clued me in was that I decided to just run all the code in my controller and when I did, I got a red squiggly saying "The name WindowsIdentity does not exist in this context":
string currUserName = WindowsIdentity.GetCurrent().Name;
Also, the reason I retrieved User.Identity.Name in the Controller and passed it to the function is because once I got things working and wanted to thin out my controller, I tried to get User.Identity.Name in the function but I got another red squiggly with the same message under User in this line:
string user = User.Identity.Name;
So I figure this is a .net thing and just went with getting the User.Identiy.Name in the controller, pass it to the function and all is well. This one really tested my patience and I hope this post can help someone else.

Active Directory Authentication

I am have made one web application in asp.net.In my project Authentication was done by matching the username and password in database.But now client ask me for the auto login in application with the help Of Active Directory authentication. Client ask suggest me to use the Email Id of user in AD for the authentication.
I tried to fetch the records in the AD, I could fetch the Fullname of user but I couldn't get the Email id,
I tried the code:
System.Security.Principal.WindowsIdentity wi = System.Security.Principal.WindowsIdentity.GetCurrent();
string[] a = Context.User.Identity.Name.Split('\\');
System.DirectoryServices.DirectoryEntry ADEntry = new System.DirectoryServices.DirectoryEntry("WinNT://" + a[0] + "/" + a[1]);
string Name = ADEntry.Properties["FullName"].Value.ToString();
Further more I Use DirectorySearcher but it genterates Error that Coulnot search the record in the client server..
I had the exact same situation while making a portal for a company.
If they dont want you to get into their AD then what you can do is to request for the NTLogins of the people who will be given access to the portal. make a simple table which have their NTLogin and simply authenticate using the system from which the portal is being accessed.
Check out the sample code i used.
// Checking if the user opening this page is listed in the allowed user list against their NT login.
String sUser = Request.ServerVariables["LOGON_USER"].ToLower();
sUser = sUser.Replace("wt\\", "");
//Authentication using a custom auth method.
DatabaseOperations authenticateUser = new DatabaseOperations();
if (!authenticateUser.authenticate(sUser))
{
//unauthorized users will be redirected to access denied page.
Server.Transfer("AccessDenied.aspx", true);
}
And making sure that you have authentication mode to windows in your web.config file
<authentication mode="Windows"></authentication>
Hope this helps.
For reading AD data, i use this class. It is setup for our AD, but basically you can just pass in all the "fields" you want to find, in the params.
But you need to know what field holds the email address. Sysinternals made a pretty good tool for browsing AD, to figure out what you are looking for, called ADExplorer.
But I don't understand why you need to look in the AD? Can you not assume that the user is already authenticated, if they are on the network, and then rely on the windows identity?
public static Hashtable GetAttributes(string initials, params string[] Attribute)
{
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://ADNAME");
DirectorySearcher ADSearcher = new DirectorySearcher(directoryEntry);
ADSearcher.Filter = "(sAMAccountName=" + initials + ")";
foreach (string para in Attribute)
{
ADSearcher.PropertiesToLoad.Add(para);
}
SearchResult adSearchResult = ADSearcher.FindOne();
Hashtable hshReturns = new Hashtable();
foreach (string para in Attribute)
{
string strReturn = "";
if (adSearchResult.Properties[para].Count == 0)
strReturn = "";
else
strReturn = ((ResultPropertyValueCollection)adSearchResult.Properties[para])[0].ToString();
hshReturns.Add(para, strReturn);
}
return hshReturns;
}

How to connect to an active directory server?

I'm using below code for connecting to an active directory server and retrieving its users.
But my web server is not in sub domain. Can I connect to it?
Or I should include its Ip address or something else?
DirectoryEntry entry = new DirectoryEntry("LDAP://dps.com", "Raymond", "xxxxxxx");
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = ("(&(objectCategory=person)(objectClass=user))");
foreach (SearchResult result in mySearcher.FindAll())
{
ResultPropertyCollection myResultPropColl = result.Properties;
DataRow dr=reader.Tables[0].NewRow();
dr[0]=myResultPropColl["samaccountname"][0].ToString()+"#"+Domain;
reader.Tables[0].Rows.Add(dr);
Response.Write(myResultPropColl["samaccountname"][0].ToString());
}
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context - connects to the current default domain
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find user by name
UserPrincipal user = UserPrincipal.FindByIdentity("John Doe");
// find all users in your AD directory - set up a "query-by-example"
// template to search for; here: a UserPrincipal, which is not locked out
UserPrincipal userTemplate = new UserPrincipal(ctx);
userTemplate.IsAccountLockedOut = false;
// create a PrincipalSearcher, based on that search template
PrincipalSearcher searcher = new PrincipalSearcher(userTemplate);
// enumerate all users that this searcher finds
foreach(Principal foundPrincipal in searcher.FindAll())
{
UserPrincipal foundUser = (foundPrincipal as UserPrincipal);
// do something with the userTemplate
}
The new S.DS.AM makes it really easy to play around with users and groups in AD:
If you cannot upgrade to S.DS.AM, what you need to do is make sure to use a proper LDAP string to connect to your server. That string should be something like:
LDAP://servername/OU=Users,DC=YourCompany,DC=com
The servername is optional - you can also leave that out. But the LDAP string needs to be made up of at least one DC=xxxxx string, and possibly other LDAP segments.

Resources