Sharepoint Single Sign-On, Impersonation and the Double-Hop Problem

31 10 2008

How do you overcome the identity double hop problem?

Windows credentials can only make one “hop” between machines on a network. The first hop is from the user’s browser to the web server; from here, to get to another machine on your network, a second hop is required.

There are two ways to work around this problem: 1) establish a delegation relationship between the web server and the other network machine, and configure the AD domain to allow Kerberos Protocol Transition, or 2) use the Win32 LogonUser API to switch to the user’s identity on the web server before making that single hop out to the other network machine.

Sharepoint Single Sign-On uses the second approach. The first approach is complex and probably akin to using a sledge hammer to crack a nut.

The great thing about the Sharepoint SSO service is that when creating Enterprise Application Definition’s, you can decide what credential fields are stored, so you can store, obviously, User names and Passwords, DB connection strings, Domain names and, well, other stuff you can put in a string.

So, as an example, a web part needs to collect information from a network machine to display in it’s UI to a user. You have two choices here, either the resource access needs to be done under the security context of the user (impersonation model), or, the resource access can be done under the security context of some ad-hoc user account (the trusted sub-system model).

In summary the webpart will retrieve security credentials from SSO, create an impersonation security context with those credentials using the LogonUser API, perform the resource access and then undo the impersonation.

We can overcome the double-hop problem using Sharepoint SSO while fulfilling both security models;

1. Trusted Sub-system Model

Create an SSO Enterprise Application Definition of type Group – all users will access network resources using the same credentials:

MbosDoDefSSO Display Name:   Mbos ESB Domain Access
  Type: Group
  Fields:  
 
Username (Unmasked) = esbprocess
Password (Masked) = *****
Domain (Unmasked) = MIT

2. Impersonation Model

Create an SSO Enterprise Application Definition of type Individual – all users will access network resources using their own credentials:

MbosLoDefSSO Display Name:   Mbos ESB LogData Access
  Type: Individual
  Fields:  
 
Username (Unmasked) = hardingp
Password (Masked) = *****
Domain (Unmasked) = MIT

First you’ll need to configure Sharepoint SSO, try google or this post here.

So assuming that you’ve configured SSO and set up these EAD’s, the next requirement is some code to do the impersonation which you can write, find on google or copy this one here.

Finally, you’ll want some code to get credentials from SSO, which I’ve reproduced below.

One thing to note, is that if you create an EAD of type Individual (Windows Authentication), when you call ISsoProvider.GetCredentials, a SingleSignonException exception will be generated if SSO doesn’t have credentials stored for the calling user, for the EAD.

In this case, you can make a call to ISsoProvider.GetCredentialManagementUrl to get the credential management URL to allow the user to enter their SSO credentials (for this EAD).

Putting these pieces together, accessing network resources either on behalf of the calling user, or as an ad-hoc user, can be accomplished as shown below;

    // get sso creds
    var ssoApp = SharepointSSO.GetEnterpriseApplication(C_SSO_EadId);
    
    using (new Network.Impersonator(ssoApp.Fields["Username"], ssoApp.Fields["Domain"], ssoApp.Fields["Password"],
               Network.LogonType.LOGON32_LOGON_NEW_CREDENTIALS,
               Network.LogonProvider.LOGON32_PROVIDER_WINNT50))
    {
          // perform your network resource access here

    }

Sharepoint Single Sign-On accessor code.

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.SharePoint.Portal.SingleSignon;

namespace Tools.Sharepoint.SSO
{
 class SSOApplication
 {
  public IDictionary<string, string> Infomation
  { get; set; }

  public IDictionary<string, string> Fields
  { get; set; }
 }

 class SharepointSSO
 {
  private static string ConvertSecureStringToString(System.Security.SecureString pValue)
  {
   IntPtr lValuePointer = IntPtr.Zero;
   string lValueAsString;
   try
   {
    lValuePointer = Marshal.SecureStringToBSTR(pValue);
    lValueAsString = Marshal.PtrToStringBSTR(lValuePointer);
   }
   catch (Exception ex)
   {
    lValueAsString = ex.Message;
   }
   finally
   {
    if (lValuePointer != IntPtr.Zero)
     Marshal.ZeroFreeBSTR(lValuePointer);
   }
   return lValueAsString;
  }

  public static SSOApplication GetEnterpriseApplication(string eadID)
  {
   if (string.IsNullOrEmpty(eadID)) throw new ArgumentException("an EAD ID is required", "eadID");

   var ssoProvider = SsoProviderFactory.GetSsoProvider();
   var ssoCreds = ssoProvider.GetCredentials(eadID);
   var ssoApp = ssoProvider.GetApplicationInfo(eadID);
   var ssoFields = ssoProvider.GetApplicationFields(eadID);

   var creds = new SSOApplication
                {
                 Infomation = new Dictionary<string, string>(),
                 Fields = new Dictionary<string, string>()
                };
   creds.Infomation["ID"] = ssoApp.ApplicationName;
   creds.Infomation["Display Name"] = ssoApp.ApplicationFriendlyName;
   creds.Infomation["Contact Name"] = ssoApp.ContactName;
   creds.Infomation["Type"] = ssoApp.Type.ToString();

   for (int idx = 0; idx < ssoFields.Length; idx++)
   {
    string ssoEvidence = ConvertSecureStringToString(ssoCreds.Evidence[idx]);
    creds.Fields[ssoFields[idx].Field] = ssoEvidence;
   }

   return creds;
  }
 }
}




Exploring Sharepoint Connected Web Parts with IWebPartRow

31 10 2008

With WSS 3.0 and MOSS 2007 you can develop web parts which can be connected together as providers and consumers. WSS 3.0 and MOSS 2007 now uses the .NET 2.0 connectable web part technology. This sample demonstrates a consumer webpart which will consume row data provided by OOTB web parts using the standard IWebPartRow interface. Custom developed web parts can implement their own connection interfaces of course, but the advantage of using the standard interfaces is that they can be integrated with the OOTB ones.

The IWebPartRow interface provides access to a row of data provided by the provider web part. Using the OOTB List View Webpart (the standard webparts which represent standard and custom lists / document libraries) as an example provider, these web parts view list data using one of the views attached to the list (and you can customise the web parts copy of that view also). So in terms of a row of data, a provider such as this will offer the currently selected row in the providers view. The contents of this row (i.e the columns) are the list columns selected for display in the web parts view. If the list is a Document or Form library their will also be a column called DocuUrl, which is the site relative url of the Document/Form library items SPFile. If the list is just a list, their will also be a column called Attachments.

Changing the columns displayed on the provider web parts view, then, obviously changes the data row columns offered to consumers.

As far as web part life cycle events go, things follow the normal web part life cycle model (see this post), however, the call to SetConnectionInterface() follows OnLoad(). This call connects the 2 web parts, but no data has been transferred as yet. It turns out that the data transfer action follows a pull model (from the consumers point of view) and it is the consumers resonsibility to pull the data from the provider as and when ready.

The consumer can pull the data from the provider whenever they require, except that it can obviously only be done after the SetConnectionInterface() method has been called. To retrieve the data, the consumer makes a call to _provider.GetRowData(callback_method). The callback_method is implemented in the consumer code, the single parameter to this callback method represents the row data and must be cast to the required type. Note, that the provider calls the consumers callback_method synchronously.

The consumer web part developed here will be connected to a Document library list view provider shown below, note the columns displayed on the view.

When connected to this provider web part, the consumer displays as follows.

The code for the consumer web part is show here.

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using System.Data;
using Microsoft.SharePoint;
namespace MbosSampleWebParts
{
 [Guid("4517a282-9681-4f7c-ac9f-b12fb200ab07")]
 public class MbosViewerWebPart : WebPart
 {
  private DataRowView _dataRow;
  private IWebPartRow _provider;
  private readonly StringBuilder _sb = new StringBuilder();
  
  public MbosViewerWebPart()
  {
   _sb.Append("- Ctor\n");
   PreRender += MbosViewerWebPart_PreRender;
  }
  protected override void OnLoad(EventArgs e)
  {
   base.OnLoad(e);
   _sb.Append(state("OnLoad"));
  }
  protected override void OnInit(EventArgs e)
  {
   base.OnInit(e);
   _sb.Append(state("OnInit"));
  }
  void MbosViewerWebPart_PreRender(object sender, EventArgs e)
  {
   _sb.Append(state("PreRender.1"));
//   if (_provider != null)
//    _provider.GetRowData(GetRowData);
   _sb.Append(state("PreRender.2"));
  }
  protected override void RenderContents(HtmlTextWriter writer)
  {
   base.RenderContents(writer);
   _sb.Append(state("RenderContents"));
   if (_provider != null)
   {
    try
    {
     PropertyDescriptorCollection schemaProps = _provider.Schema;
     if (schemaProps != null && schemaProps.Count > 0 && _dataRow != null)
     {
      DataRow providerRow = _dataRow.Row;
      // enumerate data columns
      _sb.Append("\n");
      foreach (DataColumn column in providerRow.Table.Columns)
       _sb.AppendFormat("- data column: {0} = {1}\n", column.ColumnName ?? "n/a", providerRow[column]);
      // enumerate schema
      _sb.Append("\n");
      foreach (PropertyDescriptor prop in schemaProps)
       _sb.AppendFormat("- schema entry: {0} is {1}\n", prop.Name, prop.PropertyType);
      try
      {
       // get the SPFile of the connected item (if doc/form library item)
       var colDocUrl = providerRow.Table.Columns["DocUrl"];
       if (colDocUrl != null)
       {
        var connectedFile = SPContext.Current.Web.GetFile(providerRow[colDocUrl].ToString());
        _sb.AppendFormat("\n- SPFile.Author.Name: {0}\n", connectedFile.Author.Name);
       }
      }
      catch (Exception e)
      { _sb.Append(e.Message + "\n"); }
     }
     else
      _sb.Append(" - No data\n");
    }
    catch (Exception ex)
    { _sb.Append(ex.Message +"\n"); }
   }
   else
    _sb.Append("- Not connected\n");
   writer.Write(_sb.ToString().Replace("\n", "< br >")); // replace LF with HTML line-break
  }
  protected override void Render(HtmlTextWriter writer)
  {
   _sb.Append(state("Render"));
   base.Render(writer);
  }
  protected override void CreateChildControls()
  {
   base.CreateChildControls();
   _sb.Append(state("CreateChildControls.1"));
   if (_provider != null)
    _provider.GetRowData(GetRowData);
   _sb.Append(state("CreateChildControls.2"));
  }
  private string state(string method)
  {
   var sb = new StringBuilder();
   sb.AppendFormat("- {0}", method);
   if (_provider != null)
    sb.Append(", _provider not null");
   else
    sb.Append(", _provider null");
   
   if (_dataRow != null)
    sb.Append(", _dataRow not null");
   else
    sb.Append(", _dataRow null");
   sb.Append("\n");
   return sb.ToString();
  }
  private void GetRowData(object rowData)
  {
   _sb.Append(state("GetRowData.1"));
   try
   {
    _dataRow = (DataRowView)rowData;
   }
   catch
   { _dataRow = null; }
   _sb.Append(state("GetRowData.2"));
  }
  
  [ConnectionConsumer("Row")]
  public void SetConnectionInterface(IWebPartRow provider)
  {
   _provider = provider;
   _sb.Append(state("SetConnectionInterface"));
  }
 }
}




Exercising the Sharepoint (MOSS) 2007 Single Sign-On Service SDK

30 10 2008

In an effort to understand how you might exploit the SSO for your own custom development in Sharepoint (MOSS) I wrote a Web Part to enumerate SSO Applications and Credentials, as shown below.

The code for the web part is quite simple, as is the SSO SDK itself (at least as an SSO consumer).

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Web.UI;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Portal.SingleSignon;

namespace Sharepoint.WebParts
{
 [Guid("45c8266e-6a1b-4223-97fa-5cc3e65c5629")]
 public class SSOInfoViewWebPart : System.Web.UI.WebControls.WebParts.WebPart
 {
  private static string ConvertSecureStringToString(System.Security.SecureString pValue)
  {
   IntPtr lValuePointer = IntPtr.Zero;
   string lValueAsString;
   try
   {
    lValuePointer = Marshal.SecureStringToBSTR(pValue);
    lValueAsString = Marshal.PtrToStringBSTR(lValuePointer);
   }
   catch (Exception ex)
   {
    lValueAsString = ex.Message;
   }
   finally
   {
    if (lValuePointer != IntPtr.Zero)
     Marshal.ZeroFreeBSTR(lValuePointer);
   }
   return lValueAsString;
  }

  protected override void CreateChildControls()
  {
   base.CreateChildControls();

   ISsoProvider ssoProvider = SsoProviderFactory.GetSsoProvider();
   var listOfEAD = ssoProvider.GetApplicationDefinitions();

   var sb = new StringBuilder();

   var ssoPI = ssoProvider.GetSsoProviderInfo();

   sb.Append("
<tr>
<td><b>SSO Provider:</td>
");
   sb.Append("
<td style=\"padding-left:10px;\">Vendor:</td>
");
   sb.AppendFormat("
<td>{0}</td>
</tr>
", ssoPI.Vendor);
   sb.Append("
<tr>
<td>&nbsp;</td>
");
   sb.Append("
<td style=\"padding-left:10px;\">Version:</td>
");
   sb.AppendFormat("
<td>{0}</td>
</tr>
", ssoPI.Version);
   sb.Append("
<tr>
<td>&nbsp;</td>
");
   sb.Append("
<td align=\"top\" style=\"padding-left:10px;\">Assembly:</td>
");
   sb.AppendFormat("
<td>{0}</td>
</tr>
", ssoPI.AssemblyName);

   var wi = WindowsIdentity.GetCurrent(true);
   if (wi != null)
   {
    var wic = WindowsIdentity.Impersonate(IntPtr.Zero);
    sb.Append("
<tr>
<td><b>Process Identity:</td>
");
    sb.AppendFormat("
<td style=\"padding-left:10px;\">{0}</td>
<td style=\"padding-left:10px;\">({1}, {2}{3}{4}{5}{6})</td>
</tr>
",
                    WindowsIdentity.GetCurrent().Name,
                    WindowsIdentity.GetCurrent().AuthenticationType,
                    WindowsIdentity.GetCurrent().ImpersonationLevel,
                    WindowsIdentity.GetCurrent().IsAnonymous ? ", Anonymous" : "",
                    WindowsIdentity.GetCurrent().IsAuthenticated ? ", Authenticated" : "",
                    WindowsIdentity.GetCurrent().IsGuest ? ", Guest" : "",
                    WindowsIdentity.GetCurrent().IsSystem ? ", System" : "");
    wic.Undo();
   }

   sb.AppendFormat("
<tr>
<td><b>{0} Identity:</td>
", wi != null ? "Thread" : "Process");
   sb.AppendFormat("
<td style=\"padding-left:10px;\">{0}</td>
<td style=\"padding-left:10px;\">({1}, {2}{3}{4}{5}{6})</td>
</tr>
",
                   WindowsIdentity.GetCurrent().Name,
                   WindowsIdentity.GetCurrent().AuthenticationType,
         WindowsIdentity.GetCurrent().ImpersonationLevel,
                   WindowsIdentity.GetCurrent().IsAnonymous ? ", Anonymous" : "",
                   WindowsIdentity.GetCurrent().IsAuthenticated ? ", Authenticated" : "",
                   WindowsIdentity.GetCurrent().IsGuest ? ", Guest" : "",
                   WindowsIdentity.GetCurrent().IsSystem ? ", System" : "");

   sb.Append("
<tr>
<td><b>ASP.NET Identity:</td>
");
   sb.AppendFormat("
<td style=\"padding-left:10px;\">{0}</td>
<td style=\"padding-left:10px;\">({1}{2})</td>
</tr>
",
         Context.User.Identity.Name,
         Context.User.Identity.AuthenticationType,
         Context.User.Identity.IsAuthenticated ? ", Authenticated" : "");

   sb.Append("
<tr>
<td><b>Sharepoint Identity:</td>
");
   sb.AppendFormat("
<td style=\"padding-left:10px;\">{0}</td>
<td style=\"padding-left:10px;\">(ID:{1}, {2}{3}{4}{5})</td>
</tr>
",
         SPContext.Current.Web.CurrentUser.Name,
         SPContext.Current.Web.CurrentUser.ID,
         SPContext.Current.Web.CurrentUser.LoginName,
         SPContext.Current.Web.CurrentUser.IsSiteAdmin ? ", SiteAdmin" : "",
         SPContext.Current.Web.CurrentUser.IsDomainGroup ? ", DomainGroup" : "",
         SPContext.Current.Web.CurrentUser.IsSiteAuditor ? ", SiteAuditor" : "");

   sb.Append("
<tr>
<td><b>SSO User:</b></td>
<td colspan=\"2\" style=\"padding-left:10px;\">");
   try
   { sb.AppendFormat("{0}", ssoProvider.GetCurrentUser()); }
   catch (Exception ex)
   { sb.AppendFormat("<i>n/a ({0})</i>", ex.Message); }
   sb.Append("</td>
</tr>
");

   sb.AppendFormat(
    "
<tr>
<td style=\"padding-bottom:5px;padding-top:10px\" colspan=\"3\"><b>SSO Enterprise Application Definitions</b></td>
</tr>
");
   foreach (var ead in listOfEAD)
   {
    string credManUrl = "#";
    if (ead.Type == Application.ApplicationType.Individual || ead.Type == Application.ApplicationType.IndividualWindows)
     credManUrl = ssoProvider.GetCredentialManagementURL(ead.ApplicationName).ToString();

    sb.Append("
<tr>");
    sb.AppendFormat("
<td style=\"border-top:solid 1px #B0BDC2;\"><b><a href=\"{1}\">{0}</a></b></td>
",
                    ead.ApplicationName, credManUrl);
    sb.Append("
<td style=\"padding-left:10px;border-top:solid 1px #B0BDC2;\">Display&nbsp;Name:&nbsp;&nbsp;</td>
");
    sb.AppendFormat("
<td style=\"border-top:solid 1px #B0BDC2;\">{0}</td>
", ead.ApplicationFriendlyName);
    sb.Append("</tr>
");

    sb.Append("
<tr>
<td>&nbsp;</td>
");
    sb.AppendFormat("
<td style=\"padding-left:10px;\">Type:</td>
<td>{0}</td>
", ead.Type);
    sb.Append("</tr>
");

    sb.Append("
<tr>
<td>&nbsp;</td>
");
    sb.Append("
<td style=\"padding-left:10px;\">Fields:</td>
<td>&nbsp;</td>
");
    sb.Append("</tr>
");

    sb.Append("
<tr>
<td>&nbsp;</td>
");
    sb.Append("
<td colspan=\"2\" style=\"padding-left:10px;padding-bottom:5px;\">");
    sb.Append("
<div style=\"padding-left:20px\">");

    var listOfEadFields = ssoProvider.GetApplicationFields(ead.ApplicationName);
    var listOfCreds = ssoProvider.GetCredentials(ead.ApplicationName);

    //    sb.AppendFormat("\"UserName\" = {0}
", ConvertSecureStringToString(listOfCreds.UserName));
    //    sb.AppendFormat("\"Password\" = {0}
", ConvertSecureStringToString(listOfCreds.Password));

    for (int idx = 0; idx < listOfEadFields.Length; idx++)
    {
     var eadField = listOfEadFields[idx];
     string ssoEvidence = ConvertSecureStringToString(listOfCreds.Evidence[idx]);
     sb.AppendFormat("{0} ({1}) = {2}
", eadField.Field, eadField.Mask ? "Masked" : "Unmasked", ssoEvidence);
    }

    sb.Append("</div>
");
    sb.Append("</td>
</tr>
");
   }

   Controls.Add(new LiteralControl("
<table cellspacing=\"0\" cellpadding=\"0\">"));
   Controls.Add(new LiteralControl(sb.ToString()));
   Controls.Add(new LiteralControl("</table>
"));
  }
 }
}




.NET (C#) Impersonation with Network Credentials

30 10 2008

I required a C# class to enable ad-hoc user account impersonation for accessing resources both on the local machine and also on network machines, which I’ve reproduced here.

Of note, if you require impersonation in order to access network resources, you would intuitively select the logon type of LOGON32_LOGON_NETWORK, this however doesn’t work, as according to MSDN, this type of logon is used for fast authentication where the credentials are not added to the local credential cache.

If you require the impersonated logon to have network credentials, you must select LOGON32_LOGON_NEW_CREDENTIALS as your logon type, which requires that you select LOGON32_PROVIDER_WINNT50 as the logon provider type.

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace Tools.Network
{
 public enum LogonType
 {
  LOGON32_LOGON_INTERACTIVE = 2,
  LOGON32_LOGON_NETWORK = 3,
  LOGON32_LOGON_BATCH = 4,
  LOGON32_LOGON_SERVICE = 5,
  LOGON32_LOGON_UNLOCK = 7,
  LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher
  LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher
 };

 public enum LogonProvider
 {
  LOGON32_PROVIDER_DEFAULT = 0,
  LOGON32_PROVIDER_WINNT35 = 1,
  LOGON32_PROVIDER_WINNT40 = 2,
  LOGON32_PROVIDER_WINNT50 = 3
 };

 public enum ImpersonationLevel
 {
  SecurityAnonymous = 0,
  SecurityIdentification = 1,
  SecurityImpersonation = 2,
  SecurityDelegation = 3
 }

 class Win32NativeMethods
 {
  [DllImport("advapi32.dll", SetLastError = true)]
  public static extern int LogonUser( string lpszUserName,
       string lpszDomain,
       string lpszPassword,
       int dwLogonType,
       int dwLogonProvider,
       ref IntPtr phToken);

  [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  public static extern int DuplicateToken( IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);

  [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  public static extern bool RevertToSelf();

  [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
  public static extern bool CloseHandle(IntPtr handle);
 }

 /// <summary>
 /// Allows code to be executed under the security context of a specified user account.
 /// </summary>
 /// <remarks> 
 ///
 /// Implements IDispose, so can be used via a using-directive or method calls;
 ///  ...
 ///
 ///  var imp = new Impersonator( "myUsername", "myDomainname", "myPassword" );
 ///  imp.UndoImpersonation();
 ///
 ///  ...
 ///
 ///   var imp = new Impersonator();
 ///  imp.Impersonate("myUsername", "myDomainname", "myPassword");
 ///  imp.UndoImpersonation();
 ///
 ///  ...
 ///
 ///  using ( new Impersonator( "myUsername", "myDomainname", "myPassword" ) )
 ///  {
 ///   ...
 ///   [code that executes under the new context]
 ///   ...
 ///  }
 ///
 ///  ...
 /// </remarks>
 public class Impersonator : IDisposable
 {
  private WindowsImpersonationContext _wic;

  /// <summary>
  /// Begins impersonation with the given credentials, Logon type and Logon provider.
  /// </summary>
  ///
<param name="userName">Name of the user.</param>
  ///
<param name="domainName">Name of the domain.</param>
  ///
<param name="password">The password. <see cref="System.String"/></param>
  ///
<param name="logonType">Type of the logon.</param>
  ///
<param name="logonProvider">The logon provider. <see cref="Mit.Sharepoint.WebParts.EventLogQuery.Network.LogonProvider"/></param>
  public Impersonator(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider)
  {
   Impersonate(userName, domainName, password, logonType, logonProvider);
  }

  /// <summary>
  /// Begins impersonation with the given credentials.
  /// </summary>
  ///
<param name="userName">Name of the user.</param>
  ///
<param name="domainName">Name of the domain.</param>
  ///
<param name="password">The password. <see cref="System.String"/></param>
  public Impersonator(string userName, string domainName, string password)
  {
   Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT);
  }

  /// <summary>
  /// Initializes a new instance of the <see cref="Impersonator"/> class.
  /// </summary>
  public Impersonator()
  {}

  /// <summary>
  /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  /// </summary>
  public void Dispose()
  {
   UndoImpersonation();
  }

  /// <summary>
  /// Impersonates the specified user account.
  /// </summary>
  ///
<param name="userName">Name of the user.</param>
  ///
<param name="domainName">Name of the domain.</param>
  ///
<param name="password">The password. <see cref="System.String"/></param>
  public void Impersonate(string userName, string domainName, string password)
  {
   Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT);
  }

  /// <summary>
  /// Impersonates the specified user account.
  /// </summary>
  ///
<param name="userName">Name of the user.</param>
  ///
<param name="domainName">Name of the domain.</param>
  ///
<param name="password">The password. <see cref="System.String"/></param>
  ///
<param name="logonType">Type of the logon.</param>
  ///
<param name="logonProvider">The logon provider. <see cref="Mit.Sharepoint.WebParts.EventLogQuery.Network.LogonProvider"/></param>
  public void Impersonate(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider)
  {
   UndoImpersonation();

   IntPtr logonToken = IntPtr.Zero;
   IntPtr logonTokenDuplicate = IntPtr.Zero;
   try
   {
    // revert to the application pool identity, saving the identity of the current requestor
    _wic = WindowsIdentity.Impersonate(IntPtr.Zero);

    // do logon & impersonate
    if (Win32NativeMethods.LogonUser(userName,
        domainName,
        password,
        (int)logonType,
        (int)logonProvider,
        ref logonToken) != 0)
    {
     if (Win32NativeMethods.DuplicateToken(logonToken, (int)ImpersonationLevel.SecurityImpersonation, ref logonTokenDuplicate) != 0)
     {
      var wi = new WindowsIdentity(logonTokenDuplicate);
      wi.Impersonate(); // discard the returned identity context (which is the context of the application pool)
     }
     else
      throw new Win32Exception(Marshal.GetLastWin32Error());
    }
    else
     throw new Win32Exception(Marshal.GetLastWin32Error());
   }
   finally
   {
    if (logonToken != IntPtr.Zero)
     Win32NativeMethods.CloseHandle(logonToken);

    if (logonTokenDuplicate != IntPtr.Zero)
     Win32NativeMethods.CloseHandle(logonTokenDuplicate);
   }
  }

  /// <summary>
  /// Stops impersonation.
  /// </summary>
  private void UndoImpersonation()
  {
   // restore saved requestor identity
   if (_wic != null)
    _wic.Undo();
   _wic = null;
  }
 }
}




Sharepoint Identity Contexts

30 10 2008

When writing pages or web parts for Sharepoint (or indeed just ASP.NET) their are 3 security identity contexts to take into account.

1. Process/Thread Identity

This is the identity returned by calling WindowsIdentity.GetCurrent(). If ASP.NET impersonation is not enabled the identity returned will be the process identity (that of the IIS Application Pool). If ASP.NET impersonation is enabled the identity returned will be the thread (impersonation) identity of the currently authenticated user (or the anonymous user).

Calling WindowsIdentity.GetCurrent(true) will return the identity of the thread (impersonated) user only if impersonation is enabled, otherwise it returns null. Calling WindowsIdentity.GetCurrent(false) or WindowsIdentity.GetCurrent() returns the identity of the thread (impersonated) user if impersonation is enabled and if impersonation is disabled the identity returned will be the process identity (that of the IIS Application Pool). In this way you can determine whether your ASP.NET application has impersonation enabled. ASP.NET impersonation can be configured to impersonate the currently authenticated user (or the anonymous user), or impersonate a fixed “Application” identity.

2. ASP.NET User Identity

This identity is the ASP.NET user identity which is returned by calling Context.User.Identity. This is the identity of the currently authenticated user (or the anonymous user) making a HTTP request. Depending on how ASP.NET impersonation is configured it may be different from the process/thread identity.

3. Sharepoint User Identity

This identity is the Sharepoint (2007) representation of the ASP.NET user above, and is returned by calling SPContext.Current.Web.CurrentUser.