
26/02/2010 08:46 por
zorry
En los servicios WCF se puede establecer seguridad a nivel de mensaje. Para poderlo hacer, hay que instalar un certificado, que será el que se emplee para cifrar el contenido que se requiera.
Ahora bien, un certificado generalmente es instalado por un administrador, pero un servicio generalmente corre con credenciales de usuario restringido. Con lo que normalmente no tendrá privilegios para poder acceder al fichero de la clave privada de los certificados. Vamos a ver cómo manejar esta situación.
Mas...
9f2a1c25-ca4f-4c01-a052-01ffb76c7867|0|.0

16/02/2010 12:48 por
zorry
Me comenta mi amigo Sergio, en relación a mi artículo de acceso a un certificado, que hay muchas veces que al reiniciar la máquina, se corrompe el certificado de desarrollo, con lo que tienen que eliminarlo y volverlo a crear. Este artículo cuenta cómo eliminar el certificado para poder volverlo a crear.
Mas...
96ca01a4-5244-4a5e-b584-99dc6ca911ae|0|.0

29/01/2010 13:18 por
zorry
Vamos a aprender a firmar un documento Xml con el estándar W3C Xml-DSig. En el mundo .Net, la clase SignedXml nos implementa gran parte del estándar, con lo que con unas cuantas líneas podremos implementar una tarea tediosa tiempo atrás.
Mas...
4fc0f820-9766-42b1-b4fa-5153c94668f0|1|5.0

29/01/2010 10:45 por
zorry
Vamos a aprender a leer un certificado del almacén de certificados de Windows. Esta tarea es bastante sencilla. Para poder programar con certificados de usuario X509 sin necesidad de tener que comprar un certificado a una entidad certificadora, podemos crear un certificado de pruebas. Este certificado únicamente nos valdrá en nuestra máquina local, ya que no posee una ruta de certificación completa y puede que en algunos escenarios avanzados no responda correctamente, pero para nuestro uso será más que suficiente.
Mas...
1772fe77-068e-46ac-8be5-e25e8537ec2d|2|5.0

29/12/2007 08:12 por
zorry
Revisando el fix que hice ayer, y debido sobre todo a que no me gusta hacer modificaciones en librerías que no dependen de mí (sobre todo, para evitar que en una nueva release del AjaxControlToolkit me machaquen los cambios), me he fijado en una propiedad del ToolkitScriptManager denominada CombineScriptsHandlerUrl. Esta propiedad permite especificar un handler específico para manejar la combinación de todos los script del AjaxControlToolkit. De modo que me dispongo a deshacer los cambios que hice ayer, y hago varios cambios en mi aplicación web:
- En la definición del ToolkitScriptManager, he incluído el siguiente atributo: CombineScriptsHandlerUrl="~/CombineScriptsHandler.ashx"
- He creado un nuevo handler en la aplicación con el nombre definido en el nombre anterior. En el archivo ashx he introducido el siguiente código:
1: <%@ WebHandler Language="C#" Class="CombineScriptsHandler" %>
2:
3: using System;
4: using System.Web;
5: using AjaxControlToolkit;
6:
7: public class CombineScriptsHandler : IHttpHandler
8: {
9: /// <summary>
10: /// ProcessRequest implementation outputs the combined script file
11: /// </summary>
12: /// <param name="context"></param>
13: public void ProcessRequest(HttpContext context)
14: {
15: if (!ToolkitScriptManager.OutputCombinedScriptFile(context))
16: {
17: throw new InvalidOperationException("Combined script file output failed unexpectedly.");
18: }
19: }
20:
21: /// <summary>
22: /// IsReusable implementation returns true since this class is stateless
23: /// </summary>
24: public bool IsReusable
25: {
26: get { return true; }
27: }
28: }
29:
Con estos cambios por fin he conseguido que la aplicación funcione correctamente en modo cookieless, y sin los problemas de javascript que me estaba encontrando anteriormente.
82354f83-f178-464d-8e98-09ec589bb2f2|0|.0

28/12/2007 15:12 por
zorry
Ok, por fin tengo mi aplicación funcionando en modo cookieless (para que funcione ciertas cosas de la aplicación que la consume). Instalo la última version del AjaxControlToolkit, configuro el tag ToolkitScriptManager correctamente, y pongo el tag CombineScripts a true, para que me combine todos los scripts generados por el toolkit en uno sólo.
Y al cargar la página, me pierde la sesión, en las trazas de IIS, veo que se está llamando a mi página inicial con otro SessionID, con lo que me da un error al cargar los scripts y la página no funciona...
En concreto el problema es que trata de cargar el script sin poner delante el SessionID:
/WebApp/Resumen.aspx?_TSM_HiddenField_=ctl00_scriptManager_HiddenField&_TSM_CombinedScripts_=%3b%3bAjaxControlToolkit%2c+Version%3d1.0.11119.32029%2c+Culture%3dneutral%2c+PublicKeyToken%3d28f01b0e84b6d53e%3aes-ES%3a2d550902-56f7-46bd-9795-b930029c9f3f%3ae2e86ef9%3a9ea3f0e2%3a9e8e87e9%3a1df13a87%3a80f47b59%3ad7738de7
Tenemos que lograr que el AjaxControlToolkit renderice la siguiente llamada para asegurarnos que no se pierde la sesión:
/WebApp/(S(w4fcmx45goyog355ydy4rw3w))/Resumen.aspx?_TSM_HiddenField_=ctl00_scriptManager_HiddenField&_TSM_CombinedScripts_=%3b%3bAjaxControlToolkit%2c+Version%3d1.0.11119.33116%2c+Culture%3dneutral%2c+PublicKeyToken%3d28f01b0e84b6d53e%3aes-ES%3af5528113-d4f3-4bcc-99aa-dc1a40d76a47%3ae2e86ef9%3a9ea3f0e2%3a9e8e87e9%3a1df13a87%3a80f47b59%3ad7738de7
Para ello, abrimos el código del AjaxControlToolkit... En concreto tocamos en la clase ToolkitScriptManager.cs... (Tengamos en cuenta que estoy trabajando con la release del 19 de Noviembre de 2007). La línea a modificar es la 144, y la reemplazaremos dejando el siguiente código:
144: _combinedScriptUrl = String.Format(CultureInfo.InvariantCulture,
145: "{0}?{1}={2}&{3}={4}", ((null != _combineScriptsHandlerUrl) ? _combineScriptsHandlerUrl.ToString()
146: : (new Uri(Page.Request.Url, Page.Request.RawUrl)).AbsolutePath)
147: , HiddenFieldParamName, HiddenFieldName, CombinedScriptsParamName,
148: HttpUtility.UrlEncode(SerializeScriptEntries(_scriptEntries, false)));
El código modificado se encuentra en la línea 146. De esta manera, se parsea la Url de la petición haciendo que se solicite la Url del script con el id de sesión, funcionando así correctamente la llamada Cookieless.
b3b7c0b1-e88d-463b-be94-01fe639b59ee|0|.0

05/12/2007 08:12 por
zorry
Al hilo del post anterior, la aplicación Windows que trabaja con nuestra aplicación web, es una aplicación con un Internet Explorer embebido. Como el manejo de las Url lo realiza por debajo, no soporta llamar a una Url con un SessionId específico de la siguiente forma:
http://servidor/Aplicacion/(S(i1hxwon1me1aazix1w1jnd55))/default.aspx
Esto es porque sólo soporta enviar parámetros mediante querystring, de la siguiente manera:
http://servidor/Aplicacion/default.aspx?SessionId=(S(i1hxwon1me1aazix1w1jnd55))/
Con lo que necesitamos realizar un reescrito de la Url para que el servidor coja correctamente el Id de sesión que queremos que emplee. Lo conseguimos mediante un modulo Http.
Lo primero es crearnos una clase dentro de nuestra aplicación web:
1: namespace MiAplicacion.Web.Modulo
2: {
3: public class RewriteModule : IHttpModule
4: {
5: public RewriteModule() { }
6:
7: public void Dispose() { }
8:
9: public void Init(HttpApplication application)
10: {
11: application.BeginRequest += new EventHandler(application_BeginRequest);
12: }
13:
14: void application_BeginRequest(object source, EventArgs e)
15: {
16: HttpApplication app = (HttpApplication)source;
17:
18: //Control de sesión cookieless
19: if (getIsCookielessSessionState())
20: processCookielessSessionId(app);
21: }
22: }
23: }
En este código nos creamos un manejador para el evento BeginRequest. En este manejador comprobamos que la aplicación tiene configurada la sesión cookieless (línea 19), y en el caso de que así sea, reescribir la url (línea 20).
Seguidamente, hay que modificar el web.config, para activar el módulo http:
1: <httpModules>
2: <add name="AuthHTTPModule" type="MiAplicacon.Web.Modulo.RewriteModule"/>
3: </httpModules>
Y por último, y no menos importante, el código para los métodos auxiliares:
1: private bool getIsCookielessSessionState()
2: {
3: object untypedSessionState = ConfigurationManager.GetSection("system.web/sessionState");
4: try
5: {
6: if (untypedSessionState != null)
7: {
8: System.Web.Configuration.SessionStateSection section =
9: (System.Web.Configuration.SessionStateSection)untypedSessionState;
10:
11: return section.Cookieless == HttpCookieMode.UseUri;
12: }
13: }
14: catch
15: {
16: return false;
17: }
18:
19: return false;
20: }
21:
22: private void processCookielessSessionId(HttpApplication app)
23: {
24: //Tratamiento para las sesiones cookieless
25: string parameterSessionId = "SessionId";
26: string url = app.Request.RawUrl;
27: int indexOfSessionId = url.IndexOf(parameterSessionId, StringComparison.CurrentCultureIgnoreCase);
28:
29: if (indexOfSessionId >= 0)
30: {
31: string sessionId = url.Substring(indexOfSessionId + parameterSessionId.Length + 1);
32: if (sessionId.IndexOf("&") >= 0)
33: sessionId = sessionId.Substring(0, sessionId.IndexOf("&"));
34:
35: if (sessionId.Length > 0)
36: {
37: url = url.Replace(app.Request.ApplicationPath, String.Empty);
38: url = url.Replace(parameterSessionId + "=" + sessionId, String.Empty);
39:
40: string newUrl = app.Request.ApplicationPath + "/" + sessionId + "/" + url;
41:
42: //Estandarizamos la url
43: newUrl = newUrl.Replace("?&", "?");
44: newUrl = newUrl.Replace("//", "/");
45:
46: if (newUrl[newUrl.Length - 1] == '?')
47: newUrl = newUrl.Remove(newUrl.Length - 1);
48: if (newUrl[newUrl.Length - 1] == '&')
49: newUrl = newUrl.Remove(newUrl.Length - 1);
50:
51: //Realizamos un redirect
52: app.Response.Redirect(newUrl);
53: }
54: }
55: }
En el primer método leemos el web.config para obtener si está activada la sesión cookieless (en el evento BeginRequest, no se puede acceder a los objetos de HttpApplication relativos a la sesión).
En el segundo método, parseamos la querystring y en el caso de que llegue el valor SessionId, realiza un Response.Redirect a la url con el SessionId embebido correctamente.
445cc567-d25f-405e-b5a3-944944e2c60e|0|.0

04/12/2007 21:12 por
zorry
En el proyecto en el que estoy trabajando, hemos tenido que securizar una transacción de datos mediante RSA. Este cifrado se emplea para asegurar que nadie puede obtener un usuario y contraseña que se envía entre una aplicación Windows Forms de cliente, y nuestra aplicación ASP.Net.
Si bien el cifrado se realiza sin problemas en la aplicación Windows Forms, cifrando con la clave pública, estaba teniendo problemas en el descifrado de los datos.
El código original era el siguiente:
1: RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(dwKeySize);
2: rsaCryptoServiceProvider.FromXmlString(xmlString);
3: byte[] encryptedBytes = System.Web.HttpServerUtility.UrlTokenDecode(inputString);
4: byte[] clearBytes = rsaCryptoServiceProvider.Decrypt(encryptedBytes, false);
5: return Encoding.UTF8.GetString(clearBytes);
Este código me estaba elevando una excepción en la línea 2, al tratar de leer la configuración de un string con el Xml y la clave privada. El error era un tanto críptico: CryptographicException: The system cannot find the file specified.
Navegando por la red, encontré un blog en el que especifica que el usuario que corre la aplicación debería poder realizar esta operación atacando el store de la máquina, con lo que me quedó este código:
1: CspParameters csp = new CspParameters();
2: csp.Flags = CspProviderFlags.UseMachineKeyStore;
3: RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(dwKeySize, csp);
4: rsaCryptoServiceProvider.FromXmlString(xmlString);
5: byte[] encryptedBytes = System.Web.HttpServerUtility.UrlTokenDecode(inputString);
6: byte[] clearBytes = rsaCryptoServiceProvider.Decrypt(encryptedBytes, false);
7: return Encoding.UTF8.GetString(clearBytes);
Esto es, añadiendo las dos primeras líneas y pasando los parámetros del CSP al constructor del Provider.
No obstante, los problemas no han acabado aquí. Como el resto del proceso corre impersonado, al parecer el GC da problemas al tratar de eliminar la clave privada del store. Aproximadamente al minuto de la ejecución del descifrado, se elevaba una excepcion CryptographicException: Keyset not found y se moría el proceso w3wp... Maldita sea...
Tras más investigaciones, logré averiguar que el GC trata de eliminar la clave del almacén, pero con las credenciales de otro usuario, por lo que no encuentra la clave y el proceso de IIS se muere.
Pero al final logré arreglarlo mediante el siguiente código:
1: CspParameters csp = new CspParameters();
2: csp.Flags = CspProviderFlags.UseMachineKeyStore;
3: RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(dwKeySize, csp);
4: rsaCryptoServiceProvider.FromXmlString(xmlString);
5: byte[] encryptedBytes = System.Web.HttpServerUtility.UrlTokenDecode(inputString);
6: byte[] clearBytes = rsaCryptoServiceProvider.Decrypt(encryptedBytes, false);
7: rsaCryptoServiceProvider.Clear();
8: return Encoding.UTF8.GetString(clearBytes);
Esto es, insertando la línea 7. Esta línea elimina la clave del almacén de manera explícita, con lo que el GC no tiene que hacerlo con posterioridad y el proceso finaliza correctamente... Por fin y tras dos días pegándome con ello :-(
8a6b1ae0-4549-4764-a1bd-beab150c9f6a|0|.0

03/06/2007 19:06 por
zorry
Bueno, tras una gran pausa, sigamos con este pequeño tutorial acerca de cómo usar las extensiones Ajax en .Net.
Partimos de una versión modificada de la demo del artículo anterior. Recordemos que en el artículo anterior vimos como realizar actualizaciones parciales de la página mediante el control UpdatePanel, evitando realizar postbacks completos del cliente al servidor, evitando el redibujado completo del navegador en cada cambio de la página.
En esta demo, en lugar de realizar la carga de un DropDownList desde otro, lo que hacemos es realizar la carga de un GridView dependiendo de la selección desde un DropDownList:
Lo que ocurrirá es que el usuario no sabrá si debe esperar para trabajar con la página, si la carga del grid inferior tarda más de lo normal (por ejemplo, si la base de datos está sobrecargada, o la consulta es compleja o trae muchos datos). Para ello, emplearemos el control UpdateProgress que nos facilita las extensiones Ajax. Para ello, en la página maestra, introduciremos el siguiente código:
<asp:UpdateProgress ID="UpdateProgress1" runat="server" >
<ProgressTemplate>
<div class="progress">
<asp:Image ID="Image1" runat="server"
ImageUrl="~/images/indicator_mozilla_blu.gif"/>
Actualizando página
</div>
</ProgressTemplate>
</asp:UpdateProgress>
Mediante este control, conseguimos que el HTML que está dentro del ProgressTemplate se muestre cuando las extensiones Ajax detecten que hay una llamada asíncrona al servidor. Dentro del template se puede poner cualquier código HTML ya que en cliente se renderiza con un DIV oculto. En este caso, estoy mostrando un DIV con una imagen animada y un texto indicando al usuario que la página se está actualizando. Este DIV tira de una clase llamada progress, que contiene cualquier decoración CSS que se nos ocurra. En nuestro caso, quería que se mostrara sobre los controles que están siendo actualizados, por lo que la clase progress queda en el ejemplo asi:
.progress
{
border-right: #ff6633 thin solid;
border-top: #ff6633 thin solid;
vertical-align: middle;
border-left: #ff6633 thin solid;
border-bottom: #ff6633 thin solid;
position: absolute;
background-color: #ffcc66;
text-align: center;
padding: 10px;
left: 100px; top: 100px;
}
Tras aplicar esto, y cambiar de selección en el DropDownList, podremos ver el siguiente efecto:
Esto es todo, hasta la siguiente entrega del tutorial.
f7a21ad3-47d5-42eb-8dba-be6c73120a94|0|.0

26/03/2007 05:03 por
zorry
A continuación veremos un primer ejemplo de implementación mediante el framework de Microsoft de AJAX. Para verlo, crearemos un "ASP.NET AJAX-Enabled website" en Visual Studio 2005, y lo denominaremos Ejemplo. Esta plantilla de Visual Studio nos generará un sitio Web, con un archivo Web.config con las modificaciones necesarias para que Ajax.Net funcione correctamente, así como una página default.aspx, la cual incluirá una referencia al control ScriptManager. Recordemos que en el artículo anterior vimos que el ScriptManager es el control necesario para que Ajax.pueda funcionar.
Incluimos el control ScriptManager
Es necesario incluir un control ScriptManager en cada página que vaya a emplear AJAX. En caso de una aplicación que emplee páginas maestras, lo ideal es incluirlo en la página maestra, evitando de esta manera tener que incluirlo en todas las páginas.
Eliminaremos la página default.aspx de la solución, y crearemos una página maestra denominada "master.master". Dentro de esta página, justo encima del control ContentPlaceHolder, incluiremos la siguiente línea:
<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true" />
Es importante especificar el atributo EnablePartialRendering, ya que es el que nos permitirá realizar modificaciones parciales de las páginas.
Empleamos el control UpdatePanel
Crearemos una página que emplee la página maestra que acabamos de crear, añadimos una "Web Content Form" llamada "default.aspx". Dentro de ésta página, vamos a crear dos controles DropDownList encadenados, de manera que cuando seleccionemos un valor del primero, cargue los valores del segundo. Para ello, empleamos el siguiente código, dentro de la página:
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<asp:DropDownList ID="ddl" runat="server" AutoPostBack="true" OnSelectedIndexChanged="ddl_SelectedIndexChange">
<asp:ListItem Text="Email" Value="1" />
<asp:ListItem Text="Telefono" Value="2" />
</asp:DropDownList>
<asp:UpdatePanel ID="updatePanel" runat="server">
<ContentTemplate>
<asp:DropDownList ID="ddl2" runat="server" />
</ContentTemplate>
</asp:UpdatePanel>
</asp:Content>
Adicionalmente, en el archivo default.aspx.cs incluiremos el siguiente código:
protected void ddl_SelectedIndexChange(object sender, EventArgs e)
{
ddl2.Items.Clear();
if (ddl.SelectedValue == "1")
{
ddl2.Items.Add(new ListItem("Valor 1", "1"));
ddl2.Items.Add(new ListItem("Valor 2", "2"));
}
else
{
ddl2.Items.Add(new ListItem("Valor 3", "3"));
ddl2.Items.Add(new ListItem("Valor 4", "4"));
}
}
De esta manera podremos ver cómo, al seleccionar un valor en el desplegable, el segundo desplegable cambia de valor. Pero en este caso, la actualización se está haciendo mediante un postback completo, en lugar de emplear Ajax para redibujar el segundo desplegable. Esto es así, porque el UpdatePanel no sabe que tiene que redibujarse al cambiar el primer desplegable (el primer desplegable está fuera del UpdatePanel). Para modificar la página y conseguir que se emplee Ajax, existen dos posibilidades:
- Introducir el primer desplegable dentro del UpdatePanel. De esta manera el UpdatePanel realizará las llamadas por Ajax a todos los eventos de servidor que se produzcan en su interior.
- Modificar el UpdatePanel, introduciendo un trigger configuranod el UpdatePanel para que "escuche" los eventos realizados por el desplegable. Ésta es la mejor manera desde mi punto de vista, puesto que para que las recargas sean óptimas, los UpdatePanel deberían ser lo más pequeños posibles.
Por tanto, la solución sería la siguiente, modificar el UpdatePanel introduciendo este código (antes o después del tag ContentTemplate):
<Triggers>
<asp:AsyncPostBackTrigger ControlID="ddl" EventName="SelectedIndexChanged" />
</Triggers>
1324c9e6-a239-48d6-a710-611b36fb4e94|0|.0