Basically I'm running the same problem as this post Accessing mapped drives when impersonating in ASP.NET
I'm working on a legacy website and I need to allow the admins to change the site's logo, banners, etc, from an image file on their desktops to a mapped drive on the server.
So, their website is using impersonation whenever it needs to save on the drive, and it's working just fine; however I can't manage to make it work on their test environment nor in my test environment.
¿Any ideas? I've double checked user and password (the code doesn't specify domain) and that's not the issue.
Here's an excerpt from the code that handles impersonation:
public bool ImpersonateUser(String user, String password, String domain)
{
WindowsIdentity tempWindowsIdentity;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
if (RevertToSelf())
{
if (LogonUserA(user, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
if (impersonationContext != null)
{
CloseHandle(token);
CloseHandle(tokenDuplicate);
return true;
}
}
}
}
//... rest of the code
And a -sanitized- test:
if (impUtility.ImpersonateUser("user", "password", string.Empty))
{
fu.SaveAs(#"C:\Images\" + imgName);
}
I couldn't get that to work either.
Then I realized that even if I could implement it, there is an easier way.
What I did was share the folder on the target machine, and give only read/write permissions to the users that would be using my application.
//Impersonate user to save file on server
WindowsIdentity wi = (WindowsIdentity)User.Identity;
WindowsImpersonationContext wic = null;
try
{
wic = wi.Impersonate();
if (wi.IsAuthenticated)
asyncFileUpload.SaveAs(location);
}
catch (Exception ex)
{
//Log Error or notify here
success = false;
}
finally
{
if (wic != null)
wic.Undo();
}
I created an AD group for the users, and give read/write permissions for those users on the hidden shared drive. This makes it easier to maintain, since I don't have to create mapped drives for each user.
Related
I need to fetch the certificate, and would like to fetch the client, and there is no server, I could do this form:
public static X509Certificate2 EscolherCertificado(string serial)
{
var store = new X509Store("MY", StoreLocation.CurrentUser);
var Key = new RSACryptoServiceProvider();
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection collection = store.Certificates;
X509Certificate2Collection fcollection = collection.Find(X509FindType.FindBySerialNumber, serial, false);
if (fcollection.Count == 1)
{
return fcollection[0];
}
else { cod = "00000"; msgm = "not found"; return null; }
}
But when I publish on the server it does not work. Is there any way I can do this?
I can not get the client certificate, it returns error, because on the server there are no registered certificates.
EDIT
I have already been told that it is possible, I just do not know how to do it, the ways I tried does not work.
EDIT
Following this link, I did comply, but it does not work, it does not always find. What can I do to correct this problem?
public static X509Certificate2 EscolherCertificado(string serial)
{
X509Store userCaStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
try
{
userCaStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
X509Certificate2Collection findResult = certificatesInStore.Find(X509FindType.FindBySerialNumber, serial, true);
X509Certificate2 clientCertificate = null;
if (findResult.Count == 1)
{
clientCertificate = findResult[0];
}
else
{
throw new Exception("Unable to locate the correct client certificate.");
}
cod = "0000"; msgm = clientCertificate.ToString(); return clientCertificate;
}
catch
{
throw;
}
finally
{
userCaStore.Close();
}
As far as I know, you cannot reach the client Certificates Store.
To do that you have to code a propietary plugin for each browser platform and give the right access permissions to the client.
That's a very painful task.
I wish you good luck with it.
In my case we ended up developing an EXE which has full access to the client hardware and features of the platform.
Here's the code:
private void ElegirCert()
{
System.Security.Cryptography.X509Certificates.X509Store store = new System.Security.Cryptography.X509Certificates.X509Store("MY", System.Security.Cryptography.X509Certificates.StoreLocation.CurrentUser);
store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadOnly);
System.Security.Cryptography.X509Certificates.X509Certificate2Collection collection = (System.Security.Cryptography.X509Certificates.X509Certificate2Collection)store.Certificates;
System.Security.Cryptography.X509Certificates.X509Certificate2Collection fcollection = (System.Security.Cryptography.X509Certificates.X509Certificate2Collection)collection;//.Find(System.Security.Cryptography.X509Certificates.X509FindType.FindByTimeValid, DateTime.Now, false);
try
{
Cert = System.Security.Cryptography.X509Certificates.X509Certificate2UI.SelectFromCollection(fcollection, "Elegir", "Seleccione el certificado que desea utilizar", System.Security.Cryptography.X509Certificates.X509SelectionFlag.SingleSelection)[0];
}
catch (Exception e)
{
}
store.Close();
}
I still think that it is not possible to get the list of client certificates using the sole browser capabilities.
There is a way and it involves creating an extension which communicates with an Executable Native file that does the "hard work". That means, getting the full list of certificates and exposing it to the user.
I think that after that, the user chooses one, then the EXE asks for the cert store password (if it has one), then the exe digitally signs the hash and whatever...
I'm trying to authenticate against AD using application mode (ADAM), but keep getting unknown username or bad password. If I test the login in LDP.exe it logs in no problem, on simple bind. I've trawled through all similar posts with the same issue, but have not resolved it, any suggestions what I should be checking for?
private bool ValidateActiveDirectoryLogin(string Username, string Password)
{
bool Success = false;
System.DirectoryServices.DirectoryEntry Entry = new System.DirectoryServices.DirectoryEntry("LDAP://localhost:389/OU=Users,O=TestDirectory", Username, Password);
System.DirectoryServices.DirectorySearcher Searcher = new System.DirectoryServices.DirectorySearcher(Entry);
Searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree;
try
{
System.DirectoryServices.SearchResult Results = Searcher.FindOne();
Success = (Results != null);
}
catch (Exception ex)
{
Success = false;
throw;
}
return Success;
}
Determine what context your application is hitting AD with. If your ASP.NET application pool identity is one that is low privileged, it won't have enough permissions to query active directory. If you don't want to create a custom user to run the app pool as with appropriate permissions - you could use the LogonUser API to make your ValidateActiveDirectoryLogin call under the security context of that account.
Finally, you should consider using System.DirectoryServices.AccountManagement if you are using .NET 3.5 or above.
You can use code like
bool validCreds = false;
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
{
validCreds = context.ValidateCredentials( username, password );
}
I am using windows Authentication and accessing user name as.
IIdentity winId = HttpContext.Current.User.Identity;
string name = winId.Name;
but i want to get other details like User full name and EmailID.
Since you're on a windows network, then you need to query the Active directory to search for user and then get it's properties such as the email
Here is an example function DisplayUser that given an IIdentity on a windows authenticated network, finds the user's email:
public static void Main() {
DisplayUser(WindowsIdentity.GetCurrent());
Console.ReadKey();
}
public static void DisplayUser(IIdentity id) {
WindowsIdentity winId = id as WindowsIdentity;
if (id == null) {
Console.WriteLine("Identity is not a windows identity");
return;
}
string userInQuestion = winId.Name.Split('\\')[1];
string myDomain = winId.Name.Split('\\')[0]; // this is the domain that the user is in
// the account that this program runs in should be authenticated in there
DirectoryEntry entry = new DirectoryEntry("LDAP://" + myDomain);
DirectorySearcher adSearcher = new DirectorySearcher(entry);
adSearcher.SearchScope = SearchScope.Subtree;
adSearcher.Filter = "(&(objectClass=user)(samaccountname=" + userInQuestion + "))";
SearchResult userObject = adSearcher.FindOne();
if (userObject != null) {
string[] props = new string[] { "title", "mail" };
foreach (string prop in props) {
Console.WriteLine("{0} : {1}", prop, userObject.Properties[prop][0]);
}
}
}
gives this:
Edit: If you get 'bad user/password errors'
The account that the code runs under must have access the users domain. If you run code in asp.net then the web application must be run under an application pool with credentials with domain access. See here for more information
You can define a MyCustomIdentity by overriding from IIdentity and add your own properties etc.
Cast it to the specific Identity, for example WindowsIdentity
I have a website which requires users to enter their corporate network username and password. It then looks for that account in Active Directory and gets a list of any email addresses associated with that account.
The problem I am having is that ONE incorrect password is locking out an account. Our domain policy is that an account will lock out after three incorrect entries, so I am assuming that I am doing something wrong in my code. I am not very knowledgeable about Active Directory or .NET DirectoryServices in general, which may be apparent from my code. Here it is:
public ArrayList AuthenticateActiveDirectory(string Domain, string UserName, string Password)
{
// An error occurs if the username/password combo does not exist.
// That is how we know it is not a valid entry.
try
{
DirectoryEntry entry = new DirectoryEntry("LDAP://" + Domain, UserName, Password);
object nativeObject = entry.NativeObject;
ArrayList emails = new ArrayList();
DirectorySearcher ds = new DirectorySearcher(entry);
ds.Filter = "samaccountname=" + UserName;
ds.PropertiesToLoad.Add("mail");
SearchResult sr = ds.FindOne();
if (sr.Properties["mail"] != null)
{
for (int email = 0; email < sr.Properties["mail"].Count; email++)
{
emails.Add(sr.Properties["mail"][email]);
}
}
return emails;
}
catch (DirectoryServicesCOMException) { throw; }
catch (Exception) { throw; }
}
I did some searching and found some code (thanks to Ayende Rahien for the solution) to use that just authenticates and doesn't search for emails or anything else. I am using this prior to the other function, and it seems to be working fine. I am guessing that my other code is hitting AD more than once - at least 3 times - which is resulting in the lockout. Here is the code I am using now to just authenticate:
private bool Authenticate(string domain, string user, string password)
{
try
{
using (DirectoryEntry de = new DirectoryEntry("LDAP://" + domain,
user, password))
{
return de.NativeObject != null;
}
}
catch
{
return false;
}
}
I have a report saved on a SQL2005 reporting server, and I want to return a rendered PDF of this report. I've figured this out when working with a local *.rdlc file (and I've blogged about it), but not when the *.rdl resides on a reporting server. I am getting a 401 Not Authorized error at the line...
reportViewer.ServerReport.SetParameters(reportDefinition.ReportParameters);
Here's the method used to render the report.
public byte[] Render(IReportDefinition reportDefinition)
{
var reportViewer = new ReportViewer();
byte[] renderedReport;
try
{
var credentials = new WindowsImpersonationCredentials();
reportViewer.ServerReport.ReportServerUrl = new Uri("http://myssrsbox", UrlKind.Absolute);
reportViewer.ServerReport.ReportServerCredentials = credentials;
reportViewer.ServerReport.ReportPath = reportDefinition.Path;
// Exception is thrown on the following line...
reportViewer.ServerReport.SetParameters(reportDefinition.ReportParameters);
string mimeType;
string encoding;
string filenameExtension;
string[] streams;
Warning[] warnings;
renderedReport = reportViewer.ServerReport.Render(reportDefinition.OutputType, reportDefinition.DeviceInfo, out mimeType, out encoding, out filenameExtension, out streams, out warnings);
}
catch (Exception ex)
{
// log the error...
throw;
}
finally
{
reportViewer.Dispose();
}
return renderedReport;
}
The other thing that you're missing is the WindowsImpersonationCredentials class.
public class WindowsImpersonationCredentials : IReportServerCredentials
{
public bool GetFormsCredentials(out Cookie authCookie, out string userName, out string password, out string authority)
{
authCookie = null;
userName = password = authority = null;
return false;
}
public WindowsIdentity ImpersonationUser
{
get { return WindowsIdentity.GetCurrent(); }
}
public ICredentials NetworkCredentials
{
get { return null; }
}
public override string ToString()
{
return String.Format("WindowsIdentity: {0} ({1})", this.ImpersonationUser.Name, this.ImpersonationUser.User.Value);
}
}
Other things you may need to know...
This is running on an intranet, and impersonation is turned on.
Logging indicates that the impersonation user is being set correctly.
This does work when running in Visual Studio (http://localhost:devport), and it does work when running on my development box (http://localhost/myApplication). It does not work when running on our test or production servers.
I have tried solutions both with and without system.net.defaultProxy settings in web.config. Neither worked.
What am I doing wrong? Is it a server setting? Is it code? Is it web.config?
We did finally figure out the problem. Our network administrators have disabled double-hopping, so while the impersonation was correctly connecting as domain\jmeyer, the application was still attempting to connect to the SRS box with domain\web01$. Why is it set up like this? Because double-hopping is a massive security hole. (Or so I was told. Does this sound like something you would read on The Daily WTF?)
Our solution was to create a generic domain\ssrs_report_services user, and connect with that user with the following network credentials
public class CustomCredentials : IReportServerCredentials
{
public bool GetFormsCredentials(out Cookie authCookie, out string userName, out string password, out string authority)
{
authCookie = null;
userName = password = authority = null;
return false;
}
public WindowsIdentity ImpersonationUser
{
get { return null; }
}
public ICredentials NetworkCredentials
{
get { return new NetworkCredential("ssrs_report_services", "password", "domain") ; }
}
}
The above is the classic example solution that you can find all over the internets.
"Double hopping" is allowed - swith on Kerberos authentication ... (so long as it is working properly!)