ASP.NET MVC: Mandar un byte[]

Este post surge a raíz de la siguiente pregunta en los foros de ASP.NET MVC de MSDN: http://social.msdn.microsoft.com/Forums/es-ES/aspnetmvces/thread/20a6935c-5903-4efd-8ca1-f5a70a047a15. El usuario se pregunta como mandar un byte[] de la vista al controlador. Y comenta que lo hace de la siguiente manera:

<iframe src="<%: Url.Action("GenerarPdf", "Consulta", 
new { documento = Model.Documento})%>" width="725" height="725"></iframe>

En el controlador tiene definida la acción correspondiente con un parámetro llamado documento de tipo byte[]. Y comenta que siempre recibe el parámetro con el valor null.

Veamos porque ocurre esto y cual es la solución.

El porqué

Primero veamos que URL nos genera una llamada a Url.Action como la siguiente:

@{
    var data = new byte[] {0x10, 0x11, 0x12};
}
<script type="text/javascript">
    alert('@Url.Action("Index", "Home", new {documento=data})')
</script>

El código HTML generado es el siguiente:

<script type="text/javascript">
    alert('/?documento=System.Byte%5B%5D')
</script>

Qué ha ocurrido aquí? Pues que al intentar pasar un byte[] a través de la URL, el helper Url.Action ha llamado simplemente al método .ToString() de dicho byte[] (que siempre devuelve System.Byte[]). ¡Es evidente con esto que el Model Binder no va a poder recuperar los valores de dicho byte[]!

La solución

Lo que necesitamos pasar es el contenido del byte[], pero claro… en qué formato podemos hacerlo? Porque recordad que estamos pasando bytes, que es contenido binario, pero en las URLs (y en los cuerpos de las peticiones http) no hay contenido binario, hay texto (un subconjunto de ANSI). La mejor solución es usar Base64. Base64 es un mecanismo para codificar bytes (no carácteres, bytes)  a un subconjunto de carácteres ANSI.

En C# pasar un byte[] a su cadena Base64 equivalente es tan simple como llamar al método Convert.ToBase64String:

<script type="text/javascript">
    alert('@Url.Action("Index", "Home", new {documento=Convert.ToBase64String(data)})')
</script>

Ahora el código HTML generado es:

<script type="text/javascript">
    alert('/?documento=EBES')
</script>

La cadena EBES es la codificación en Base64 del array de bytes. Ahora nos toca recibir esto en el controlador. Primero voy a modificar la vista para que genere un enlace:

@{
    var data = new byte[] {0x10, 0x11, 0x12};
}
<a href="@Url.Action("Ver", "Home", new {documento=Convert.ToBase64String(data)})">Pulsar aquí</a>

Y ahora la acción Ver del controlador:

public ActionResult Ver(byte[] documento)
{
  // Código
}

Y esto es lo que recibimos en el controlador:

image

Como podemos ver ASP.NET MVC ha sido capaz de convertir automáticamentela cadena en formato BASE64 a un array de bytes!

Por supuesto aquí he usado @Url.Action para mandar el array de bytes a través de la URL, pero si usáis un @Html.Hidden para mandarlo a través de un campo Hidden os funcionará igual. La clave es que el byte[] esté codificado usando Base64.

Disclaimer

En mi post he usado MVC3, pero en el foro el usuario usaba MVC2. No tengo ahora una versión de MVC2 a mano para probar si esta conversión automática de cadenas Base64 a byte[] ocurre como en MVC3. Creo que sí (aunque debería comprobarlo) pero supongamos que no.

Supongamos que si mandas una cadena Base64 recibes un null si el parámetro es de tipo byte[]. En este caso… ¿qué deberías hacer?

Pues la solución pasa por crearte tu propio Model Binder. Este Model Binder es el que recibiría una cadena (en formato Base64) y la transformaría a un byte[]:

public class Base64ModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var data = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;
        var array = Convert.FromBase64String(data);
        return array;
    }
}

Este ModelBinder convierte el valor en Base64 a un array de bytes. Por supuesto faltaría añadir código de comprobación de errores.

Ahora simplemente debemos decirle a ASP.NET MVC que use este ModelBinder cuando se encuentre con un byte[]:

ModelBinders.Binders.Remove(typeof (byte[]));
ModelBinders.Binders.Add(typeof(byte[]), new Base64ModelBinder());

Fijaos que primero elimino el ModelBinder asociado a byte[] y luego asocio mi Model Binder al tipo byte[]. La primera línea es necesaria porque ASP.NET MVC3 ya tiene un Model Binder asociado a byte[] (lógico,simplemente MVC3 trae de serie lo que estamos comentando justo ahora): Concretamente uno llamado ByteArrayModelBinder (http://msdn.microsoft.com/en-us/library/system.web.mvc.bytearraymodelbinder.aspx).

Insisto en que creo que MVC2 lo tiene también, pero bueno, estamos suponiendo que no (en este caso la llamada  a Remove no sería necesaria claro).

Y listos, con esto ya podemos enlazar cadenas en Base64 a parámetros byte[].

Un saludo!

Lidiando con oAuth (1/n) – Pseudoautenticacion oAuth

Muy buenas! Este post es el primero de una serie de “n” donde veremos como podemos lidiar un poco con oAuth 1.0a. Vamos a ver como implementar un cliente y lo más interesante un proveedor.

Para seguir esta serie de posts recomiendo la lectura del documento “Entendiendo oAuth” que he dejado en Slideshare (http://www.slideshare.net/eduardtomas/entendiendo-o-auth) donde se describe brevemente el procolo oAuth y los distintos flujos asociados a él.

Comentaros también que he dejado en codeplex una librería que os va a permtir crear de forma extremadamente fácil un proveedor de oAuth para ASP.NET y también un cliente. Podéis encontrar la librería en https://epnukeoauth.codeplex.com/ (de momento está tan solo el código fuente, debéis compilarla vosotros mismos).

Breve, brevísima, introducción a oAuth

oAuth es un protocolo de autorización pensado especialmente para aplicaciones web, aunque su uso se ha extendido en aplicaciones móviles y de escritorio. Lo que permite es que una aplicación (cliente) pueda acceder a los recursos de un servidor (usualmente una API estilo REST) en nombre de un determinado usuario, sin que en ningún momento la aplicación cliente tenga acceso a las credenciales de dicho usuario. Para conseguir esto hay un juego a 3 bandas entre la aplicación cliente, el servidor y quien posee les credenciales del usuario (que sí, suele ser el servidor).

Pongamos twitter como ejemplo. Cuando instalas un cliente de twitter, este cliente debe conectarse a tu cuenta para poder leer tweets y enviar tweets en tu nombre. Para ello el cliente no te solicitará tus credenciales si no que en su lugar:

  1. La aplicación cliente hará una petición al proveedor de servicios pasándole su token de aplicación (que identifica a la aplicación cliente). Este es el inicio de todo, signfica que la aplicación “quiere empezar una negociación oAuth”.
  2. Si el proveedor de servicios acepta la aplicación le mandará un token temporal.
  3. La aplicación cliente abrirá una ventana de navegador hacia una URL de quien pueda autenticar el usuario. En este caso twitter. Y le pasará a twitter este token temporal, diciéndole “Hey! El proveedor de servicios me acepta como cliente (tengo este token que lo demuestra) pero necesito que el usuario se autentique).”
  4. El usuario hará login en twitter con tus credenciales. Insisto: en twitter. En ningún momento la aplicación cliente recibe las credenciales.
  5. Twitter validará las credenciales y si son correctas pedirá que confirmes que das acceso a la aplicación”XXX” a tu cuenta. Si confirmas, se genera un token, el cual es enviado (el mecanismo exacto no importa ahora) a la aplicación cliente.
  6. La aplicación cliente, usa este token para hablar con el proveedor de servicios diciéndole: “Hey! Tengo este token que me han dado con el cual se supone que tengo acceso a los servicios”. En el caso de twitter el proveedor de servicios es el propio twitter.
  7. El proveedor de servicios valida este token y si es correcto, le manda otro token al cliente. Este otro token es el que sirve (durante un tiempo determinado) para hacer llamados a los servicios en nombre del usuario que se autenticó. El resto de tokens pasan a ser inváldos.
  8. El cliente usa este segundo token para hacer llamadas al proveedor de servicios.

Este es el escenario más complejo de oAuth (el llamado 3-legged). En el fondo cada vez que hemos hablado de “token” hay realmente dos tokens (uno público que se manda junto con la petición y otro secreto que se usa para firmar digitalmente la petición).

El principal problema a la hora de crear un proveedor de servicios de oAuth es todo el tema de la firma digital, ya que se debe ser extremadamente cuidadoso en este punto. Probablemente el 90% de implementaciones fallidas de oAuth, fallan en este punto.

Pseudo Autenticación oAuth

Este sea probablemente uno de los usos más conocidos de oAuth, el de usarlo para “autenticar” un usuario (el famoso botón de sign-in with twitter p.ej.). Vamos a ver como podemos crear una web que tenga una sección privada a la que se pueda acceder a través de un botón de “sign with twitter”. Vamos a usar la librería que os comentaba antes para hacerlo.

La idea es acceder a una acción de un controlador que sea privada:

  1. public class PrivateController : Controller
  2. {
  3.     [Authorize]
  4.     public ActionResult Index()
  5.     {
  6.         return View();
  7.     }
  8. }

Fijaos que la acción está marcada como privada de la forma estándard en ASP.NET MVC (usando [Authorize]). Si intentamos navegar a /Private/Index al no estar autenticados en ASP.NET MVC seremos redirigidos a la vista de login:

image

La vista muestra un formulario de login estándard (eso seria para entrar sin usar twitter, vamos a obviar este punto) y un enlace para entrar usando twitter. Si miramos el código fuente de la vista /Views/Account/LogOn.cshtml el enlace para usar twitter llama a la acción LogOnTwitter del controlador Account. Esta es la acción que empieza todo el flujo de oAuth. Veamos primero su código:

  1. public ActionResult LogOnTwitter()
  2. {
  3.     var oauthSession = new OAuthClientSession(ConfigurationManager.AppSettings["consumer-key"],
  4.         ConfigurationManager.AppSettings["consumer-secret"],
  5.         new NonceGenerator32Bytes());
  6.     var uri = ConfigurationManager.AppSettings["request_token"];
  7.     oauthSession.RequestTemporaryCredentials(uri, "POST", "http://127.0.0.1:64983/Account/Callback");
  8.     Session["oauth"] = oauthSession;
  9.     return Redirect(oauthSession.GetAuthorizationUri(ConfigurationManager.AppSettings["authorize"]));
  10. }

Creamos un objeto de la clase OAuthClientSession. Esta clase está pensada para encapsular toda una sesión oAuth, desde el inicio hasta la obtención de los tokens finales.

El constructor de OAuthClienteSession (que se encuentra en Epnuke.OAuth) es:

  1. public OAuthClientSession(string ckey, string csecret, INonceGenerator nonceGenerator)
  2. {
  3.     _consumerKey = ckey;
  4.     _consumerSecret = csecret;
  5.     _nonceGenerator = nonceGenerator;
  6. }

Listemos brevemento los parámetros:

  • ckey: Es el consumer key. Este es la clave que te da el proveedor (en este caso twitter) cuando registras tu aplicación en twitter. Todas las aplicaciones que quieran usar oAuth deben ser registradas en twitter.
  • csecret: Es el consumer secret. Es la otra clave que te da el proveedor cuando registras tu aplicación.
  • nonceGenerator: Es el generador de nonces. El nonce es un parámetro que debe ser introducido en cada llamada oAuth y que básicamente “debe ser único”. La librería incluye un generador de nonces llamado NonceGenerator32Bytes. La especificación oAuth no dice NADA al respecto del formato que debe tener el nonce.

Una vez tenemos el objeto oauthSession creado llamamos al método RequestTemporaryCredentials y empezamos la danza de oAuth. Llamara este método hará lo siguiente:

  1. Creará una petición http oAuth. Una petición http es oAuth si tiene una cabecera “oAuth” con un formato determinado.
  2. Envia esta petición alproveedor de servicios. En el caso de twitter esto es la URL https://api.twitter.com/oauth/request_token. Esta petición servirá para indicar al proveedor de servicios que la “aplicación XXX quiere iniciar una sesión oAuth”.

Si miramos con fiddler, ahí podremos ver nuestra petición:

image

Los datos enviados son:

POST https://api.twitter.com/oauth/request_token HTTP/1.1
Authorization: OAuth oauth_consumer_key="dbMEy71w8pGLPpbmxlwg", oauth_signature_method="HMAC-SHA1", oauth_version="1.0", oauth_timestamp="1338538452", oauth_nonce="v1TsKDiFyVZNjQ9dfffWS3AMRsWsvM3iycTJilGMY", oauth_callback="http%3A%2F%2F127.0.0.1%3A64983%2FAccount%2FCallback", oauth_signature="%2BsRXDw7GrMcZZ4Xp%2Bvyf%2FVNnhaE%3D"
Host: api.twitter.com

Y los datos recibidos (elimino algunos no relevantes):

HTTP/1.1 200 OK
Date: Fri, 01 Jun 2012 08:14:17 GMT
Status: 200 OK
Content-Length: 147
Content-Type: text/html; charset=utf-8
Pragma: no-cache
ETag: "6f0566f3a5d5e338f2cefbc9bd1a7c1f"
X-Runtime: 0.01432
X-Transaction: 91371892a8469732
X-MID: 48cd92e044b19b57d1a6b5e471ccd03d2915ce48
Expires: Tue, 31 Mar 1981 05:00:00 GMT
Vary: Accept-Encoding
Server: tfe

oauth_token=P6xUReTuLm7cDxbSAbDZ8jUriKBIO3zvFfTemXc6VM&
oauth_token_secret=yJi3IP7CRts2o68Rl2CQOgNsjRKFvZ8pFxMPJ7TKozY&
oauth_callback_confirmed=true

Fijaos en los datos de la cabecera de la petición (en azul). Esos datos conforman la cabecera oAuth y se van enviando a cada petición. Fijaos la firma digital que se computa usando el consumer secret y en como hemos enviado el consumer key (para identificar la aplicación que hace la petición).

Bueno, ya tenemos la respuesta de twitter: nos ha mandado el primer token (como dije antes siempre hay uno público y el secreto que usaremos para firmar la petición): oauth_token y oauth_token_secret. Este token (oauth_token y oauth_token_secret) es el token que dice que “twitter ha aceptado la aplicación cliente XXX como un cliente oAuth válido)”.

Ahora debemos recojer el token (oauth_token) y mandarlo a quien posee las credenciales, en este caso el mismo twitter (la URL exacta es https://api.twitter.com/oauth/authenticate). Eso es lo que hace el Redirect() de la última línea de la acción LogOnTwitter. Y el resultado:

image

Fijaos en la URL: https://api.twitter.com/oauth/authenticate?oauth_token=P6xUReTuLm7cDxbSAbDZ8jUriKBIO3zvFfTemXc6VM

Hemos pasado el oauth_token que hemos obtenido en el punto anterior. De esta manera el autenticador (twitter) sabe que el proveedor de servicios (la api de twitter) sabe quien es esa aplicación que se autodenomina “Epnuke OAuth Library Test”).

Ahora entro las credenciales de twitter… y continúa el baile:

image

Las dos peticiones marcadas de amarillo son la petición que acabamos de realizar a api.twitter.com/oauth/authenticate (el Redirect() que teníamos) y otra petición a 127.0.0.1/Account/Callback

¿Quien hace esta petición? Pues bien, mi navegador claro pero ¿por que? Pues bien porque una vez me he autenticado twitter debe comunicar a mi aplicación que el usuario está autenticado. Y esto lo hace mandando un código HTTP de redirección hacia esta URL. Y porque esta URL, como la sabe twitter? Pues básicamente porque esta URL (la llamada URL de callback) se introduce cuando se registra la aplicación en twitter. Fijaos en un tema importante: esto tan solo es posible si la aplicación que se quiere conectar con twitter es capaz de exponer un endpoint http. Es decir es, básicamente, una aplicación web. En el caso de que no sea así, existe otro mecanismo (en otro post lo trataremos).

Sigamos… Twitter nos ha mandado via querystring un código, llamado oauth_verifier que sirve para indicarnos que el autenticador ha aceptado las credenciales del usuario. Veamos el código de la acción Callback:

  1. public ActionResult Callback(string oauth_verifier)
  2. {
  3.     var oauthSession = Session["oauth"] as OAuthClientSession;
  4.     oauthSession.Authorize(oauth_verifier, ConfigurationManager.AppSettings["access_token"]);
  5.  
  6.     if (oauthSession.IsAuthorized)
  7.     {
  8.         var name = oauthSession.GetAdditionalData("screen_name");
  9.         FormsAuthentication.SetAuthCookie(name, false);
  10.         return RedirectToAction("Index", "Private");
  11.     }
  12.     else
  13.     {
  14.         return RedirectToAction("Index", "Home");
  15.     }
  16.  
  17. }

Recuperamos el objeto oAuthSession (que habíamos guardado a la sesión), recogemos el oauth_verifier y llamamos al método “Authorize”. Dicho método hace lo siguiente:

  1. Creará una petición oAuth (con el header oAuth y la firma digital)
  2. Enviará dicha petición al proveedor de servicios. Para decirle: “Hey! El autenticador ha aceptado las credenciales del usuario y me ha devuelto este token. Puedes darme el token final? Gracias”.

Si seguimos mirando fiddler:

image

Fijaos como esta última petición NO ha hecho Chrome sino el servidor web (es la petición del método Authorize). De nuevo si miramos los datos de la petición:

POST https://api.twitter.com/oauth/access_token?oauth_verifier=heqcwkOPuopuMdsoqdMYP8My6kycqOuLYFSeIFK33w HTTP/1.1
Authorization: OAuth oauth_consumer_key="dbMEy71w8pGLPpbmxlwg", oauth_signature_method="HMAC-SHA1", oauth_version="1.0", oauth_timestamp="1338539804", oauth_nonce="NQZmiFzvGpaBlY2JW4DAhdL1RBJrGq7qmlygByoIc", oauth_token="P6xUReTuLm7cDxbSAbDZ8jUriKBIO3zvFfTemXc6VM", oauth_signature="kiAPOn%2F8Ybb5oDfd6aPqsG6M750%3D"
Host: api.twitter.com
Connection: Keep-Alive

Y los de la respuesta (quito datos no relevantes)

HTTP/1.1 200 OK
Date: Fri, 01 Jun 2012 08:37:03 GMT
Status: 200 OK
X-MID: ee5c8c5470911804d71f005a28c69a5be0b72cf4
ETag: "4521b768dcce87386275e193df917953"
Expires: Tue, 31 Mar 1981 05:00:00 GMT
Content-Length: 162
X-Frame-Options: SAMEORIGIN
Pragma: no-cache
Last-Modified: Fri, 01 Jun 2012 08:37:03 GMT
X-Runtime: 0.04706
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
Server: tfe

oauth_token=84274067-2TMYBrU2cH4x6B3zsYeKYOdCyOpCrxoxKeYZCuDQ&
oauth_token_secret=TYRlbDfgb6gVGLxcoVWBTuJyR60pwC3w6fvaq2aw9g&
user_id=xxxxxxxx&
screen_name=eiximenis

Bueno, hemos recibido otro par de tokens. Ese par de tokens son el par de tokens “final” y que nos van a permitir hacer llamadas oAuth a twitter en nombre del usuario. Además recibimos el ID interno del usuario y su nombre.

En este punto hemos terminado el baile de tokens oAuth. Ya tenemos los tokens finales para “hacer cosas” en nombre del usuario.

Pero nosotros no queríamos hacer nada en nombre del usuario, tan solo usar oAuth para autenticar el usuario. ¡Bueno, ningún problema! El resto de código de la Accion Callback es puro ASP.NET:

  1. Nos aseguramos que la respuesta de oAuth ha sido correcta y que el usuario está autorizado según oAuth (oauthSession.IsAuthorized)
  2. Recogemos el valor del parámetro screen_name (ese parámetro NO es estándard de oAuth, lo envía twitter)
  3. Llamamos a FormsAuthorization.SetAuthCookie para autenticar el usuario dentro de ASP.NET.

¡Y listos! ¡Hemos terminado!

image

¡Hemos conseguido autenticarnos a nuesta web usando las credenciales de twitter!

En posteriores posts iremos explorando como generar estas peticiones oAuth, siempre apoyándonos en el código fuente de la librería que he dejado en CodePlex.

Un saludo!

PD: Si os descargáis el código fuente de Codeplex está este ejemplo.