Impersonation and SharePoint 5
Part1: http://dotnetjunkies.com/WebLog/victorv/archive/2005/04/20/69524.aspx
Part2: http://dotnetjunkies.com/WebLog/victorv/archive/2005/04/20/69519.aspx
Part3: http://dotnetjunkies.com/WebLog/victorv/archive/2005/04/20/69521.aspx
Part4: http://dotnetjunkies.com/WebLog/victorv/archive/2005/06/30/128887.aspx
Julien Lepine got inspired by my code and created his own version which looks very neat! I changed some naming, but I definitely like this version of impersonation.
Note that the admin account and password is not embedded in the code; Julien uses the revert to ApplicationPool identity trick I explained in part 2.
Julien's class also implements the IDisposable interface, now using the impersonation class is very elegant:
using(Identity impersonate = Identity.ImpersonateAdmin())
{
// ... Do whatever job you want as the AppPool
}
When the scope leaves the using, the impersonation is automatically undone and the class garbage collected. Well done, Julien and thanks for the feedback!
Futher more, Patrick Tisseghem describes another way to do 'dangerous' actions in SharePoint using COM+. The assembly with the SharePoint code is registered with COM+ under the SharePoint admin account. Whenever your application uses the code, COM+ makes sure the code is run under the specified identity. Read Patricks article here.
This is Julien's code:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
/// <summary>
/// This class manages the creation of an impersonation to the Application
/// Pool's context for a defined scope.
///
/// It just wraps the .Net class for impersonation, thus allowing easier
/// use.
///
/// This class is made for use with the C# 'using' keyword, as in the
/// example below:
/// <code>
/// using(Identity impersonate = Identity.ImpersonateAdmin())
/// {
/// // ... Do whatever job you want as the AppPool
/// }
/// // Back to the original, you don't have to worry about reverting,
/// // this is automatically done with the IDisposable interface
/// </code>
/// </summary>
public sealed class Identity: IDisposable
{
/// <summary>
/// Windows identity used for the Application Pool
/// </summary>
private static WindowsIdentity _appPoolIdentity;
/// <summary>
/// Gets the windows identity used for the Application Pool
/// </summary>
private static WindowsIdentity AppPoolIdentity
{
get
{
// Lock current type to ensure thread safety on
// identity creation.
lock(typeof(Identity))
{
if(_appPoolIdentity == null)
{
// Create a new handle from this one
IntPtr token = WindowsIdentity.GetCurrent().Token;
// Throw an exception if we have an empty token
if(token == IntPtr.Zero)
{
throw new ApplicationException("Unable to fetch AppPool's identity token !");
}
// Create a duplicate of the user's token in order to use it for impersonation
if(! DuplicateToken(token, 2, ref token))
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to duplicate AppPool's identity token !");
}
// Throw an exception if we were unable to duplicate the token
if(token == IntPtr.Zero)
{
throw new ApplicationException("Unable to duplicate AppPool's identity token !");
}
// Store app pool's identity
_appPoolIdentity = new WindowsIdentity(token);
// Free the windows unmanaged resource
CloseHandle(token);
}
return _appPoolIdentity;
}
}
}
/// <summary>
/// This function returns the current user's login name.
/// </summary>
public static string CurrentUserName
{
get { return WindowsIdentity.GetCurrent().Name; }
}
/// <summary>
/// Stores the currently available Windows Impersonation context.
/// </summary>
private WindowsImpersonationContext _context;
/// <summary>
/// Stores the app pool's identity context.
/// </summary>
private WindowsImpersonationContext _selfContext;
/// <summary>
/// Private constructor, static function accessed class.
/// </summary>
private Identity()
{
// Try catch structure to ensure we don't change context in case
// we had an error duplicating the token.
try
{
_selfContext = WindowsIdentity.Impersonate(IntPtr.Zero); // REVERT to AppPool identity!
_context = AppPoolIdentity.Impersonate();
}
catch
{
// Close the context
UndoImpersonation();
// Rethrow the exception
throw;
}
}
/// <summary>
/// This method creates a new impersonation context.
/// </summary>
public static Identity ImpersonateAdmin()
{
return new Identity();
}
/// <summary>
/// This method closes the current impersonation context in order revert the user
/// to his real principal.
/// </summary>
public void UndoImpersonation()
{
if(_context != null)
{
_context.Undo();
_context = null;
}
if(_selfContext != null)
{
_selfContext.Undo();
_selfContext = null;
}
}
/// <summary>
/// Duplicates a token in order to have it working for impersonation.
/// </summary>
/// <param name="hToken_">Initial token to be duplicated</param>
/// <param name="impersonationLevel_">Level of impersonation needed</param>
/// <param name="hNewToken_">Reference to the new token created</param>
/// <returns>True if the call succeeded, false otherwise.</returns>
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern bool DuplicateToken(IntPtr hToken_, int impersonationLevel_, ref IntPtr hNewToken_);
/// <summary>
/// Closes an unmanaged handle in order to free allocated resources.
/// </summary>
/// <returns>True if the call succeeded, false otherwise.</returns>
[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
#region IDisposable Members
/// <summary>
/// This method disposes the current object, it frees all resources used by this class.
/// </summary>
public void Dispose()
{
Dispose(true);
// Ensure I'm garbage collected.
GC.SuppressFinalize(this);
}
/// <summary>
/// This method disposes the current object, it frees all resources used by this class.
/// </summary>
/// <param name="disposing_">Do actual disposing or not.</param>
private void Dispose(bool disposing_)
{
if(disposing_)
{
this.UndoImpersonation();
}
}
#endregion
}
(published by permission)