miércoles, 4 de marzo de 2009

Impersonar Temporalmente

¿Alguna vez les ha pasado que están desarrollando un web service y requieren acceder a una carpeta compartida? ¿No? pues a mi me pasó. Necesitaba dejar un archivo en una carpeta compartida que de paso ya tenia un usuario y una contraseña particular para este tipo de tareas, por lo que no queda mas remedio de impersonar temporalmente.

¿Como? haciendo una clase sobrepuesta a las dlls de Windows que se requieren para tal propósito. Siguiendo este articulo se puede crear una clase similar a la siguiente que sirva a nuestros intereses:


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


namespace Foyland.Comun
{
//Esta clase impersonaliza un usuario temporalmente
public class Impersonalizacion
{
enum LogonSessionType : uint
{
Interactive = 2, //Esta tiene permisos sobre los recursos de red
Network, //Esta No tiene permisos sobre los recursos de red (curioso no?)
Batch,
Service,
NetworkCleartext = 8,
NewCredentials
}

enum LogonProvider : uint
{
Default = 0, // default (usar esta)
WinNT35, // usa una señales dummy para autenticar (sends smoke signals to authority)
WinNT40, // usa NTLM
WinNT50 // usa Kerberos o NTLM
}

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool LogonUser(
string principal,
string authority,
string password,
LogonSessionType logonType,
LogonProvider logonProvider,
out IntPtr token);

[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, LogonSessionType.Interactive,
LogonProvider.Default, out tokenHandle))

if (DuplicateToken(tokenHandle, 2, ref tokenDuplicate) != 0)
return (new WindowsIdentity(tokenDuplicate)).Impersonate();
return null;

}
}
}

Aqui termina la case que necesitamos para impersonar. Ahora necesitamos invocarla desde el webservice. Porción de codigo de ejemplo:

using System.Configuration;
using System.IO;
using System.Security.Principal;
.
.
.
//ya dentro del metodo apropiado

//Carga las variables que estan en el web.config
string usuario = ConfigurationManager.AppSettings.Get("USUARIO_DFS");
string password = ConfigurationManager.AppSettings.Get("PASSWORD_DFS");
string dominio = ConfigurationManager.AppSettings.Get("DOMINIO_DFS");

//crea el objeto necesario para impersonalizar
WindowsImpersonationContext _objContext = null;

//Despues de esta sentencia ya estamos impersonalizando con el usuario deseado
_objContext = Impersonalizacion.WinLogOn(usuario, password, dominio);
try
{
//Codigo para crear y guardar el archivo en la carperta compartida;
}
catch (Exception ex)
{
throw ex;
}
finally
{
//SIEMPRE debemos ejecutar el metodo Undo para regresar a las credenciales anteriores
_objContext.Undo();
}

Para lo que necesitaba funciona perfectamente.

4 comentarios:

Anónimo dijo...

Muy bueno y útil el artículo. Sin embargo me he encontrado un problema al desplegarlo en el servidor me da una excepción de seguridad:

Security Exception
Description: The application attempted to perform an operation not allowed by the security policy. To grant this application the required permission please contact your system administrator or change the application's trust level in the configuration file.

Exception Details: System.Security.SecurityException: Access is denied.


Source Error:


[No relevant source lines]


Source File: c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\aplicacion\92a5f12d\3813f1ab\App_Web_oazgzzca.1.cs Line: 0

Stack Trace:


[SecurityException: Access is denied.
]
System.Security.Principal.WindowsIdentity.SafeImpersonate(SafeTokenHandle userToken, WindowsIdentity wi, StackCrawlMark& stackMark) +2743162
System.Security.Principal.WindowsIdentity.Impersonate(IntPtr userToken) +81
System.CodeDom.Compiler.Executor.RevertImpersonation() +73
System.CodeDom.Compiler.TempFileCollection.SafeDelete() +25
Microsoft.CSharp.CSharpCodeGenerator.System.CodeDom.Compiler.ICodeCompiler.CompileAssemblyFromSourceBatch(CompilerParameters options, String[] sources) +206
System.CodeDom.Compiler.CodeDomProvider.CompileAssemblyFromSource(CompilerParameters options, String[] sources) +21
System.Xml.Serialization.Compiler.Compile(Assembly parent, String ns, CompilerParameters parameters, Evidence evidence) +593
System.Xml.Serialization.TempAssembly.GenerateAssembly(XmlMapping[] xmlMappings, Type[] types, String defaultNamespace, Evidence evidence, CompilerParameters parameters, Assembly assembly, Hashtable assemblies) +2024
System.Xml.Serialization.TempAssembly..ctor(XmlMapping[] xmlMappings, Type[] types, String defaultNamespace, String location, Evidence evidence) +120
System.Xml.Serialization.XmlSerializer.GenerateTempAssembly(XmlMapping xmlMapping, Type type, String defaultNamespace) +99
System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace) +348
System.Xml.Serialization.XmlSerializer..ctor(Type type) +6

Datos del servidor:
IIS6 Windows 2003 sp2. asp.net 2.0.50727.42

Si lo lanzo en el Visual Studio funciona.

¿Alguna sugerencia?
Gracias

Ángel

Foy dijo...

Primera asegúrate que la carpeta(folder) a la que quieres tener acceso realmente lo tenga y tal y como lo tienes configurado. Pensando en que el "acceso no permitido" no se deba a eso.

Sin embargo, si realmente se debe al nivel de confianza de la ejecución del código en el servidor y a las políticas de seguridad puedes seguir este articulo del sitio de soporte de Microsoft que indica como cambiarla

http://support.microsoft.com/kb/320268

Espero te sirva.

Anónimo dijo...

Gracias, pero sigue con el mismo error.

Me pasa una cosa muy curiosa. Desde un pc entro a la aplicación con mi usuario, al intentar la aplicación acceder al recurso compartido lanza la excepción. Desde otro pc entro a la aplicación con el usuario que se utiliza para hacer la impersonación, accede perfectamente al recurso compartido.
Entonces voy al pc donde estoy logeado con mi usuario, refresco el navegador y funciona.
A partir de ese momento y hasta que se reinicia el pool de aplicaciones en el servidor funciona correctamente.

¿Un poco extrano, no?

Ángel

Anónimo dijo...

Yo monte una red virtual con win xp y ws2003, todo bien, pero desde mi aplicacion no puedo acceder, dice que no se ha encontrado la ruta de acceso a la red, sera xq son equipos de una red virtual? ya he intentado de todo, ya logre impersonar, paso bien la ruta y nada, estaria muy agradecido si pudieran ayudarme