Como impersonar (impersonate) un bloque de código
En algunas oportunidades es necesario ejecutar ciertas tareas o procesos con algún usuario que tenga otros permisos o privilegios distintos al usuario actual con que se está ejecutando el proceso principal o aplicación.
Como práctica realizada de forma habitual, pero que a su vez es muy riesgosa, se ejecutan las aplicaciones con permisos administrativos o de administrador, permitiendo que cualquier tipo de acceso a dispositivos o recursos, se hagan con todos los privilegios.
Una forma de sobrellevar este problema es utilizar impersonalización o suplantación (impersonate) en la ejecución de ciertos procesos como también de funciones o cierto código, es decir, que parte de toda mi aplicación se ejecute con otro usuario y otros permisos. No necesitamos ejecutar toda la aplicación con un permiso, sino que podemos ejecutar sólo algunas líneas con este nuevo usuario.
Veamos como se compone la función y su clase correspondiente para impersonar o suplantar un usuario. La clase no es necesaria crearla si van a implementar esta función dentro de alguna librería de clases de su aplicación.
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
public class Impersonalizacion
{
private const int LOGON32_PROVIDER_DEFAULT = 0;
private const int LOGON32_LOGON_INTERACTIVE = 2;
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", EntryPoint="DuplicateToken", ExactSpelling=false, CharSet=CharSet.Auto, SetLastError=true)]
public static extern int DuplicateToken(IntPtr ExistingTokenHandle, int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);
public static WindowsImpersonationContext WinLogOn(string strUsuario, string strClave, string strDominio)
{
IntPtr tokenDuplicate = new IntPtr(0);
IntPtr tokenHandle = new IntPtr(0);
if (LogonUser(strUsuario, strDominio, strClave, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref tokenHandle))
if (DuplicateToken(tokenHandle, 2, ref tokenDuplicate) != 0)
return (new WindowsIdentity(tokenDuplicate)).Impersonate();
return null;
}
}
Si hay cierto código que parece complicado, sólo deben enfocar su atención en una sola función, la llamada WinLogOn. Esta función recibe 3 parámetros, la cuenta de usuario, la clave y el dominio contra el que deberá ser validado el usuario.
Esta función retorna un objeto del tipo System.Security.Principal.WindowsImpersonationContext en caso de ser exitoso el login o null (nothing en VB.NET) en caso de no poder validar el usuario contra el dominio exigido.
Una vez que se tenga el objeto WindowsImpersonationContext, todas las líneas de código que se ejecuten posteriormente, lo harán con los permisos y privilegios de este nuevo usuario, hasta el llamado del método Undo() del objeto de tipo WindowsImpersonationContext. Cuando se llama a Undo(), la aplicación vuelve a correr con el usuario que estaba antes ejecutando la aplicación.
Veamos entonces código donde se muestre la usabilidad de la impersonalización de usuarios. Esta es una aplicación de consola.
class Class1
{
[STAThread]
static void Main(string[] args)
{
WindowsImpersonationContext _objContext = null;
Console.WriteLine(QuienSoy());
_objContext = Impersonalizacion.WinLogOn("patrick", "clave", "tango");
if (_objContext != null)
{
Console.WriteLine(QuienSoy());
_objContext.Undo();
}
Console.WriteLine(QuienSoy());
Console.ReadLine();
}
internal static string QuienSoy()
{
return WindowsIdentity.GetCurrent().Name;
}
}
NOTA: Es importante garantizar que se produzca el Undo aunque haya alguna excepción de por medio dentro del código que se está impersonando. Si la línea marcada de rojo lanzase una excepción, el código que se ejecute después, que en este caso no son las líneas de más abajo sino que podría ser un catch o un finally de cualquier método que haya llamado a esta función, se hará con los privilegios del usuario específico, y no necesariamente con el usuario que estaba ejecutando la aplicación anteriormente. Esto sucederá por que no se ha realizado el logout o Undo.
Aplicando la corrección el código anterior, éste quedaría:
if (_objContext != null)
{
try
{
Console.WriteLine(QuienSoy());
}
finally
{
_objContext.Undo();
}
}
Patrick Mac Kay.
Septiembre 2004.