RSS 2.0 Atom 1.0 CDF  
JASoft.org - ASP.NET
El blog de José Manuel Alarcón Aguín. Programación .NET y mucho más...
 

El pasado lunes, en la charla on-line que impartí para el NEt User Group de la UOC sobre Dynamic Data, comenté que existía la posibilidad de sacarle partido a esta interesante tecnología sin tener que crear desde cero un proyecto especial de Dynamic Data, es decir, sin partir de este diálogo:

Tal y como prometí ese día, a continuación explico cómo añadir las capacidades de Dynamic Data a un proyecto Web pre-existente. Como veremos se trata de algo muy sencillo.

Dado que Dynamic Data se basa en el uso plantillas, tanto para las acciones como para generar campos, entidades concretas y otros controles, lo primero que debemos hacer es copiar desde un proyecto Dynamic Data pre-existente los siguientes elementos:

1.- La carpeta de nombre DynamicData
2.- La página maestra Site.master y su correspondiente archivo de código Site.master.vb (o.cs si trabajamos con C#)
3.- La hoja de estilos Site.css

Usa el propio explorador de archivos de Windows para copiarlos, y luego refresca la lista de archivos de proyecto con el botón correspondiente en el explorador de proyectos:

Con esto sólo no es suficiente, ya que también debemos declarar al menos un modelo de datos con el que trabajar.

Asumiendo que hemos definido un contexto de acceso a datos con Linq2SQL o con Entity Framework (enla figura anterior tenemos uno definido en App_Code, con el nombre "MiModelo.dbml"), lo que tenemos que hacer es declararlo en Global.asax a la hora de arrancar la aplicación. Así, debemos escribir en el evento Application_Start un código análogo al siguiente:

    Sub Application_Start(ByVal sender As Object, _
            ByVal e As EventArgs)
        Dim DefaultModel As MetaModel = New MetaModel()
        DefaultModel.RegisterContext(GetType(MiModeloDataContext), _
                New ContextConfiguration() With {.ScaffoldAllTables = True})
    End Sub

Además, debemos asegurarnos de que están incluídos los siguientes espacios de nombres:

<%@ Import Namespace="System.ComponentModel.DataAnnotations" %>
<%@ Import Namespace="System.Web.Routing" %>
<%@ Import Namespace="System.Web.DynamicData" %>

De esta manera, ya tenemos todo lo necesario para poder usar DynamicData junto con controles de acceso a datos en páginas propias. Más sobre esto enseguida.

Antes de continar me gustaría indicar que podemos hacer uso de las rutas de acceso dinámico a acciones con tablas simplemente declarándolas de la manera habitual, es decir, con un código similar al siguiente dentro de Application_Start en Global.asax:

    RouteTable.Routes.Add(New DynamicDataRoute( _ 
        "{table}/{action}.aspx") _
        With {.Constraints = New RouteValueDictionary( _
        New With {Key .Action = "List|Details|Edit|Insert"}), _
        .Model = DefaultModel})

de forma que quedaría un evento con el código de esta figura:

A partir de este momento podríamos manejar cualquier tabla de nuestro modelo de datos con tan sólo escribir una ruta del estilo de esta:

http://miapplicacion/Productos/List

para listar todos los productos de la base de datos y poder editarlos, borrarlos o añadir nuevos. O también:

http://miapplicacion/Clientes/Insert

para insertar un nuevo cliente en la tabla Clientes (si la hubiera). Como vemos muy útil...

Páginas propias con Dynamic Data

Además de lo que acabamos de ver, que genera páginas de mantenimiento con rutas pre-establecidas, podemos sacar partido a Dynamic Data en formularios propios usando controles normales de datos, como una rejilla (GrtidView) o un FormView, usando los controles especiales disponibles a tal efecto:

Por ejemplo, añade una nueva página y en ésta agrega un control DynamicDataManager (figura anterior), y desde el grupo de controles de datos un LinqDataSource o EntityDataSource (según la tecnología que hayas usado para el modelo de datos),  y una rejilla GridView.

En la vista de marcado de la página .aspx edita el control DynamicDataManager para que active esta tecnología en la rejilla, con un código como este:

        <asp:DynamicDataManager ID="DynamicDataManager1" runat="server" >
            <DataControls>
                 <asp:DataControlReference ControlID="GridView1" />
            </DataControls>
        </asp:DynamicDataManager>

Con esto lo que conseguimos es qu el control GridView1 sea capaz de generar automáticamente campos para mostrar y editar campos de la base de datos que se correspondan con el modelo de datos. Es decir, estamos habilitando Dynamic Data en ese control.

En el control LinqDataSource se configura el contexto de datos que representa a nuestro modelo para que utilice una de las tablas que nos interese (por ejemplo la de productos). Asignamos el control DataSource a la rejilla pero deshabilitamos la capacidad de ésta para generar los campos de manera automática.

Ahora sólo nos resta añadir campos dinámicos a la rejilla de forma que, en tiempo de ejecución, éstos se muestren de acuerdo con lo especificado en el modelo de datos (validación, formato, etc... gracias a las DataAnnotations) y en las plantillas para campos, tal y como se hace siempre en Dynamic Data. Para ello existe un nuevo tipo de campo llamado DynamicField que se usa precisamente para esto. Sólo tenemos que indicar el nombre del campo en el modelo que queremos enlazar y listo, ofreciéndosenos ayuda con Intellisense en Visual Studio:

Quedando campos con el siguiente aspecto:

pudiendo asignar otras características como el texto para la cabecera o el formato (si bien este último es más interesante asignarlo mediante metadatos en el modelo, ya que así nos aseguramos un uso consistente en toda la aplicación).

Si no queremos añadirlos a mano Visual Studio nos ofrece un pequeño asistente desde la lista de acciones de la rejilla:

lo que es mucho más cómodo. Podemos añadirle además un DynamicValidator a la página (ver grupo de controles de dynamic data en una figura anterior), asociándolo con el a rejilla. Con esto tenemos controlada la validación de cada uno de los campos dinámicos que editemos dentro de la rejilla.

Ahora, cuando ejecutemos esta página, dentro de la rejilla se mostrarán los campos de acuerdo con el modelo de datos, y tendremos automáticamente los controles de edición más apropiados según el tipo de datos y el modelo, validación de la información introducida, etc... con las ventajas de velocidad y robustez que ello conlleva.

Espero que esto os resulte útil (sobre todo a los que asistísteis el otro día a mi ponencia sobre Dynamic Data, jeje) :-)

Por: José Manuel Alarcon | Friday, March 19, 2010 7:32:21 PM (Hora estándar romance, UTC+01:00)  #    Comments [0] - Trackback
Tags: ASP.NET


Sígueme en:

:: Twitter JM Alarcón: tecnología, marketing, este blog y frikadas varias
:: Twitter campusMVP: los mejores recursos sobre tecnología Microsoft: trucos, artículos, noticias, vídeos...
:: Facebook campusMVP: los mismos mejores recursos pero en directamente en Facebook.
:: Boletín campusMVP Nuestra publicación electrónica, una vez al mes en tu buzón de correo.
 
Banner

Si llevas unos cuantos años en esto del desarrollo Web seguro que tienes todavía aplicaciones por ahí escritas en ASP 3.0, también conocido como "ASP Clásico". Este precursor del actual ASP era estupendo y funciona de maravilla aún hoy en día. A pesar de todas las virguerías técnicas existentes en la actualidad (que me encantan) me confieso un enamorado de esa antigua plataforma.

El caso es que aún hoy en día, si tienes que montar una aplicación de ASP 3.0 incluso en un moderno Windows Server 2008 R2 con IIS 7.5, podrás hacerlo sin problemas y funcionará todo de maravilla. O casi...

El otro día tuvimos que montar una de nuestras aplicaciones "legacy" en este entorno precisamente y todo parecía ir de maravilla. El caso es que nosotros instrumentamos todas nuestras aplicaciones, incluso  las antiguas, para llevar un registro automático de todos los eventos de interés que se producen: avisos, advertencias, operaciones importantes sobre los datos... y por supuesto los errores no gestionados y por tanto inesperados. Se trata de una buena práctica que deberías seguir en cualquier aplicación, y más en una para Internet y de la cual algunos ya me habréis oído hablar en ponencias por ahí.

En ASP clásico la forma de gestionar los errores inesperados pasa necesariamente por personalizar la página de error para el estátus 500 del servidor Web con una página .asp propia. En ésta se obtiene información sobre el error producido usando el método Server.GetLastError que devuelve un objeto ASPError con todos los detalles.

La configuración en IIS 7.0 o IIS 7.5 se hace de la siguiente forma:

1.- En las propiedades del servidor virtual se va a la sección de páginas de error:

2.- Una vez dentro de ésta aparecen la lista de códigos de estátus HTTP estándar, como por ejemplo el 404 para páginas no encontradas (muy recomendable gestionarlo), el 302 (acceso denegado), o el que nos interesa: 50, error interno del servidor. Sustituimos la página por defecto de IIS para el error 500 y colocamos una página .asp propia. Utilizando el mencionado objeto ASPError podemos obtener información sobre errores que se produzcan, anotar información sobre lo que ha pasado con todo lujo de detalles (error, tipo, ubicación, página, usuario, tipo de navegador...) para luego poder hacer un diagnóstico y detectar problemas. También devolveremos una página más bonita y adecuada para los usuarios, lo cual es muy importante también, y además es una buena práctica de seguridad el no mostrar mensajes detallados de error al usuario final.

No funciona en IIS 7.x

La idea es estupenda y funciona muy bien si lo hacemos en IIS 5.0 o 6.0, ¡pero en IIS 7.x no funcionará!. Es decir, sí que se llamará a nuestra página personalizada, y ésta funcionará. El problema es que el método Server.GetLastError devolverá un objeto con todas sus propiedades vacías, por lo que sabremos que se produce un error pero no tendremos ni un sólo detalle del mismo :-(

Se trata de un bug reconocido por Microsoft desde IIS 7.0, pero que no ha sido corregido en ninguno de los Service Pack ni tampoco en la versión R2 del sistema operativo (nueva versión 7.5 de IIS). Son los riesgos de usar una tecnología antigua para la que Microsoft no tiene la menor intención de seguir invirtiendo ni un segundo.

¿Cómo lo solucionamos?

Como casi siempre hay una vía de escape para solventar el problema. IIS 7.x dispone de un gestor global de códigos de estado HTTP que se usa cuando no hay uno específicamente designado en la configuración. Resulta que en este gestor global de errores el objeto ASPError sí que se rellena correctamente.

Por lo tanto lo que tenemos que hacer es eliminar el estátus HTTP 500 de la lista de errores gestionados por IIS, para que no tenga asignada ni siquiera la página por defecto. Si editamos la configuración general de este módulo de gestión global de errores:

Nos aparece un diálogo en el que podremos ajustar nuestra página de gestión global:

Allí deberemos introducir una ruta relativa a la raíz de nuestra aplicación (por ejemplo "/Logevents/logerr.asp" como en la figura), y usar en el PathType la opción de "Ejecutar una URL".

De este modo se recibirán perfectamente las propiedades del error y podremos trabajar como siempre :-)

¡Uppps! IIS me da un error de "violación de bloqueo" (Lock Violation)

Vaya. Cuando ya pensábamos que lo teníamos controlado y podríamos tener nuestra aplicación ASP Clásico funcionando a pleno rendimiento, al pulsar "OK" en el diálogo de la figura anterior nos sale este mensaje:

¿Qué demonios es esto? La verdad es que el mensaje no proporciona demasiada información al respecto. He de confesar que cuando nos salio la primera vez pensé que era un bug de la interfaz de gestión de IIS 7.5, que tenía interbloqueos mal hechos ;-P

A base de indagar y probar, ya que no había información en Internet al respecto y la poca que hay no está bien, llegué a la solución del problema.

Por defecto, hay ciertas secciones de la configuración global de IIS 7.x que están protegidas para impedir la modifiación en sub-ramas del servidor. Esto está diseñado de esta forma pensando en las empresas de hosting, ya que de esta manera pueden bloquear ciertas secciones peligrosas para que los administradores de los sitios web que venden no puedan cambiar ajustes que puedan comprometer la seguridad global del servidor. ¡Bien hecho Microsoft!. Lo malo es que no está nada claro en ningún sitio (o al menos a mi eso me parece), y a más de uno que gestiona sus propios servidores le puede traer por la calle de la amargura. ¡Mal hecho Microsoft! ;-)

En nuestro caso lo que debemos hacer es desbloquear este ajuste en la configuración global del servidor para que nos permita cambiarlo en la configuración secundaria de los sitios Web.

Para ello debes ir, como administrador, a la carpeta "C:\Windows\System32\Inetsrv" o equivalente (está ahí incluso en versiones de 64 bits de Windows). Dentro de ésta encontrarás el archivo applicationHost.config. Sácale una copia por si acaso te cargas algo que no debes, y ábrelo con el bloc de notas, ya que es un simple archivo de texto XML.

Una vez abierto busca el nodo "<httpErrors>". Encontrarás una rama que pondrá algo similar a esto:

<httpErrors defaultPath="" defaultResponseMode="ExecuteURL"
lockAttributes="allowAbsolutePathsWhenDelegated,defaultPath">

La parte importante aquí es el atributo lockAttributes. Como puedes observar, por defecto, tiene bloqueada la modificación de la ruta por defecto para los errores, que es precisamente lo que pretendemos modificar en nuestro sitio web.

Elimina la palabra "defaultPath" del atributo lockAttributes, graba el archivo y ciérralo. Ahora vuelve a IIS y cambia d enuevo la configuración en el diálgoo que hemos visto. Esta vez, al cerrarlo, no te generará la violación de bloqueo, y todos felices.

Este último consejo te valdrá para IIS en general, y por lo tanto para ASP.NET o incluso PHP, y no se restringe sólo a esta sección de la configuración.

Espero que si llegas hasta aquí a través de un búsqueda, desesperado/a por el problema, todo esto te haya servido. Si me lo quieres agradecer puedes dejar un comentario, y también ya sabes ;-)

Por: José Manuel Alarcon | Saturday, January 09, 2010 12:38:42 PM (Hora estándar romance, UTC+01:00)  #    Comments [0] - Trackback
Tags: ASP.NET | Sistemas operativos


Sígueme en:

:: Twitter JM Alarcón: tecnología, marketing, este blog y frikadas varias
:: Twitter campusMVP: los mejores recursos sobre tecnología Microsoft: trucos, artículos, noticias, vídeos...
:: Facebook campusMVP: los mismos mejores recursos pero en directamente en Facebook.
:: Boletín campusMVP Nuestra publicación electrónica, una vez al mes en tu buzón de correo.
 
Banner

Los temporizadores (Timer) de las extensiones de AJAX para ASP.NET son muy útiles. Nos permiten ejecutar una determinada tarea cada cierto tiempo, provocando postbacks de la página  (tanto completos, como parciales) en intervalos regulares. Un único Timer colocado en la página puede conseguir que se refresquen todos los UpdatePanels disponibles o cada uno de manera individual.

Lo habitual es colocarlos en la página y olvidarnos de ellos. Pero ¿qué pasa si queremos poder pararlos y activarlos a voluntad?

La cosa tiene más complicación de la que parece a simple vista. Lo primero que se nos ocurre a cualquiera es que, dado que tiene una propiedad Enabled para activarlo y desactivarlo bastará con establecerla en False para conseguir el efecto deseado. Si lo hacemos desde un postback asíncrono enviado desde dentro de un UpdatePanel, ni se notará en la página, ¿no?.

Lo malo de esta idea es que, simplemente, no funciona. El motivo es que en un refresco parcial de página, aunque se establezca la propiedad del temporizador, al retornar desde el servidor el cambio no se ve reflejado en el código JavaScript que controla el funcionamiento del mismo (un temporizador no es más que un simple setInterval() de JavaScript de los de toda la vida que provoca por código un refresco parcial de la página).

Entonces, para conseguirlo no nos queda más remedio que controlarlo desde el lado cliente, con JavaScript, lo cual tiene la ventaja añadida de que no es necesario un viaje al servidor para una cosa tan prosaica como esta, así que mejor.

Si observamos el código fuente del control Timer en lado cliente (basta con pulsar F12 en Internet Explorer y ver el código desde la pestaña Script) verás que tiene las siguientes definiciones en su prototipo:

Sys.UI._Timer.prototype = {
    get_enabled: Sys$UI$_Timer$get_enabled,
    set_enabled: Sys$UI$_Timer$set_enabled,
    get_interval: Sys$UI$_Timer$get_interval,
    set_interval: Sys$UI$_Timer$set_interval,
    get_uniqueID: Sys$UI$_Timer$get_uniqueID,
    set_uniqueID: Sys$UI$_Timer$set_uniqueID,
    dispose: Sys$UI$_Timer$dispose,
    _doPostback: Sys$UI$_Timer$_doPostback,
    _handleEndRequest: Sys$UI$_Timer$_handleEndRequest,
    initialize: Sys$UI$_Timer$initialize,
    _raiseTick: Sys$UI$_Timer$_raiseTick,
    _startTimer: Sys$UI$_Timer$_startTimer,
    _stopTimer: Sys$UI$_Timer$_stopTimer,
    _update: Sys$UI$_Timer$_update
}

Como podemos ver es posible hacer muchas cosas desde lado cliente, en concreto las interesantes son: ver/establecer si está habilitado o no, ver/establecer su intervalo, pararlo y ponerlo de nuevo en marcha.

Sabiendo esto tomar control sobre el timer es muy sencillo, basta con incluir este código JavaScript:

<script language="javascript" type="text/javascript">

var tmr = null;

   function ReferenciaTimer() { 
      if (tmr == null)
         tmr = $find("<%= Timer1.ClientID %>");
      return tmr;
   }

   function PararTimer() {
      ReferenciaTimer()._stopTimer();
   }

   function LanzarTimer() {
      ReferenciaTimer()._startTimer();
   }
</script>

Lo único que hago es obtener una referencia al control Timer usando su identificador de cliente (que se "inyecta" desde el servidor al generar la página gracias a una expresión <%= %> de ASP.NET) y la función especial de AJAX $find que sirve para obtener referencias a los componentes en el lado cliente. Luego para pararlo o ponerlo en marcha llamo los método _stopTimer o _startTimer del control JavaScript, que hemos visto en su propotipo. Si colocamos dos botones HTML normales y corrientes que llamen a estas dos funciones que acabo de definir:

   <input id="Button1" type="button" value="Parar Timer" onclick="PararTimer();" />
   <input id="Button2" type="button" value="Lanzar Timer" onclick="LanzarTimer();" />

¡Listo!

Con esto tenemos controlado el Timer y sin necesidad de ir al servidor para nada. Por supuesto podemo susar un código similar para cambiar o leer su intervalo de refresco o, incluso, para provocar manualmente un evento "tick" que lance el postback asociado al control.

He preparado un pequeño ejemplo práctico con todo lo explicado que puedes descargar desde aquí (3,11 KB)

¡Espero que te sea útil!

Por: José Manuel Alarcon | Monday, November 02, 2009 8:53:18 PM (Hora estándar romance, UTC+01:00)  #    Comments [0] - Trackback
Tags: AJAX | ASP.NET | JavaScript


Sígueme en:

:: Twitter JM Alarcón: tecnología, marketing, este blog y frikadas varias
:: Twitter campusMVP: los mejores recursos sobre tecnología Microsoft: trucos, artículos, noticias, vídeos...
:: Facebook campusMVP: los mismos mejores recursos pero en directamente en Facebook.
:: Boletín campusMVP Nuestra publicación electrónica, una vez al mes en tu buzón de correo.
 
Banner

En mis tres anteriores post me centré en JSONP, la solución soportada actualmente por cualquier navegador para hacer llamadas a servicios JSON ubicados en dominios diferentes al actual (ver índice al final de este post). Ahora, tras quince dís sin tiempo para nada (con Codecamp y TTT para Microsoft por el medio), por fin saco un par de horitas para escribir esta últma parte de la serie, dedicada a los estándares.

Las limitaciones del objeto XmlHttpRequest a la hora de hacer peticiones desde el navegador a páginas y recursos ubicados en otros dominios son de sobra conocidas. Ello ha hecho que los programadores busquen atajos para conseguir esta funcionalidad y saltarse esta barrera. Hasta ahora hemos visto cómo JSONP nos permite conseguirlo gracias al uso de la etiqueta <script>, y cómo se le puede sacar partido en muchas ocasiones. No obstante esta técnica tiene bastantes limitaciones:

· Sólo sirve para peticiones GET
· Los datos deben devolverse en formato JSON, o sea, JavaScript.
· La información que se puede enviar es muy limitada (únicamente datos planos como parámetros de la URL)
· El servidor debe colaborar explícitamente con el método para devolver el código envuelto en una llamada a un método JavaScript
· No existe un método estándar para controlar el acceso a este tipo de recursos remotos por parte del servidor que los provee, es decir, decidir quién y cuándo puede acceder al recurso.

Conscientes de esta necesidad en el World Wide Web Consortium (W3C), el organismo encargado de regular los estándares para la Web, han estado trabajando en una modificación del modo de funcionamiento habitual del objeto XmlHttpRequest para dotarlo de capacidades de acceso a otros dominios, así como de las convenciones necesarias para que los servidores que ofrecen servicios puedan regular el acceso de los navegadores a los mismos. El último borrador en el momento de escribir esto es de marzo de este año, lo escribe una persona que trabaja para el navegador Opera, y se puede leer aquí: Cross-Origin Resource Sharing. A partir de ahora en este texto le llamaré CORS a este protocolo, para facilitar la lectura.

Funcionamiento básico de CORS

El comportamiento por defecto del objeto XmlHttpRequest impide que se puedan hacer llamadas a recursos en otros dominios, impidendo así la realización de ataques de XSS y de XSRF, como ya hemos visto en los artículos anteriores. Lo que pretende la W3C con CORS es definir un protocolo para que los servidores Web y los navegadores colaboren a la hora de definir políticas de seguridad de acceso entre dominios.

Así, en su forma más básica, CORS define que cuando se haga una llamada con XmlHttpRequest a un dominio diferente del actual, dicha llamada debe incluir automáticamente una cabecera llamada "Origin" que indicará al servidor de destino el protocolo, dominio y puerto desde el que se está originando la petición. Esta cabecera difiere de la cabecera "Referer" en que no se indica ningún tipo de ruta, por lo que las preocupaciones de privacidad de los posibles usuarios son menores.

Con esta cabecera el servidor debe decidir si permite o no el acceso al recurso, devolviendo en su respuesta una cabecera de tipo Access-Control-Allow-Origin en la que indica al navegador qué dominios son permitidos como origen de las peticiones. Por ejemplo, puede devolver:

Access-Control-Allow-Origin: http://www.tuservidor.com

o, en muchos casos:

Access-Control-Allow-Origin: *

que significa que admite peticiones desde cualquier dominio externo.

Al recibir esta cabecera el navegador sabe si debe permitir o no que se realice la llamada. De esta forma se protege a los usuarios de un uso indebido de JavaScript para traspasar u obtener información entre dominios debido a alguna vulnerabilidad en el código de la página que permita inyectar JavaScript para hacer XSS o XSRF.

Por defecto, si el servidor no incluye esta cabecera el navegador bloquea la llamada, con lo que el comportamiento es exactamente el mismo que el tradicional.

Lo mejor es que realmente los programadores del lado cliente (del JavaScript del navegador) no tienen que hacer nada especial para soportar estas llamadas entre dominios, ya que pueden usar el objeto XmlHttpRequest como ya están acostumbrados a hacerlo y sobre lo que ya he hablado en este blog anteriormente, sólo que en la URL de destino incluyen la ruta completa, con el nombre de otro dominio.

El protocolo define todavía más cabeceras para cuestiones algo más especializadas, como por ejemplo para cachear el resultado de una valoración de seguridad y cuánto tiempo permanece guardada. Otra cosa muy interesante es lo que el borrador de CORS denomina "preflight requests". Se trata de una cómo provocar una petición previa por parte del navegador que, antes de solicitar el verdadero recurso en el que el código está interesado, lanza una petición con el método HTTP OPTIONS en el que el servidor le devuelve las condiciones de seguridad de las llamadas desde ese origen. El navegador las cachea y reutiliza durante un tiempo determinado. El programador no tiene que hacer dos llamadas para consegui esto, sino que con una única llamada mediante XmlHttpRequest, construida de determinada manera, provoca la obtención de esta información. Si tienes interés en profundizar en el estos detalles puedes leerte el borrador de CORS o también el documento que tiene al respecto la gente de la fundación Mozilla (que merendilla :-P).

Pero ¿qué conseguimos con esto?

Puede que a estas alturas te estés diciendo: "Vale, estupendo, el servidor indica qué dominios tienen acceso pero ¿cómo impides que con esto alguien haga una llamada al servicio con cualquier otro método manual, sin pasar por el navegador?¿Qué modo de proteger mi aplicación del servidor es esta?".

La respuesta es que este protocolo no pretende proteger el acceso a tus aplicaciones de servidor. No es un método de seguridad para servicios en Internet. Lo que busca es proteger a los usuarios que navegan usando un navegador estándar de problemas en el código de las páginas que visitan, de modo que de forma inadvertida para ellos un pirata pueda usar técnicas de inyección de código para obtener acceso a su información. Revisa el tercer post de esta serie, más abajo, para recordar cómo son este tipo de ataques.

Esta es una distinción fundamental, ya que muchos programadores a los que se les habla de este protocolo piensan que les servirá para proteger sus recursos en el servidor, y es justo al contrario: es para proteger el uso indebido de código de cliente. Así que ojo con esto. La protección de los recursos del servidor no tiene nada que ver con esto y cada aplicación deberá usar el método, estándar o no, que considere oportuno (como el uso de Cookies cifradas, como ya he analizado en este blog).

Por otra parte el hecho de que podamos usar XmlHttpRequest para este tipo de llamadas nos quita de un plumazo todas las limitaciones que comentaba al principio del post, ya que podemos enviar peticiones GET y POST, cuaqluier tipo de información de cualquier longitud y en cualquier formato de texto (XML, JSON...), y tampoco necesitamos colaboración explícita por parte del servidor más que la inclusión de la cabecera, algo que no complica la programación en absoluto.

Así que si expones un servicio en cualquier formato y quieres darle un punto más de seguridad a tus usuarios incluye la cabecera Access-Control-Allow-Origin para indicar desde qué dominios podrá ser utilizado.

Soporte de navegadores

En la actualidad, y a pesar de que se trata todavía de un borrador, la especificación de este protocolo está soportada por todos los navegadores recientes, es decir, por Firefox 3.5, Safari 4 y Chrome 2.0.

"¿Eh? Un momento, ¿no acabas de decir todos los navegadores recientes?. ¿Y donde está Internet Explorer 8.0? ¿Y Opera?"

Curiosamente, aunque alguien de Opera es el responsable del borrador en la W3C, el fabricante europeo parece que no se ha decidido a implementarlo todavía.

Por otro lado, lamentablemente, Microsoft ha ido por su cuenta con este asunto, ofreciendo su propia implementación y permitiendo hacer lo mismo mediante un nuevo objeto especial llamado XDomainRequest. En realidad, como verás si te lees el enlace anterior, el uso es casi igual al que haríamos con el objeto XmlHttpRequest, pero tiene pequeñas diferencias. Pero por lo demás funciona prácticamente igual y acepta las mismas cabeceras desde el servidor.

Esto, como puedes imaginar, ha creado una nueva corriente anti-IE entre la comunidad más activa en el desarrollo Web y los estándares. Mi opinión personal es también la misma: que deberían haber seguido el borrador al pie de la letra, con el objeto XmlHttpRequest y no con un objeto nuevo que le complique la vida a los scripts multi-navegador. Y más si pensamos que con IE8 Microsoft ha dado un giro espectacular hacia el soporte estricto de los estándares Web, alejándose de la nefasta corriente con la que tontearon con IE6 (motivo principal del clamor popular para eliminar esta infame versión 6.0 del navegador de Microsoft)

¿Por qué decidió Microsoft crear un objeto propio y no hacer como los demás lo que indica el borrador del estándar?. El motivo oficial es que consideran que CORS en su forma actual no es totalmente seguro. En un post en el blog del equipo de Internet Explorer hacen referencia a un documento sobre seguridad en el que indican (en la sección 5) entre otras los posibles ataques que se pueden producir con este protocolo. Gran parte de la comunidad de desarrolladores no está de acuerdo y han contestado a estas reservas que tiene el equipo de IE, como se hace eco este post de ajaxian, blog muy activo dedicado al mundo del desarrollo de lado cliente.

En resumen

CORS es una gran iniciativa para librarnos a los programadores de las dificultades actuales para hacer llamdas entre dominios y, al mismo tiempo, conseguir seguridad para los usuarios. Los principales navegadores en sus últimas versiones soportan el último borrador del protocolo, con la notable excepción de IE8, y tampoco Opera, aunque en este caso dado su baja cuota de usuarios no supone demasiado problema.

Conviene que tratemos de usarlo en nuestros desarrollos de servicios para mejorar la seguridad de los usuarios y aunque no esté totalmente soportado aún.

Es de esperar que cuando el protocolo pase de la fase de borrador a versión definitiva todos los navegadores lo soporten (esperemos que también IE).

Con esto termino la serie de artículos sobre llamadas a servicios entre dominios. Espero que te haya resultado útil, y recuerda que si quieres formarte en desarrollo Web o en cualquier tecnología de Microsoft, en campusMVP encontrarás los mejores cursos on-line tutelados por conocidos MVP.

Las otras partes de esta serie:

· Parte 1: JSONP: llamadas AJAX a servidores remotos
· Parte 2: JSONP (II): Soporte desde ASP.NET AJAX 4.0
· Parte 3: JSONP (yIII): Cuestiones de seguridad y ASP.NET rompiendo la compatibilidad en 3.5

Por: José Manuel Alarcon | Saturday, October 24, 2009 3:01:44 PM (Hora de verano romance, UTC+02:00)  #    Comments [0] - Trackback
Tags: AJAX | ASP.NET | JavaScript


Sígueme en:

:: Twitter JM Alarcón: tecnología, marketing, este blog y frikadas varias
:: Twitter campusMVP: los mejores recursos sobre tecnología Microsoft: trucos, artículos, noticias, vídeos...
:: Facebook campusMVP: los mismos mejores recursos pero en directamente en Facebook.
:: Boletín campusMVP Nuestra publicación electrónica, una vez al mes en tu buzón de correo.
 
Banner

En mis dos anteriores post (busca los enlaces al final de este post y leetelos antes si no lo has hecho) he estado hablando sobre la técnica de JSONP para consumo remoto de datos JSON entre dominios, algo a priori prohibido por el modelo de seguridad del navegador. Anteriormente comentaba que JSONP podría tener ciertos problemas de seguridad, y que para evitarlos en ASP.NET 3.5 se habían introducido cambios para paliarlos que rompían la compatibilidad con versiones anteriores. Voy a aclararlo ahora.

El problema de seguridad tiene que ver con la obtención de acceso no autorizado a información privada que se comparte con JSON.

Una de las técnicas habituales de securizar el acceso a los servicios, consiste en crear cookies encriptadas con una validez temporal limitada y asociadas a una sesión concreta. Esta técnica se utiliza de manera mayoritaria en los sitios Web actuales (como Facebook, Windows Live, o, como ya hemos visto, en la propia seguridad de ASP.NET). El problema de esta técnica es que la mayor parte de los navegadores actuales conservan las sesiones entre las diferentes pestañas abiertas (e incluso entre las diferentes ventanas), lo cual puede ser aprovechado por los atacantes para realizar ataques de phising y acceder a esa información privada en ciertos casos. Esto de las sesiones no tiene remedio fácil en navegadores como Firefox (al menos que yo sepa), pero en Internet Explorer 8.0 sí, como ya he explicado en su día.

Bien, supongamos que tenemos un servicio Web que devuelve información privada en formato JSON y que comprueba el acceso del usuario actual basándose en una cookie encriptada. El usuario tiene abierto el navegador con nuestra aplicación y recibe un correo electrónico de un atacante que lo "fuerza" a pulsar en un enlace aparentemente legal, pero que lo lleva a otra página controlada por el atacante. En esta página el pirata intenta una llamada a nuestro servicio JSON usando técnicas de JSONP, es decir, empleando etiquetas de tipo <script> para hacer la llamada. Al hacerlo, como las sesiones se conservan entre pestañas y ventanas, el servicio "se fía" y envía la información.

Lo que ocurre es que esta información generalmente será una matriz de datos JSON del estilo de la que vimos en el anterior post que devolvía Delicio.us. Al recibir en la página esta matriz, dentro de un <script>, aunque es válida realmente el atacante no puede hacer nada porque se recibe y al ser pura información, no asignada enningún sitio, simplemente se recibe y se descarta ¿no?

El problema es que en JavaScript es perfectamente lícito redefinir el constructor de una matriz, de modo que cuando se cree una de ellas se lleven a cabo algunas operaciones adicionales, por lo que el pirata puede escribir un código tan sencillo como este:

<script type="text/javascript">

var datos;

Array = function() {
  datos = this;
};

</script>

Lo que se hace es redefinir el constructor de las matrices en JavaScript, de modo que se asigna a una variable el contenido de ésta al ser creada. Este sencillo truco hace que al recibir el JSON desde el otro dominio, los datos se encuentren listos para ser usados por el pirata en el momento que lo desee, sin necesidad de estar de acuerdo con el otro servidor para envolver el JSON en una llamada a una función JavaScript, que sería lo normal y es lo que tendríamos que hacer normalmente. El pirata podría enviar a un tercer dominio los datos o hacer con ellos lo que quisiera.

El impacto de estas técnicas

¿Te parece un ataque muy rebuscado? Quizá lo sea, pero debes saber que GMail fue crackeado hace no tanto tiempo con una técnica similar para obtener acceso a los contactos de cualquier usuario. Además es, en definitiva, una técnica habitual de Cross Site Request Forgery (CSRF), las cuales tienen bastantes casos de éxito en todo tipo de Webs.

No obstante sí que hay algo que minimiza el posible impacto de esta técnica en la actualidad, y es que las últimas versiones de los navegadores están protegidas "de serie" contra ello. Así, ni Internet Explorer 7 u 8, ni Firefox 3 ni Chrome o Safari son vulnerables a la redifinición del constructor de la matriz o de la clase genérica Object. Firefox 2.x síque lo es, por ejemplo.

¿Quiere decir esto que estmao slibres de todo peligro?. En absoluto. para empezar tus usuarios pueden estar usando navegadores viejos. Además, lo cierto es que tus datos son accesibles desde otros dominios sin que esa fuera tu intención inicial, y hay otras posibles técnicas que se pueden utilizar (muchas de las cuales ni siquiera son conocidas hoy, pero pueden surgir). Por ejemplo, Twitter fue crackeado no hace mucho usando técnicas avanzadas de JavaScript y el acceso a los datos JSON remotos usando técnicas JSONP. Y otras similares pueden surgir en cualquier momento.

¿Qué se puede hacer para evitar este problema y otros del estilo que pueden aparecer?

Hay muchas recomendaciones que podemos seguir para evitar este problema.

· Primeramente podemos exigir que el acceso a nuestro servicio privado se haga siempre mediante POST y no usando el más habitual GET. El motivo es que, obviamente, la etiqueta <script> de HTML hace siempre la llamada usando un método GET, y por lo tanto este tipo de ataque no funcionaría.
· Otra técnica sencillña y efectiva es comprobar las cabeceras de la petición. Cuando la petición se hace con el objeto XmlHttpRequest generalmente las diferentes bibliotecas AJAX marcan la petición con una cabecera como esta: "X-Requested-With: XMLHttpRequest", para indicar que es una petición AJAX, y distinguirla de una normal. jQuery y ASP.NET AJAX lo hacen. Y si lo hacemos nosotros mismos es muy fácil incluirla también. Además generalmente si se hace una petición que espera JSON se suele indicar también el tipo de contenido esperado con una cabecera "Content-Type: application/json", cosa que tampoco hace la etiqueta <script>.

Otra cosa que hicieron los programadores de .NET para evitar el ataque descrito es incluir el famoso parámetro "d:" en todas las respuestas JSON de los servicios Web, tanto .asmx como WCF, expuesto como JSON.

En AJAX 1.0 para ASP.NET 2.0, cuando se devolvía un dato o colección de datos como JSON a través de un servicio Web, se obtenía un dato JSON normal y corriente, como el que acabamos de ver para Delicio.us, por ejemplo:

{"__type:": "Usuario", "Login":"usuario1", "Clave": "miclave"}

Sin embargo en la versión 3.5 de .NET (es decir, en realidad la versión de AJAX 1.0 específica para ser usada con Visual Studio 2008) y posteriores (ASP.NET 4.0), todas las respuestas JSON de servicios incluyen un parámetro adicional de nombre "d:", lo que convierte las respuestas en algo como esto:

{"d": {"__type:": "Usuario", "Login":"usuario1", "Clave": "miclave"} }

La diferencia en este caso es que siempre nos aseguramos de que lo que se devuelve es un objeto con un miembro de nombre "d" que es el que contiene los datos. En este ejemplo no se ve tanto la ventaja porque ya se devolvía un objeto, pero si devolviésemos una matriz de objetos (algo muy habitual) o un dato simple (como una cadena o un número) la cosa sí está más clara, ya que al devolverlo con forma de objeto y este miembro "d", conseguimos:

1.- Evitar que, aunque se redefina el constructor de la matriz, ello no tenga efecto sobre el acceso al JSON devuelto.
2.- El código no se pueda evaluar directamente en destino.

No tiene porqué ser una medida infalible porque pueden aparecer nuevos tipos de ataques que se salten la seguridad también, pero ayuda. Además, sigue siendo recomendable comprobar las cabeceras o permitir sólo POST para las peticiones de ciertos métodos de servicios, como ya he comentado.

Incompatibilidad

La principal consecuencia de añadir la "d" en las repsuestas JSON es que si tenemos código manual para leer estas respuestas y procesarlas, si cambiamos de versión de .NET (de la 2.0 a una superior) nos dejará de funcionar el código de JavaScript, debiendo, en tal caso hacer una de dos cosas:

1.- Leer las verdaderas propiedades de nuestros datos desde la propiedad "d" del objeto devuelto.
2.- Procesar previamente la respuesta antes de evaluarla para quitarle esa "d"

Si usamos ASP.NET AJAX no tendremos que preocuparnos pues ya hace esto último por nosotros,pero si el código es manual o usamos otras bibliotecas como jQuery, entonces tendremos que tenerlo en cuenta.

La mejor forma de verificarlo si tenemos que hacerlo de forma manual es usando la propiedad hasOwnProperty de la clase Object de JavaScript, soportado en todos los navegadores modernos (incluso IE6 y Firefox 2.0 lo soportan). Por ejemplo, si tenemos un servicio propio con las medidas de seguridad propuestas (sólo admite POST y comprueba que el tipo de datos solicitado sea JSON), si queremos llamarlo desde jQuery, con su función Ajax para llamadas a servicios, podríamos hacer esto y que funcionara con cualquier versión de .NET:

$.ajax({
  type: "POST",
  url: "miservicio.asmx/mimetodo",
  data: "{}",
  contentType: "application/json; charset=utf-8",
  dataType:"json",
  success: function(datos) {
    if (datos.hasOwnProperty("d"))
      ProcesarResultado(datos.d);
    else
      ProcesarResultado(datos);
  }
});
 
function ProcesarResultado(datos) {
  // aquí procesamos lo devuelto con la confianza de que nunca tendrá la "d"
}

Lo único que se hace es pre-procesar el resultado comprobando si tiene la propiedad "d" característica de ASP.NET 3.5+ o no, y si la tiene usar la propiedad para obtener los datos necesarios, o usar directamente lo devuelto en caso contrario.

Con cualquier otra biblioteca de AJAX o con código propio haríamos lo mismo o muy similar.

En resumen

La seguridad de los servicios basados en JSON puede estar comprometida entre dominios si no tenemos un poco de cuidado. ASP.NET AJAX a partir de .NET 3.5 incluye algunas medidas de protección, las cuales pueden producir algunas incompatibilidades. Si tenemos un poco de cuidado es fácil proteger a los usuarios de un uso indebido de su inforamción privada.

En un próximo post quiero comentar qué está preparando la W3C en las próximas versiones de HTML para facilitar nativamente las llamadas entre dominios y cómo incluso algunos navegadores hoy en día soportan estas características más seguras y estándar.

Las otras partes de este post:

· Parte 1: JSONP: llamadas AJAX a servidores remotos
· Parte 2: JSONP (II): Soporte desde ASP.NET AJAX 4.0

Por: José Manuel Alarcon | Monday, October 12, 2009 1:13:23 PM (Hora de verano romance, UTC+02:00)  #    Comments [0] - Trackback
Tags: AJAX | ASP.NET | JavaScript


Sígueme en:

:: Twitter JM Alarcón: tecnología, marketing, este blog y frikadas varias
:: Twitter campusMVP: los mejores recursos sobre tecnología Microsoft: trucos, artículos, noticias, vídeos...
:: Facebook campusMVP: los mismos mejores recursos pero en directamente en Facebook.
:: Boletín campusMVP Nuestra publicación electrónica, una vez al mes en tu buzón de correo.
 
Banner
Page 1 of 24 in the ASP.NET category Next Page
Copyright © 2010 José Manuel Alarcón Aguín. All rights reserved.