viernes, 20 de febrero de 2015

De los delegados a las lambdas 1: Comenzando

Inicio una nueva serie de post (3 en principio) dedicados a los delegados y las lambdas... que hace tiempo se convirtieron en algo imprescindible para todo desarrollador C#.

Empecemos por el principio: que es un delegado, métodos anónimos, predicados y todas esas cosas que algunos desarrolladores usan, sin llegar a comprender lo que hacen.

¿Qué es un delegado? 

 
Algo así (un poco de código vale mas que mil palabras):

   1:  delegate <return type> <delegate-name> <parameter list>

Un ejemplo:
 
   1:  delegate int delOperacionSobreEnteros(int num1, int num2);

Si eres de la vieja escuela habrás programado en C / C++ (que tiempos) así que lo más fácil es decir que es muy similar a un puntero a función.

Pero si no, te diré que un delegado es un tipo que permite almacenar un método. En el ejemplo anterior el delegado delOperacionSobreEnteros permite representar métodos que cumplan con su firma: recibe dos enteros como parámetros y devuelve un entero. Puesto que un delegado almacena un método, puede ser ejecutado.

Uno de los principales usos es definir delegados en los parámetros de un método, lo que nos permite mucha flexibilidad:

   1:  class Program
   2:  {
   3:      public delegate int delOperacionSobreEnteros(int num1, int num2);
   4:      static void Main(string[] args)
   5:      {
   6:   
   7:          delOperacionSobreEnteros d1 = OperacionSuma;
   8:          delOperacionSobreEnteros d2 = new delOperacionSobreEnteros(OperacionMultiplicacion);
   9:   
  10:          EjecutaOperacion(d1);
  11:          EjecutaOperacion(d2);            
  12:              
  13:          Console.ReadLine();
  14:      }
  15:   
  16:      protected static void EjecutaOperacion(delOperacionSobreEnteros delegado)
  17:      {
  18:          const int a=3;
  19:          const int b=2;
  20:              
  21:          Console.WriteLine("Vamos a llamar al delegado");
  22:          var res=delegado(a, b);
  23:          Console.WriteLine("a op b={0}", res);
  24:      }
  25:   
  26:      protected static int OperacionSuma(int a, int b)
  27:      {
  28:          return a + b;
  29:      }
  30:   
  31:      protected static int OperacionMultiplicacion(int a, int b)
  32:      {
  33:          return a * b;
  34:      }
  35:  }
 
Como veis tengo un método que se encarga de ejecutar operaciones (EjecutaOperacion). ¿Qué operación? La recibida como parámetro a través de un delegado. La salida del anterior ejemplo es la siguiente:
 

 
También podemos hacer que una propiedad de una clase sea un delegado...  seguro que se os ocurren más usos :-P
 

¿Y que es eso de los métodos anónimos?


Pues más de lo mismo, pero con sintaxis "reducida". Nos permiten asignar un bloque de código a un delegado, sin tener que declarar un método. Son por tanto métodos sin nombre, solo con cuerpo.

   1:  public delegate int delOperacionSobreEnteros(int num1, int num2);
   2:   
   3:  ...
   4:   
   5:  delOperacionSobreEnteros d3 = delegate (int a, int b)  { return a / b ;  };
   6:  var resd3=d3(10,2);
   7:  Console.WriteLine("10 / 2={0}", resd3);
 
Estos métodos anónimos han sido reemplazados por las lambdas, mucho más potentes y cómodos. Lo veremos en las siguientes entradas


Otros tipos de delegados

 
Si... hay más. ¿ habéis usado TaskFactory para ejecutar algo de un modo asíncrono ?
Pues bien, aunque tiene varias sobrecargas, vamos a fijarnos en estas:
 
   1:  public Task StartNew( Action action)
   2:  public Task StartNew<TResult>(Func<TResult>)  

La primera permite ejecutar código asíncrono que no devuelve nada. Veis que recibe un parámetro tipo Action
La segunda permite ejecutar código asíncrono que devolverá un valor, y recibe un parámetro tipo Func
 
¿Y que son action y Func?  Pues otros tipos de delegados.
 
Action <T1, T2,...> es un delegado que puede almacenar un método que recibe hasta 16 parámetros, pero que no retorna valor (void)
Func <T1,T2..., TResult>  también almacena un método con hasta 16 parámetros, pero puede devolver un valor
 
Veámos un ejemplo con Func:
 
   1:  Func<int, int, int> funcSuma = OperacionSuma;
   2:  var resFunc = funcSuma.Invoke(6, 7);
   3:  Console.WriteLine("6 + 7={0}", resFunc);
 
Como veis, me ahorro la definición del delegado y utilizo uno que ya viene de serie con el framework. ¿Más cómodo no?

¿Y predicate?

De nuevo es un delegado que viene definido en .NET Framework (msdn). En este caso se trata de un delegado que recibe un elemento como parámetros y que devuelve un booleano:

   1:  public delegate bool Predicate<in T>(
   2:      T obj
   3:  )

Se usa principalmente en ciertos métodos de filtrado sobre los tipos de datos Array y List. Mirad el siguiente ejemplo, creo que es autoexplicativo:  
 
 
   1:  Predicate<int> predMayor = MayorQue10;
   2:   
   3:  var lista = new List<int>() { 3, 6, 15, 45 };
   4:   
   5:  var listaFiltrada = lista.FindAll(predMayor);
   6:   
   7:  Console.WriteLine("Elementos de la lista mayores que 10:");
   8:  foreach(var elem in listaFiltrada)
   9:      Console.Write("{0} ", elem);
  10:   
  11:   
  12:  protected static bool MayorQue10 (int valor)
  13:  {
  14:      return (valor > 10);
  15:  }
 
 
Con esto terminamos, espero que lo anterior os resulte claro y útil.
 
Como siempre aquí os dejo el código.

miércoles, 18 de febrero de 2015

Claims Series 5: Autorizando!

Vamos a terminar esta serie dedicada a Claims autorizando, que es el objetivo final: Ver si un usuario puede o no realizar una operación en base al conjunto de claims que presenta.

Podemos utilizar atributos decorando métodos para indicar operaciones sobre recursos:

   1:  [ClaimsPrincipalPermission(SecurityAction.Demand, Operation="operacion", Resource="recurso")]

O bien podemos comprobar la seguridad programáticamente:

   1:   ClaimsPrincipalPermission.CheckAccess("operacion", "recurso");

Pero claro, ¿como realizamos la autorización?  Debemos codificar una clase que heredando de ClaimsAuthorizationManager sobrescriba el método CheckAccess:

   1:  public override bool CheckAccess(AuthorizationContext context)
   2:  {
   3:      return base.CheckAccess(context);
   4:  }

Aquí implementaremos la lógica de autorización en base a las claims presentadas por el usuario.

Vamos con un ejemplo

 
Supongamos que queremos autorizar dos operaciones, una se encarga de formatear mi laptop y otra mi desktop (vivo al limite!). Dichas operaciones quedarían así decoradas:

   1:  [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = "Format", Resource = "Desktop")]
   2:  protected static void Operation1 ()
   3:  {
   4:        Console.WriteLine("Operation1: Format Desktop");
   5:  }
   6:   
   7:  [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = "Format", Resource = "Laptop")]
   8:  protected static void Operation2()
   9:  {
  10:        Console.WriteLine("Operation2: Format Laptop");
  11:  }

Bien, para realizar la autorización vamos a basarnos en dos claims:

Claim
Valor
Significado
http://serginet.com/isUserUsingMyLaptop
true/false
Indica si el usuario puede formatear el laptop
http://serginet.com/isUserUsingMyDesktop
true/false
Indica si el usuario puede formatear el desktop


Tenemos, como he comentado antes, que crear una clase que herede de ClaimsAuthorizationManager  y sobrescriba el método CheckAccess:

   1:  public override bool CheckAccess(AuthorizationContext context)
   2:  {
   3:      string resource = context.Resource.First().Value;
   4:      string action = context.Action.First().Value;
   5:   
   6:      if (action == "Format" && resource == "Laptop")
   7:      {
   8:          bool isLaptopUser = context.Principal.HasClaim("http://serginet.com/isUserUsingMyLaptop", "true");
   9:          return isLaptopUser;
  10:      }
  11:      else if (action == "Format" && resource == "Desktop")
  12:      {
  13:          bool isDesktopUser = context.Principal.HasClaim("http://serginet.com/isUserUsingMyDesktop", "true");
  14:          return isDesktopUser;
  15:      } 
  16:      return false;
  17:  }

 
Como veis se comprueban los claims en base al par operación/recurso y se retorna un booleano indicando si la operación está permitida.
 
A continuación, y de modo similar a como vimos en la entrada anterior, debemos configurar nuestro manager de autorización en el archivo de configuración de la aplicación. Veréis que he configurado también un transformador, para que incluya las claims necesarias en el momento de la autenticación (si no lo hacemos el usuario nunca va a tener las claims necesarias):

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:    <configSections>
   4:      <section name="system.identityModel"
   5:          type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
   6:    </configSections>
   7:   
   8:    <system.identityModel>
   9:      <identityConfiguration>
  10:        <claimsAuthenticationManager type="Claims5_Autorizando.CustomClaimsTransformer,Claims5_Autorizando"/>
  11:        <claimsAuthorizationManager type="Claims5_Autorizando.CustomClaimsAuth,Claims5_Autorizando"/>
  12:      </identityConfiguration>
  13:    </system.identityModel>
  14:   
  15:    <startup>
  16:      <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  17:    </startup>
  18:   
  19:  </configuration>

He utilizado el mismo transformador de la entrada anterior: Claims Series 4: Transformaciones
Se encarga de añadir una claim al usuario que ejecuta la aplicación siempre que dicho usuario sea un usuario de mi equipo (cambiadlo vosotros o no os funcionará). Es decir, en mi caso añade la siguiente claim: "http://serginet.com/isUserUsingMyDesktop" con el valor true
 
Una vez tenemos todo podemos probar a llamar a las operaciones. La primera funciona correctamente ya que el usuario tiene el claim necesario,. Sin embargo al ejecutar la segunda obtenemos una excepción:

   1:  class Program
   2:  {
   3:      static void Main(string[] args)
   4:      {
   5:   
   6:          SetCurrentPrincipal();
   7:   
   8:          Operation1();
   9:          Operation2();
  10:      }
  11:   
  12:      [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = "Format", Resource = "Desktop")]
  13:      protected static void Operation1 ()
  14:      {
  15:          Console.WriteLine("Operation1: Format Desktop");
  16:      }
  17:   
  18:      [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = "Format", Resource = "Laptop")]
  19:      protected static void Operation2()
  20:      {
  21:          Console.WriteLine("Operation2: Format Laptop");
  22:      }
  23:   
  24:      private static void SetCurrentPrincipal()
  25:      {
  26:          WindowsPrincipal incomingPrincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
  27:          Thread.CurrentPrincipal = FederatedAuthentication.FederationConfiguration.IdentityConfiguration
  28:              .ClaimsAuthenticationManager.Authenticate("none", incomingPrincipal);
  29:      }
  30:  }
 
 
 
Con esto finalizamos la serie dedicada a claims. No es más que una introducción... habrá que profundizar!

Como siempre os dejo el código

Posts publicados en Claims Series:

martes, 17 de febrero de 2015

Claims Series 4: Transformaciones

Después de bastante tiempo continuo con esta serie dedicada a la seguridad basada en claims.

Autenticación y Tokens


Una de los principales "mantras" que debemos asumir es que las aplicaciones no deben preocuparse de como los claims han sido generados, por ejemplo que tipo de autenticación se ha utilizado para determinar que el username es "Tancredo" (existe un claim que dice eso).

Tenemos en .NET 4.5 un mecanismo basado en plugins que nos aísla de cada tipo de autenticación (de su implementación concreta) y que se encarga de generar los claims necesarios. Los mecanismo de autenticación soportados son varios: Autenticación tipo Forms, Windows integrada, SAML, etc... 

Estos mecanismo emiten tokens de seguridad en diferentes formatos, de los cuales podemos extraer los claims utilizando un objeto de tipo SecurityTokenHandler, cuyas funciones son:

  • Leer tokens
  • Escribir tokens
  • Validar tokens
  • Determinar el tipo de un token
Para ello en el framework disponemos de clases especializadas para cada tipo de credenciales:

  • KerberosSecurityTokenHandler
  • RsaSecurityTokenHandler
  • SamlSecurityTokenHandler
  • UserNameSecurityTokenHandler
  • Otras
 
El pipeline utilizado es el siguiente:
 
  1. La aplicación recibe un token en un formato dado (XML, binario, etc...)
  2. El token es deserializado (Método ReadToken)
  3. El token es validado (Método ValidateToken). El resultado de una validación afirmativa es la generación de un ClaimsPrincipal
  4. El objeto ClaimsPrincipal  es transformado de acuerdo a las necesidades (opcional)
La autenticación es algo que se escapa del ámbito de lo que estamos tratando, así que vamos a centrarnos en el punto 4. Una vez que el mecanismo de autenticación ha generado un ClaimsPrincipal, puede que no lleve la información que espero.  ¿Cómo lo transformo según necesite?


Transformando Claims


Para transformar, lo que debemos hacer es generar una nueva clase, que heredando de ClaimsAuthenticationManager sobrescriba el método Authenticate.

   1:      public class CustomClaimsTransformer : ClaimsAuthenticationManager
   2:      {
   3:          public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
   4:          {
   5:              throw new NotImplementedException();
   6:          }
   7:      }


Antes de empezar hay que añadir las referencias pertinentes:
 
 
 
Pues bien, vamos a mi implementación de ejemplo. Lo que voy a hacer es analizar el username que recibo para ver si es un usuario local a un equipo concreto que tengo en casa (mi desktop). Si es así emitiré un claim indicándolo.
Asumimos que este claim será mas tarde utilizado por la aplicación (¿no es muy buen ejemplo verdad?)

   1:   public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
   2:          {
   3:              string nameClaimValue = incomingPrincipal.Identity.Name;
   4:              List<Claim> claimsCollection = new List<Claim>();
   5:              claimsCollection.Add(new Claim(ClaimTypes.Name, nameClaimValue));
   6:                          
   7:              if(nameClaimValue.IndexOf("RAISTLIN-DT") >=0)
   8:              {
   9:                  //We add claim indicating user is working on my desktop
  10:                  claimsCollection.Add(new Claim("http://serginet.com/isUserUsingMyDesktop", "true"));
  11:   
  12:              }
  13:              else
  14:              {
  15:                  //We add claim indicating user is not working on my desktop
  16:                  claimsCollection.Add(new Claim("http://serginet.com/isUserUsingMyDesktop", "true"));
  17:              }
  18:   
  19:              return new ClaimsPrincipal(new ClaimsIdentity(claimsCollection, "MyCustomAuth"));
  20:          }       

El siguiente paso es configurar la aplicación para que se utilice el nuevo manager:

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:    <configSections>
   4:      <section name="system.identityModel" 
   5:          type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
   6:    </configSections>
   7:     
   8:  <system.identityModel>
   9:    <identityConfiguration>
  10:      <claimsAuthenticationManager type="Claims4_Transformaciones.CustomClaimsTransformer,Claims4_Transformaciones"/>
  11:    </identityConfiguration>
  12:  </system.identityModel>
  13:    
  14:   <startup> 
  15:          <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  16:      </startup>
  17:   
  18:  </configuration>

Y para acabar, podemos hacer uso del nuevo manager:

   1:   class Program
   2:      {
   3:          static void Main(string[] args)
   4:          {
   5:              SetCurrentPrincipal();
   6:          }
   7:   
   8:          private static void SetCurrentPrincipal()
   9:          {
  10:              WindowsPrincipal incomingPrincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
  11:              Thread.CurrentPrincipal = FederatedAuthentication.FederationConfiguration.IdentityConfiguration
  12:                  .ClaimsAuthenticationManager.Authenticate("none", incomingPrincipal);
  13:          }
  14:   
  15:      }

Si depuramos veremos que obtenemos un ClaimsPrincipal que contiene los 2 claims que hemos emitido:
 
 
 
Espero que os haya gustado!  En el próximo post "cerraremos el circulo" para explicar como permitir que se ejecuten acciones o no en una aplicación (autorización) en base a los claims del usuario.
 
Como siempre os dejo el código

Posts publicados en Claims Series: