I'm using the following Microsoft example. Every time the WindowsIdentity instance calls .Impersonate(), nothing happens. No error, no impersonation.
Both before and after the call, the current identity is always the AppPool identity.
I've also tried another example found online, the Impersonator class, and the same thing happens.
The only modification I've made to those examples is changing LOGON32_LOGON_INTERACTIVE to LOGON32_LOGON_NETWORK in the LogOnUser call, since using Interactive always returned a 0 error.
It's an ASP.NET 4.0 app running on a Win2k8 server trying to impersonate a user in AD.
EDIT:
I hadn't mentioned this originally, but I modified the Microsoft example and turned it into a class so that I can could it from my ASP.NET app. I also have impersonate=true in web.config.
I eventually found a helper class where I had done something similar a couple of years ago. The helper implements IDisposable, so just wrap your file access code in a "using" like this:-
using (Impersonate imp = new Impersonate())
{
// Code in here will run under the identity specified in the helper
}
And here is the code for the helper class (I've removed the "usings" to save some space). You'll notice the user account is hardcoded in the constructor:
internal class Impersonate : IDisposable
{
private const int LOGON32_LOGON_INTERACTIVE = 2;
private const int LOGON32_PROVIDER_DEFAULT = 0;
private bool _disposed = false;
private WindowsImpersonationContext _context = null;
[DllImport("advapi32.dll")]
private static extern int LogonUserA(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
internal Impersonate()
{
WindowsIdentity tempWindowsIdentity;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
string domain = "<whatever>";
string username = "<whatever>";
string password = "<whatever>";
try
{
if (RevertToSelf())
{
if (LogonUserA(username, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
_context = tempWindowsIdentity.Impersonate();
}
}
}
}
finally
{
if (token != IntPtr.Zero)
{
CloseHandle(token);
}
if (tokenDuplicate != IntPtr.Zero)
{
CloseHandle(tokenDuplicate);
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Dispose any managed resources here.
}
// Stop the impersonation.
_context.Undo();
_disposed = true;
}
}
}
Give it a go and let me know how you get on...
Andrew
The example you link is for a console application. If you have an ASP.Net web application where you want your code to be executed under the security context of the visitor you can enable ASP.Net impersonation in the IIS authentication settings (in the IIS manager MMC snap in).
Giving write access to the App_Data folder for the Users group fixed the issue. Not sure what that has to do with impersonation though.
Related
From a web handler (xxx.ashx) I need to run a command as the same user using the web page and sending the request. I have IIS setup using impersonation, have code in my .ashx that shows it is impersonating the user, and then I use the C# Process.start() to run the command. (It's a .cmd file I am running)
The problem is that the .cmd file runs as the user assigned to the Application Pool and not the web user. I even tried this code:
WindowsImpersonationContext impersonationContext = ((WindowsIdentity)System.Security.Principal.WindowsIdentity.GetCurrent()).Impersonate();
Could this be an issue: I wrote the .ashx file and put just it under the IIS wwwroot/myapp folder and call it from a URL from a web browser. I have setup the Application Pool using the DefaultAppPool user and an OS user, but no difference.
I'm a novice with IIS & ASP too so this is like working in a black box.
We had to use the native windows method: CreateProcessAsUser(), and had to call DuplicateTokenEx() to duplicate the security token.
<%# WebHandler Language="C#" Class="batchRunSAS" %>
using System;
using System;
using System.IO;
using System.Web;
using System.Diagnostics;
using System.Security.Principal;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.Runtime.InteropServices;
using System.ComponentModel;
public class batchRun : IHttpHandler
{
public void ProcessRequest (HttpContext context)
{
//Call DuplicateTokenEx
//https://msdn.microsoft.com/en-us/library/ms682429(VS.85).aspx
//http://stackoverflow.com/questions/9095909/createprocessasuser-creating-window-in-active-session
Process process = null;
process = NativeMethods.CreateProcessAsUser("C:\\temp\\test.exe");
}
public bool IsReusable
{
get { return false; }
}
[SuppressUnmanagedCodeSecurity]
class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public Int32 dwProcessID;
public Int32 dwThreadID;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public Int32 Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public const int GENERIC_ALL_ACCESS = 0x10000000;
public const int CREATE_NO_WINDOW = 0x08000000;
[
DllImport("kernel32.dll",
EntryPoint = "CloseHandle", SetLastError = true,
CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)
]
public static extern bool CloseHandle(IntPtr handle);
[
DllImport("advapi32.dll",
EntryPoint = "CreateProcessAsUser", SetLastError = true,
CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)
]
public static extern bool
CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment,
string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
ref PROCESS_INFORMATION lpProcessInformation);
[
DllImport("advapi32.dll",
EntryPoint = "DuplicateTokenEx")
]
public static extern bool
DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel, Int32 dwTokenType,
ref IntPtr phNewToken);
public static Process CreateProcessAsUser(string filename, string args)
{
var hToken = WindowsIdentity.GetCurrent().Token;
var hDupedToken = IntPtr.Zero;
var pi = new PROCESS_INFORMATION();
var sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
try
{
if (!DuplicateTokenEx(
hToken,
GENERIC_ALL_ACCESS,
ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary,
ref hDupedToken
))
throw new Win32Exception(Marshal.GetLastWin32Error());
var si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "";
var path = Path.GetFullPath(filename);
var dir = Path.GetDirectoryName(path);
// Revert to self to create the entire process; not doing this might
// require that the currently impersonated user has "Replace a process
// level token" rights - we only want our service account to need
// that right.
using (var ctx = WindowsIdentity.Impersonate(IntPtr.Zero))
{
if (!CreateProcessAsUser(
hDupedToken,
path,
string.Format("\"{0}\" {1}", filename.Replace("\"", "\"\""), args),
ref sa, ref sa,
false, 0, IntPtr.Zero,
dir, ref si, ref pi
))
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return Process.GetProcessById(pi.dwProcessID);
}
finally
{
if (pi.hProcess != IntPtr.Zero)
CloseHandle(pi.hProcess);
if (pi.hThread != IntPtr.Zero)
CloseHandle(pi.hThread);
if (hDupedToken != IntPtr.Zero)
CloseHandle(hDupedToken);
}
}
}
}
I am currently trying to add functionality to our company's ASP.NET website which will allow end users to upload files generated from the website into their own Google Drive (within the same company).
The company has it's own Google Domain which includes Mail, Drive, Contacts, Calendar (and pretty much everything else). It uses SAML to authenticate with our Active Directory set up which links in to Google.
It should be noted that this code works OK with my #gmail.com account. With the companies Google App Domain account, I get the invalid credentials error (shown at the bottom). I have spoken with our Google Admin who claims that I have no restrictions on my account with regards to Drive API access. Please also bear in mind that the API ID/Secret's were created with my company account rather than a Gmail account.
using System;
using System.IO;
using System.Text;
using System.Web;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2;
using Google.Apis.Authentication.OAuth2;
using Google.Apis.Drive.v2;
using Google.Apis.Util;
namespace GoogleTesting
{
public partial class GoogleAuth : System.Web.UI.Page
{
private static readonly string CLIENTID = "xxxxxxxxx.apps.googleusercontent.com";
private static readonly string CLIENTSECRET = "xxxxxxxxxxxxxxxxxxxxxx";
private static readonly string APIKEY = "xxxxxxxxxxx";
private static readonly string REDIRECTURI = http://localhost:61111/default.aspx";
private static readonly string[] SCOPES = new[] { DriveService.Scopes.Drive.GetStringValue() };
private static DriveService driveService;
private static OAuth2Authenticator<WebServerClient> authenticator;
private static IAuthorizationState _state;
private IAuthorizationState AuthState
{
get
{
return _state ?? HttpContext.Current.Session["GoogleAuthState"] as IAuthorizationState;
}
}
private OAuth2Authenticator<WebServerClient> CreateAuthenticator()
{
var provider = new WebServerClient(GoogleAuthenticationServer.Description, CLIENTID, CLIENTSECRET);
var authenticator = new OAuth2Authenticator<WebServerClient>(provider, GetAuthorization);
return authenticator;
}
private IAuthorizationState GetAuthorization(WebServerClient client)
{
IAuthorizationState state = AuthState;
if (state != null)
{
if (state.AccessTokenExpirationUtc.Value.CompareTo(DateTime.Now.ToUniversalTime()) > 0)
return state;
else
state = null;
}
state = client.ProcessUserAuthorization(new HttpRequestInfo(HttpContext.Current.Request));
if (state != null && (!string.IsNullOrEmpty(state.AccessToken) || !string.IsNullOrEmpty(state.RefreshToken)))
{
if (state.RefreshToken == null)
state.RefreshToken = "";
HttpContext.Current.Session["GoogleAuthState"] = _state = state;
return state;
}
client.RequestUserAuthorization(SCOPES, "", HttpContext.Current.Request.Url);
return null;
}
protected void Page_Load(object sender, EventArgs e)
{
if(authenticator == null)
authenticator = CreateAuthenticator();
if (driveService == null)
{
driveService = new DriveService(authenticator);
driveService.Key = APIKEY;
}
//We should now be authenticated and ready to use Drive API.
UploadFile();
}
private void UploadFile()
{
Google.Apis.Drive.v2.Data.File newFile = new Google.Apis.Drive.v2.Data.File { Title = "Test File", MimeType = "text/plain" };
byte[] byteArray = Encoding.ASCII.GetBytes("Body of the document.");
MemoryStream stream = new MemoryStream(byteArray);
FilesResource.InsertMediaUpload request = driveService.Files.Insert(newFile, stream, newFile.MimeType);
request.Convert = true;
request.Upload();
}
}
}
The problem occurs on the "request.Upload" line with the following exception.
Exception Details: System.Exception: Invalid Credentials
Source Error: Line 85: request.Upload();
Source File: C:\TMGGoogle\TMGGoogle\TMGGoogle\GoogleAuth.aspx.cs
Line: 85
Stack Trace:
[Exception: Invalid Credentials]
Google.Apis.Upload.ResumableUpload`1.Upload() +722
GoogleTesting.GoogleAuth.UploadFile()
Any help is much appreciated. Thanks.
Well, I figured out the root cause of the problem. It turns out that our company's Google Apps account had Docs API access turned off.
Our Google Apps Administrator has since turned it on and this has resolved the issue.
First, I apologize is this a duplicate, but I really couldn't find a similar problem anywhere.
The situation is I'm attempting to use the impersonation feature in asp.net to retrieve a file located on a network directory. When I specify the user in the web.config, it works fine:
<identity impersonate="true" userName="contoso\Jane" password="********" />
However, when I try using the following, I recieve a prompt to login to the site, which I'm never able to do successfully.
<identity impersonate="true"/>
My understanding of the latter example is that it will attempt to impersonate with the windows credential of whomever is currently viewing the page (via windows authentication). Is this not correct?
I should note, I do have windows authentication working properly in other areas of the app.
Thanks
EDIT
I should also mention, this is running on II6... and it just "feels" like a configuration issue...
I would go a other way with a extra class Impersonate.cs and You need a user, a password and a Domain.
Imperosnate.cs :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Security;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.IO;
using System.Text;
using System.Web;
namespace [YourProgramName] //You must change it
{
public class Impersonate
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern int LogonUser(string lpszUsername, string lpszDomain, string lpszPassword,
int dwLogonType, int dwLogonProvider, out int phToken);
[DllImport("kernel32.dll")]
private static extern int FormatMessage(int dwFlags, string lpSource, int dwMessageId, int dwLanguageId,
StringBuilder lpBuffer, int nSize, string[] Arguments);
private const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
private const int LOGON32_PROVIDER_DEFAULT = 0;
private const int FORMAT_MESSAGE_FROM_SYSTEM = 0x1000;
private static WindowsImpersonationContext winImpersonationContext = null;
public static void ImpersonateUser(string domain, string userName, string password)
{
//Benutzer einloggen
int userToken = 0;
bool loggedOn = (LogonUser(userName, domain, password, LOGON32_LOGON_NETWORK_CLEARTEXT,
LOGON32_PROVIDER_DEFAULT, out userToken) != 0);
if (loggedOn == false)
{
int apiError = Marshal.GetLastWin32Error();
StringBuilder errorMessage = new StringBuilder(1024);
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, null, apiError, 0, errorMessage, 1024, null);
throw new Exception(errorMessage.ToString());
}
WindowsIdentity identity = new WindowsIdentity((IntPtr)userToken);
winImpersonationContext = identity.Impersonate();
}
public static void UndoImpersonation()
{
if (winImpersonationContext != null)
{
winImpersonationContext.Undo();
}
}
}
}
Use it in your program:
string Admin = Properties.Settings.Default.Admin;
string AdminPassword = Properties.Settings.Default.AdminPassword;
string Domain = Properties.Settings.Default.Domain;
Impersonate.ImpersonateUser(Domain , Admin , AdminPassword);
//Your Code as the new User
Impersonate.UndoImpersonation();
hope it is what you search ^^
I have a web application that impersonates using the Win32 API. I recently returned from a trip to find the part of the application that impersonates failing.
The reason for the impersonation is that there is a network share that is used for a specific purpose. When my web application accesses that network share, it must impersonate a special account. For the sake of discussion, the uid is "Bob", the password is "1234", and the domain is "home".
Two weeks ago, I wrote my program to use the Win32 API to impersonate Bob and all was well. Now, the Win32 API indicates logon failure. I'm having trouble figuring out what could have changed. To be specific, the server indicates that it fails to impersonate Bob. The error occurs before the server actually tries to access a network share.
The bizarre thing is that if I connect to my web server with MSTSC, I can click on "Map Network Drive" in the Windows Explorer and access the files using Bob#home and password 1234. (I enter the uid and password by clicking on "Connect using a different user name" in the "Map Network Drive" dialog)
It seems that something different must be happening when I try to access my network share with impersonation versus using the Windows Explorer.
What is that difference?
Update: I feel like the answer to this problem is that there is some sort of logon permission that is being denied that is somehow not involved when I access my files through Windows Explorer.
Make sure that you're using the UNC path to access the folder:
string path = #"\\server\folder";
I don't know what your impersonation class looks like, but here's a class that I use for similar tasks:
/// <summary>
/// Leverages the Windows API (advapi32.dll) to programmatically impersonate a user.
/// </summary>
public class ImpersonationContext : IDisposable
{
#region constants
private const int LOGON32_LOGON_INTERACTIVE = 2;
private const int LOGON32_PROVIDER_DEFAULT = 0;
#endregion
#region global variables
private WindowsImpersonationContext impersonationContext;
private bool impersonating;
#endregion
#region unmanaged code
[DllImport("advapi32.dll")]
private static extern int LogonUserA(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
#endregion
#region constructors
public ImpersonationContext()
{
impersonating = false;
}
/// <summary>
/// Overloaded constructor and begins impersonating.
/// </summary>
public ImpersonationContext(string userName, string password, string domain)
{
this.BeginImpersonationContext(userName, password, domain);
}
#endregion
#region impersonation methods
/// <summary>
/// Begins the impersonation context for the specified user.
/// </summary>
/// <remarks>Don't call this method if you used the overloaded constructor.</remarks>
public void BeginImpersonationContext(string userName, string password, string domain)
{
//initialize token and duplicate variables
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
if (RevertToSelf())
{
if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
using (WindowsIdentity tempWindowsIdentity = new WindowsIdentity(tokenDuplicate))
{
//begin the impersonation context and mark impersonating true
impersonationContext = tempWindowsIdentity.Impersonate();
impersonating = true;
}
}
}
}
//close the handle to the account token
if (token != IntPtr.Zero)
CloseHandle(token);
//close the handle to the duplicated account token
if (tokenDuplicate != IntPtr.Zero)
CloseHandle(tokenDuplicate);
}
/// <summary>
/// Ends the current impersonation context.
/// </summary>
public void EndImpersonationContext()
{
//if the context exists undo it and dispose of the object
if (impersonationContext != null)
{
//end the impersonation context and dispose of the object
impersonationContext.Undo();
impersonationContext.Dispose();
}
//mark the impersonation flag false
impersonating = false;
}
#endregion
#region properties
/// <summary>
/// Gets a value indicating whether the impersonation is currently active.
/// </summary>
public bool Impersonating
{
get
{
return impersonating;
}
}
#endregion
#region IDisposable implementation
~ImpersonationContext()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (impersonationContext != null)
{
impersonationContext.Undo();
impersonationContext.Dispose();
}
}
}
#endregion
}
Using ImpersonationContext:
using (ImpersonationContext context = new ImpersonationContext("user", "password", "domain"))
{
if (context.Impersonating)
{
Process.Start(#"/Support/SendFax/SendFax.exe");
}
}
I'm currently developing my own AuthorizationManager, it looks something like that:
public class MyAuthorizationManager : ServiceAuthorizationManager
{
static bool initialize = false;
public override bool CheckAccess(OperationContext operationContext)
{
ServiceSecurityContext context = ServiceSecurityContext.Current;
string[] roles = Roles.GetRolesForUser(operationContext.ServiceSecurityContext.PrimaryIdentity.Name);
return roles.Count() > 0;
}
public override bool CheckAccess(OperationContext operationContext, ref System.ServiceModel.Channels.Message message)
{
MessageBuffer buffer = operationContext.RequestContext.RequestMessage.CreateBufferedCopy(int.MaxValue);
message = buffer.CreateMessage();
Console.WriteLine(message);
return base.CheckAccess(operationContext, ref message);
}
}
I would like to perform authorization check based on a service contract parameter, in example, if contract looks like:
[ServiceContract]
public interface IServerContract
{
[OperationContract]
[ServiceKnownType(typeof(ChildTypeOne))]
[ServiceKnownType(typeof(ChildTypeTwo))]
string SecuredMessage(ParentType incoming);
}
My goal is authorizing depending on type, in example, authorizing if incoming date is ChildTypeOne and deniying in case it was ChildTypeTwo.
I've checked "Message" and it looks like:
It must be decrypted
Seems to be highly dependent on binding
Is there any easy way to simply get parameter type?
Ok, i've figured out how to perform that. Anyway, if you know any better way to do so, let me know:
Here is the AuthorizationManager i'm using:
public class MyAuthorizationManager : ServiceAuthorizationManager
{
static bool initialize = false;
public override bool CheckAccess(OperationContext operationContext, ref System.ServiceModel.Channels.Message message)
{
bool returnedValue = base.CheckAccess(operationContext, ref message);
// messags in WCF are always read-once
// we create one copy to work with, and one copy to return back to the plumbing
MessageBuffer buffer = operationContext.RequestContext.RequestMessage.CreateBufferedCopy(int.MaxValue);
message = buffer.CreateMessage();
// get the username vale using XPath
XPathNavigator nav = buffer.CreateNavigator();
StandardNamespaceManager nsm = new StandardNamespaceManager(nav.NameTable);
nav = nav.SelectSingleNode("//#i:type",nsm);
returnedValue &= (nav.ToString() == "a:"+typeof(ChildTypeOne).Name);
return returnedValue;
}
public class StandardNamespaceManager : XmlNamespaceManager
{
public StandardNamespaceManager(XmlNameTable nameTable)
: base(nameTable)
{
this.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/");
this.AddNamespace("s11", "http://schemas.xmlsoap.org/soap/envelope/");
this.AddNamespace("s12", "http://www.w3.org/2003/05/soap-envelope");
this.AddNamespace("wsaAugust2004", "http://schemas.xmlsoap.org/ws/2004/08/addressing");
this.AddNamespace("wsa10", "http://www.w3.org/2005/08/addressing");
this.AddNamespace("i", "http://www.w3.org/2001/XMLSchema-instance");
}
}
}
Previous AuthorizationManager will work rejecting "ChildTypeTwo". You can use a RoleProvider in order to get role based on type.